New Upstream Release - binaryen

Ready changes

Summary

Merged new upstream version: 111 (was: 108).

Resulting package

Built on 2023-01-02T01:16 (took 22m33s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases binaryen-dbgsymapt install -t fresh-releases binaryen

Lintian Result

Diff

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d6db011..cf11cb1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -266,12 +266,14 @@ jobs:
     - name: install ninja
       run: sudo apt-get install ninja-build
     - name: emsdk install
+      # TODO: Go back to tot once problem with
+      # https://github.com/emscripten-core/emscripten/pull/17948 is resolved.
       run: |
         mkdir $HOME/emsdk
         git clone --depth 1 https://github.com/emscripten-core/emsdk.git $HOME/emsdk
         $HOME/emsdk/emsdk update-tags
-        $HOME/emsdk/emsdk install tot
-        $HOME/emsdk/emsdk activate tot
+        $HOME/emsdk/emsdk install latest
+        $HOME/emsdk/emsdk activate latest
     - name: update path
       run:  echo "PATH=$PATH:$HOME/emsdk" >> $GITHUB_ENV
     - name: emcc-tests
diff --git a/.gitignore b/.gitignore
index 9f3746c..45a0fca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ CMakeFiles
 /bin/
 /lib/
 /config.h
+/emcc-build
 compile_commands.json
 test/lit/lit.site.cfg.py
 
@@ -46,3 +47,6 @@ test/lit/lit.site.cfg.py
 
 # files related to Emsdk installation
 .emsdk_version
+
+# files related to clangd cache
+.cache/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a80469..b55e9cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,56 @@ full changeset diff at the end of each section.
 Current Trunk
 -------------
 
+v111
+----
+
+- Add extra `memory64` argument for `BinaryenSetMemory` and new
+  `BinaryenMemoryIs64` C-API method to determine 64-bit memory. (#4963)
+- `TypeBuilderSetSubType` now takes a supertype as the second argument.
+- `call_ref` now takes a mandatory signature type immediate.
+- If `THROW_ON_FATAL` is defined at compile-time, then fatal errors will throw a
+  `std::runtime_error` instead of terminating the process. This may be used by
+  embedders of Binaryen to recover from errors.
+- Implemented bottom heap types: `none`, `nofunc`, and `noextern`. RefNull
+  expressions and null `Literal`s must now have type `nullref`, `nullfuncref`,
+  or `nullexternref`.
+- The C-API's `BinaryenTypeI31ref` and `BinaryenTypeDataref` now return nullable
+  types.
+- The `sign-extension` and `mutable-globals` features are now both enabled by
+  default in all tools. This is in order to match llvm's defaults (See
+  https://reviews.llvm.org/D125728).
+- Add a pass to lower sign-extension operations to MVP.
+
+v110
+----
+
+- Add support for non-nullable locals in wasm GC. (#4959)
+- Add support for multiple memories. (#4811)
+- Add support for the wasm Strings proposal. (see PRs with [Strings] in name)
+- Add a new flag to Directize, `--pass-arg=directize-initial-contents-immutable`
+  which indicates the initial table contents are immutable. That is the case for
+  LLVM, for example, and it allows us to optimize more indirect calls to direct
+  ones. (#4942)
+- Change constant values of some reference types in the C and JS APIs. This is
+  only observable if you hardcode specific values instead of calling the
+  relevant methods (like `BinaryenTypeDataref()`). (#4755)
+- `BinaryenModulePrintStackIR`, `BinaryenModuleWriteStackIR` and
+  `BinaryenModuleAllocateAndWriteStackIR` now have an extra boolean
+  argument `optimize`. (#4832)
+- Remove support for the `let` instruction that has been removed from the typed
+  function references spec.
+- HeapType::ext has been restored but is no longer a subtype of HeapType::any to
+  match the latest updates in the GC spec. (#4898)
+- `i31ref` and `dataref` are now nullable to match the latest GC spec. (#4843)
+- Add support for `extern.externalize` and `extern.internalize`. (#4975)
+
+v109
+----
+
+- Add Global Struct Inference pass (#4659) (#4714)
+- Restore and fix SpillPointers pass (#4570)
+- Update relaxed SIMD instructions to latest spec
+
 v108
 ----
 
@@ -161,6 +211,13 @@ v100
 v99
 ---
 
+- Fix optimization behavior on assuming memory is zero-filled. We made that
+  assumption before, but it is incorrect in general, which caused problems.
+  The fixed behavior is to not assume it, but require the user to pass it in as
+  a flag, `--zero-filled-memory`. Large binaries with lots of empty bytes in the
+  data section may regress without that flag. Toolchains like Emscripten can
+  pass the flag automatically for users if they know it is right to assume,
+  which can avoid any regressions. (#3306)
 - `RefFunc` C and JS API constructors (`BinaryenRefFunc` and `ref.func`
   respectively) now take an extra `type` parameter, similar to `RefNull`. This
   is necessary for typed function references support.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e34de4..3538a3b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.10.2)
 # to reduce this for compatability with emsdk.
 set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version")
 
-project(binaryen LANGUAGES C CXX VERSION 108)
+project(binaryen LANGUAGES C CXX VERSION 111)
 include(GNUInstallDirs)
 
 # The C++ standard whose features are required to build Binaryen.
@@ -107,7 +107,7 @@ function(binaryen_setup_rpath name)
     set(_install_name_dir INSTALL_NAME_DIR "@rpath")
     set(_install_rpath "@loader_path/../lib")
   elseif(UNIX)
-    set(_install_rpath "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}")
+    set(_install_rpath "\$ORIGIN/../lib")
     if(${CMAKE_SYSTEM_NAME} MATCHES "(FreeBSD|DragonFly)")
       set_property(TARGET ${name} APPEND_STRING PROPERTY
                    LINK_FLAGS " -Wl,-z,origin ")
@@ -289,18 +289,28 @@ else()
     # explicitly undefine it:
     add_nondebug_compile_flag("-UNDEBUG")
   endif()
+  if(NOT APPLE AND NOT "${CMAKE_CXX_FLAGS}" MATCHES "-fsanitize")
+    # This flag only applies to shared libraries so don't use add_link_flag
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+  endif()
 endif()
 
 if(EMSCRIPTEN)
-  # link with -O3 for metadce and other powerful optimizations. note that we
-  # must use add_link_options so that this appears after CMake's default -O2
-  add_link_options("-O3")
-  add_link_flag("-s SINGLE_FILE")
-  add_link_flag("-s ALLOW_MEMORY_GROWTH=1")
-  add_compile_flag("-s DISABLE_EXCEPTION_CATCHING=0")
-  add_link_flag("-s DISABLE_EXCEPTION_CATCHING=0")
+  if("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
+    add_link_flag("-sERROR_ON_WASM_CHANGES_AFTER_LINK")
+    add_link_flag("-sWASM_BIGINT")
+  else()
+    # link with -O3 for metadce and other powerful optimizations. note that we
+    # must use add_link_options so that this appears after CMake's default -O2
+    add_link_options("-O3")
+    add_link_flag("-sSINGLE_FILE")
+  endif()
+
+  add_link_flag("-sALLOW_MEMORY_GROWTH")
+  add_compile_flag("-sDISABLE_EXCEPTION_CATCHING=0")
+  add_link_flag("-sDISABLE_EXCEPTION_CATCHING=0")
   # make the tools immediately usable on Node.js
-  add_link_flag("-s NODERAWFS")
+  add_link_flag("-sNODERAWFS")
   # in opt builds, LTO helps so much (>20%) it's worth slow compile times
   add_nondebug_compile_flag("-flto")
 endif()
@@ -370,6 +380,7 @@ else()
   message(STATUS "Building libbinaryen as shared library.")
   add_library(binaryen SHARED ${binaryen_SOURCES} ${binaryen_objs})
 endif()
+target_link_libraries(binaryen ${CMAKE_THREAD_LIBS_INIT})
 if(NOT (BUILD_STATIC_LIB AND BYN_INSTALL_TOOLS_ONLY))
   install(TARGETS binaryen
     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -396,14 +407,19 @@ if(EMSCRIPTEN)
   add_executable(binaryen_wasm
                  ${binaryen_emscripten_SOURCES})
   target_link_libraries(binaryen_wasm wasm asmjs emscripten-optimizer passes ir cfg support wasm)
-  target_link_libraries(binaryen_wasm "-s NO_FILESYSTEM=0")
-  target_link_libraries(binaryen_wasm "-s NODERAWFS=0")
-  target_link_libraries(binaryen_wasm "-s EXPORT_NAME=Binaryen")
-  target_link_libraries(binaryen_wasm "-s EXPORT_ES6=1")
-  target_link_libraries(binaryen_wasm "--post-js ${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js")
-  target_link_libraries(binaryen_wasm "--extern-pre-js ${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-extern-pre.js")
-  target_link_libraries(binaryen_wasm optimized "--closure 1")
-  target_link_libraries(binaryen_wasm optimized "--closure-args \"--language_in=ECMASCRIPT6 --language_out=ECMASCRIPT6\"")
+  target_link_libraries(binaryen_wasm "-sFILESYSTEM")
+  target_link_libraries(binaryen_wasm "-sEXPORT_NAME=Binaryen")
+  target_link_libraries(binaryen_wasm "-sNODERAWFS=0")
+  target_link_libraries(binaryen_wasm "-sEXPORT_ES6")
+  target_link_libraries(binaryen_wasm "-sEXPORTED_RUNTIME_METHODS=allocateUTF8OnStack")
+  target_link_libraries(binaryen_wasm "--post-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js")
+  target_link_libraries(binaryen_wasm "--extern-pre-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-extern-pre.js")
+  target_link_libraries(binaryen_wasm "-msign-ext")
+  target_link_libraries(binaryen_wasm "-mbulk-memory")
+  target_link_libraries(binaryen_wasm optimized "--closure=1")
+  target_link_libraries(binaryen_wasm optimized "--closure-args=\"--language_in=ECMASCRIPT6 --language_out=ECMASCRIPT6\"")
+  # TODO: Fix closure warnings! (#5062)
+  target_link_libraries(binaryen_wasm optimized "-Wno-error=closure")
   target_link_libraries(binaryen_wasm optimized "-flto")
   target_link_libraries(binaryen_wasm debug "--profiling")
   install(TARGETS binaryen_wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -412,45 +428,48 @@ if(EMSCRIPTEN)
   add_executable(binaryen_js
                  ${binaryen_emscripten_SOURCES})
   target_link_libraries(binaryen_js wasm asmjs emscripten-optimizer passes ir cfg support wasm)
-  target_link_libraries(binaryen_js "-s WASM=0")
-  target_link_libraries(binaryen_js "-s WASM_ASYNC_COMPILATION=0")
+  target_link_libraries(binaryen_js "-sWASM=0")
+  target_link_libraries(binaryen_js "-sWASM_ASYNC_COMPILATION=0")
   if(${CMAKE_CXX_COMPILER_VERSION} STREQUAL "6.0.1")
     # only valid with fastcomp and WASM=0
-    target_link_libraries(binaryen_js "-s ELIMINATE_DUPLICATE_FUNCTIONS=1")
+    target_link_libraries(binaryen_js "-sELIMINATE_DUPLICATE_FUNCTIONS")
   endif()
   # Disabling filesystem and setting web environment for js_of_ocaml
   # so it doesn't try to detect the "node" environment
   if(JS_OF_OCAML)
-    target_link_libraries(binaryen_js "-s NO_FILESYSTEM=1")
-    target_link_libraries(binaryen_js "-s ENVIRONMENT=web,worker")
+    target_link_libraries(binaryen_js "-sFILESYSTEM=0")
+    target_link_libraries(binaryen_js "-sENVIRONMENT=web,worker")
   else()
-    target_link_libraries(binaryen_js "-s NO_FILESYSTEM=0")
+    target_link_libraries(binaryen_js "-sFILESYSTEM=1")
   endif()
-  target_link_libraries(binaryen_js "-s NODERAWFS=0")
-  target_link_libraries(binaryen_js "-s EXPORT_NAME=Binaryen")
+  target_link_libraries(binaryen_js "-sNODERAWFS=0")
+  target_link_libraries(binaryen_js "-sEXPORT_NAME=Binaryen")
   # Currently, js_of_ocaml can only process ES5 code
   if(JS_OF_OCAML)
-    target_link_libraries(binaryen_js "-s EXPORT_ES6=0")
+    target_link_libraries(binaryen_js "-sEXPORT_ES6=0")
   else()
-    target_link_libraries(binaryen_js "-s EXPORT_ES6=1")
+    target_link_libraries(binaryen_js "-sEXPORT_ES6=1")
   endif()
-  target_link_libraries(binaryen_js "--post-js ${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js")
+  target_link_libraries(binaryen_js "-sEXPORTED_RUNTIME_METHODS=allocateUTF8OnStack")
+  target_link_libraries(binaryen_js "--post-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-post.js")
   # js_of_ocaml needs a specified variable with special comment to provide the library to consumers
   if(JS_OF_OCAML)
-    target_link_libraries(binaryen_js "--extern-pre-js ${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.jsoo-extern-pre.js")
+    target_link_libraries(binaryen_js "--extern-pre-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.jsoo-extern-pre.js")
   else()
-    target_link_libraries(binaryen_js "--extern-pre-js ${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-extern-pre.js")
+    target_link_libraries(binaryen_js "--extern-pre-js=${CMAKE_CURRENT_SOURCE_DIR}/src/js/binaryen.js-extern-pre.js")
   endif()
-  target_link_libraries(binaryen_js optimized "--closure 1")
+  target_link_libraries(binaryen_js optimized "--closure=1")
   # Currently, js_of_ocaml can only process ES5 code
   if(JS_OF_OCAML)
-    target_link_libraries(binaryen_js optimized "--closure-args \"--language_in=ECMASCRIPT6 --language_out=ECMASCRIPT5\"")
+    target_link_libraries(binaryen_js optimized "--closure-args=\"--language_in=ECMASCRIPT6 --language_out=ECMASCRIPT5\"")
   else()
-    target_link_libraries(binaryen_js optimized "--closure-args \"--language_in=ECMASCRIPT6 --language_out=ECMASCRIPT6\"")
+    target_link_libraries(binaryen_js optimized "--closure-args=\"--language_in=ECMASCRIPT6 --language_out=ECMASCRIPT6\"")
   endif()
+  # TODO: Fix closure warnings! (#5062)
+  target_link_libraries(binaryen_js optimized "-Wno-error=closure")
   target_link_libraries(binaryen_js optimized "-flto")
   target_link_libraries(binaryen_js debug "--profiling")
-  target_link_libraries(binaryen_js debug "-s ASSERTIONS")
+  target_link_libraries(binaryen_js debug "-sASSERTIONS")
   install(TARGETS binaryen_js DESTINATION ${CMAKE_INSTALL_BINDIR})
 endif()
 
diff --git a/README.md b/README.md
index b3fdb62..a6f2590 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,25 @@ There are a few differences between Binaryen IR and the WebAssembly language:
     `(elem declare func $..)`. Binaryen will emit that data when necessary, but
     it does not represent it in IR. That is, IR can be worked on without needing
     to think about declaring function references.
+  * Binaryen IR allows non-nullable locals in the form that the wasm spec does,
+    (which was historically nicknamed "1a"), in which a `local.get` must be
+    structurally dominated by a `local.set` in order to validate (that ensures
+    we do not read the default value of null). Despite being aligned with the
+    wasm spec, there are some minor details that you may notice:
+    * A nameless `Block` in Binaryen IR does not interfere with validation.
+      Nameless blocks are never emitted into the binary format (we just emit
+      their contents), so we ignore them for purposes of non-nullable locals. As
+      a result, if you read wasm text emitted by Binaryen then you may see what
+      seems to be code that should not validate per the spec (and may not
+      validate in wasm text parsers), but that difference will not exist in the
+      binary format (binaries emitted by Binaryen will always work everywhere,
+      aside for bugs of course).
+    * The Binaryen pass runner will automatically fix up validation after each
+      pass (finding things that do not validate and fixing them up, usually by
+      demoting a local to be nullable). As a result you do not need to worry
+      much about this when writing Binaryen passes. For more details see the
+      `requiresNonNullableLocalFixups()` hook in `pass.h` and the
+      `LocalStructuralDominance` class.
 
 As a result, you might notice that round-trip conversions (wasm => Binaryen IR
 => wasm) change code a little in some corner cases.
@@ -204,7 +223,7 @@ This repository contains code that builds the following tools in `bin/`:
    performs emscripten-specific passes over it.
  * **wasm-ctor-eval**: A tool that can execute functions (or parts of functions)
    at compile time.
- * **binaryen.js**: A standalone JavaScript library that exposes Binaryen methods for [creating and optimizing Wasm modules](https://github.com/WebAssembly/binaryen/blob/main/test/binaryen.js/hello-world.js). For builds, see [binaryen.js on npm](https://www.npmjs.com/package/binaryen) (or download it directly from [github](https://raw.githubusercontent.com/AssemblyScript/binaryen.js/master/index.js), [rawgit](https://cdn.rawgit.com/AssemblyScript/binaryen.js/master/index.js), or [unpkg](https://unpkg.com/binaryen@latest/index.js)).
+ * **binaryen.js**: A standalone JavaScript library that exposes Binaryen methods for [creating and optimizing Wasm modules](https://github.com/WebAssembly/binaryen/blob/main/test/binaryen.js/hello-world.js). For builds, see [binaryen.js on npm](https://www.npmjs.com/package/binaryen) (or download it directly from [github](https://raw.githubusercontent.com/AssemblyScript/binaryen.js/master/index.js), [rawgit](https://cdn.rawgit.com/AssemblyScript/binaryen.js/master/index.js), or [unpkg](https://unpkg.com/binaryen@latest/index.js)). Minimal requirements: Node.js v15.8 or Chrome v75 or Firefox v78.
 
 Usage instructions for each are below.
 
@@ -322,7 +341,7 @@ A C++17 compiler is required. Note that you can also use `ninja` as your generat
 
 To avoid the gtest dependency, you can pass `-DBUILD_TESTS=OFF` to cmake.
 
-Binaryen.js can be built using Emscripten, which can be installed via [the SDK](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html)).
+Binaryen.js can be built using Emscripten, which can be installed via [the SDK](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html).
 
 ```
 emcmake cmake . && emmake make binaryen_js
@@ -367,28 +386,31 @@ passes on it, as well as print it (before and/or after the transformations). For
 example, try
 
 ````
-bin/wasm-opt test/passes/lower-if-else.wat --print
+bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -
 ````
 
-That will pretty-print out one of the test cases in the test suite. To run a
+That will output one of the test cases in the test suite. To run a
 transformation pass on it, try
 
 ````
-bin/wasm-opt test/passes/lower-if-else.wat --print --lower-if-else
+bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o -
 ````
 
-The `lower-if-else` pass lowers if-else into a block and a break. You can see
-the change the transformation causes by comparing the output of the two print
-commands.
+The `name-types` pass ensures each type has a name and renames exceptionally long type names. You can see
+the change the transformation causes by comparing the output of the two commands.
 
 It's easy to add your own transformation passes to the shell, just add `.cpp`
 files into `src/passes`, and rebuild the shell. For example code, take a look at
-the [`lower-if-else` pass](https://github.com/WebAssembly/binaryen/blob/main/src/passes/LowerIfElse.cpp).
+the [`name-types` pass](https://github.com/WebAssembly/binaryen/blob/main/src/passes/NameTypes.cpp).
 
 Some more notes:
 
  * See `bin/wasm-opt --help` for the full list of options and passes.
- * Passing `--debug` will emit some debugging info.
+ * Passing `--debug` will emit some debugging info.  Individual debug channels
+   (defined in the source code via `#define DEBUG_TYPE xxx`) can be enabled by
+   passing them as list of comma-separated strings.  For example: `bin/wasm-opt
+   --debug=binary`.  These debug channels can also be enabled via the
+   `BINARYEN_DEBUG` environment variable.
 
 ### wasm2js
 
@@ -403,7 +425,7 @@ This will print out JavaScript to the console.
 For example, try
 
 ```
-$ bin/wasm2js test/hello_world.wat
+bin/wasm2js test/hello_world.wat
 ```
 
 That output contains
@@ -563,6 +585,19 @@ The `check.py` script supports some options:
  * We have tests from upstream in `tests/spec`, in git submodules. Running
    `./check.py` should update those.
 
+Note that we are trying to gradually port the legacy wasm-opt tests to use `lit`
+and `filecheck` as we modify them.  For `passes` tests that output wast, this
+can be done automatically with `scripts/port_passes_tests_to_lit.py` and for
+non-`passes` tests that output wast, see
+https://github.com/WebAssembly/binaryen/pull/4779 for an example of how to do a
+simple manual port.
+
+For lit tests the test expectations (the CHECK lines) can often be automatically
+updated as changes are made to binaryen.  See `scripts/update_lit_checks.py`.
+
+Non-lit tests can also be automatically updated in most cases.  See
+`scripts/auto_update_tests.py`.
+
 ### Setting up dependencies
 
 ```
@@ -612,13 +647,13 @@ Emscripten's WebAssembly processing library (`wasm-emscripten`).
 * Does it compile under Windows and/or Visual Studio?
 
 Yes, it does. Here's a step-by-step [tutorial][win32]  on how to compile it
-under **Windows 10 x64** with with **CMake** and **Visual Studio 2015**. 
+under **Windows 10 x64** with with **CMake** and **Visual Studio 2015**.
 However, Visual Studio 2017 may now be required. Help would be appreciated on
 Windows and OS X as most of the core devs are on Linux.
 
 [compiling to WebAssembly]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen
 [win32]: https://github.com/brakmic/bazaar/blob/master/webassembly/COMPILING_WIN32.md
-[C API]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#c-api-1
+[C API]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#c-api
 [control flow graph]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api
 [JS_API]: https://github.com/WebAssembly/binaryen/wiki/binaryen.js-API
 [compile_to_wasm]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#what-do-i-need-to-have-in-order-to-use-binaryen-to-compile-to-webassembly
diff --git a/debian/changelog b/debian/changelog
index aad1fc8..ba8becf 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+binaryen (111-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 02 Jan 2023 01:00:26 -0000
+
 binaryen (108-1) unstable; urgency=medium
 
   * New upstream version 108
diff --git a/auto_update_tests.py b/scripts/auto_update_tests.py
similarity index 97%
rename from auto_update_tests.py
rename to scripts/auto_update_tests.py
index 240fcbd..527b128 100755
--- a/auto_update_tests.py
+++ b/scripts/auto_update_tests.py
@@ -19,12 +19,12 @@ import subprocess
 import sys
 from collections import OrderedDict
 
-from scripts.test import binaryenjs
-from scripts.test import lld
-from scripts.test import shared
-from scripts.test import support
-from scripts.test import wasm2js
-from scripts.test import wasm_opt
+from test import binaryenjs
+from test import lld
+from test import shared
+from test import support
+from test import wasm2js
+from test import wasm_opt
 
 
 def update_example_tests():
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 696ccc4..2f4fad5 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1,12 +1,19 @@
 #!/usr/bin/python3
 
 '''
-Runs random passes and options on random inputs, using wasm-opt.
+Run various fuzzing operations on random inputs, using wasm-opt. See
+"testcase_handlers" below for the list of fuzzing operations.
 
-Can be configured to run just wasm-opt itself (using --fuzz-exec)
-or also run VMs on it.
+Usage:
 
-For afl-fuzz integration, you probably don't want this, and can use
+./scripts/fuzz_opt.py
+
+That will run forever or until it finds a problem.
+
+Setup: Some tools are optional, like emcc and wasm2c. The v8 shell (d8),
+however, is used in various sub-fuzzers and so it is mandatory.
+
+Note: For afl-fuzz integration, you probably don't want this, and can use
 something like
 
 BINARYEN_CORES=1 BINARYEN_PASS_DEBUG=1 afl-fuzz -i afl-testcases/ -o afl-findings/ -m 100 -d -- bin/wasm-opt -ttf --fuzz-exec --Os @@
@@ -26,6 +33,7 @@ import re
 import sys
 import time
 import traceback
+from os.path import abspath
 
 from test import shared
 from test import support
@@ -39,7 +47,6 @@ TYPE_SYSTEM_FLAG = '--nominal'
 
 # feature options that are always passed to the tools.
 CONSTANT_FEATURE_OPTS = ['--all-features']
-CONSTANT_FEATURE_OPTS.append(TYPE_SYSTEM_FLAG)
 
 INPUT_SIZE_MIN = 1024
 INPUT_SIZE_MEAN = 40 * 1024
@@ -119,7 +126,10 @@ def randomize_feature_opts():
                 FEATURE_OPTS.append(possible)
                 if possible in IMPLIED_FEATURE_OPTS:
                     FEATURE_OPTS.extend(IMPLIED_FEATURE_OPTS[possible])
-    print('randomized feature opts:', ' '.join(FEATURE_OPTS))
+    print('randomized feature opts:', '\n  ' + '\n  '.join(FEATURE_OPTS))
+    # Type system flags only make sense when GC is enabled
+    if '--disable-gc' not in FEATURE_OPTS:
+        FEATURE_OPTS.append(TYPE_SYSTEM_FLAG)
 
 
 ALL_FEATURE_OPTS = ['--all-features', '-all', '--mvp-features', '-mvp']
@@ -172,7 +182,7 @@ def randomize_fuzz_settings():
 def init_important_initial_contents():
     FIXED_IMPORTANT_INITIAL_CONTENTS = [
         # Perenially-important passes
-        os.path.join('lit', 'passes', 'optimize-instructions.wast'),
+        os.path.join('lit', 'passes', 'optimize-instructions-mvp.wast'),
         os.path.join('passes', 'optimize-instructions_fuzz-exec.wast'),
     ]
     MANUAL_RECENT_INITIAL_CONTENTS = [
@@ -257,6 +267,39 @@ def init_important_initial_contents():
     IMPORTANT_INITIAL_CONTENTS = [os.path.join(shared.get_test_dir('.'), t) for t in initial_contents]
 
 
+INITIAL_CONTENTS_IGNORE = [
+    # not all relaxed SIMD instructions are implemented in the interpreter
+    'relaxed-simd.wast',
+    # TODO: fuzzer and interpreter support for strings
+    'strings.wast',
+    'simplify-locals-strings.wast',
+    # TODO: fuzzer and interpreter support for extern conversions
+    'extern-conversions.wast',
+    # ignore DWARF because it is incompatible with multivalue atm
+    'zlib.wasm',
+    'cubescript.wasm',
+    'class_with_dwarf_noprint.wasm',
+    'fib2_dwarf.wasm',
+    'fib_nonzero-low-pc_dwarf.wasm',
+    'inlined_to_start_dwarf.wasm',
+    'fannkuch3_manyopts_dwarf.wasm',
+    'fib2_emptylocspan_dwarf.wasm',
+    'fannkuch3_dwarf.wasm',
+    'multi_unit_abbrev_noprint.wasm',
+    # TODO fuzzer support for multi-memories
+    'multi-memories-atomics64.wast',
+    'multi-memories-basics.wast',
+    'multi-memories-simd.wast',
+    'multi-memories-atomics64.wasm',
+    'multi-memories-basics.wasm',
+    'multi-memories-simd.wasm',
+    'multi-memories_size.wast',
+    # TODO: fuzzer support for internalize/externalize
+    'optimize-instructions-gc-extern.wast',
+    'gufa-extern.wast',
+]
+
+
 def pick_initial_contents():
     # if we use an initial wasm file's contents as the basis for the
     # fuzzing, then that filename, or None if we start entirely from scratch
@@ -279,6 +322,8 @@ def pick_initial_contents():
         # no longer exist, and we should just skip it.
         if not os.path.exists(test_name):
             return
+    if os.path.basename(test_name) in INITIAL_CONTENTS_IGNORE:
+        return
     assert os.path.exists(test_name)
     # tests that check validation errors are not helpful for us
     if '.fail.' in test_name:
@@ -308,7 +353,7 @@ def pick_initial_contents():
                 # there is no module in this choice (just asserts), ignore it
                 print('initial contents has no module')
                 return
-            test_name = 'initial.wat'
+            test_name = abspath('initial.wat')
             with open(test_name, 'w') as f:
                 f.write(module)
             print('  picked submodule %d from multi-module wast' % index)
@@ -317,17 +362,17 @@ def pick_initial_contents():
     FEATURE_OPTS += [
         # has not been fuzzed in general yet
         '--disable-memory64',
-        # avoid multivalue for now due to bad interactions with gc rtts in
-        # stacky code. for example, this fails to roundtrip as the tuple code
-        # ends up creating stacky binary code that needs to spill rtts to locals,
-        # which is not allowed:
+        # avoid multivalue for now due to bad interactions with gc non-nullable
+        # locals in stacky code. for example, this fails to roundtrip as the
+        # tuple code ends up creating stacky binary code that needs to spill
+        # non-nullable references to locals, which is not allowed:
         #
         # (module
         #  (type $other (struct))
-        #  (func $foo (result (rtt $other))
+        #  (func $foo (result (ref $other))
         #   (select
-        #    (rtt.canon $other)
-        #    (rtt.canon $other)
+        #    (struct.new $other)
+        #    (struct.new $other)
         #    (tuple.extract 1
         #     (tuple.make
         #      (i32.const 0)
@@ -338,9 +383,6 @@ def pick_initial_contents():
         #  )
         # )
         '--disable-multivalue',
-        # DWARF is incompatible with multivalue atm; it's more important to
-        # fuzz multivalue since we aren't actually fuzzing DWARF here
-        '--strip-dwarf',
     ]
 
     # the given wasm may not work with the chosen feature opts. for example, if
@@ -436,6 +478,9 @@ def numbers_are_close_enough(x, y):
         return False
 
 
+FUZZ_EXEC_NOTE_RESULT = '[fuzz-exec] note result'
+
+
 # compare between vms, which may slightly change how numbers are printed
 def compare_between_vms(x, y, context):
     x_lines = x.splitlines()
@@ -455,8 +500,7 @@ def compare_between_vms(x, y, context):
                 y_val = y_line[len(LEI_LOGGING) + 1:-1]
                 if numbers_are_close_enough(x_val, y_val):
                     continue
-            NOTE_RESULT = '[fuzz-exec] note result'
-            if x_line.startswith(NOTE_RESULT) and y_line.startswith(NOTE_RESULT):
+            if x_line.startswith(FUZZ_EXEC_NOTE_RESULT) and y_line.startswith(FUZZ_EXEC_NOTE_RESULT):
                 x_val = x_line.split(' ')[-1]
                 y_val = y_line.split(' ')[-1]
                 if numbers_are_close_enough(x_val, y_val):
@@ -480,8 +524,14 @@ def fix_output(out):
             x = str(float(x))
         return 'f64.const ' + x
     out = re.sub(r'f64\.const (-?[nanN:abcdefxIity\d+-.]+)', fix_double, out)
+
     # mark traps from wasm-opt as exceptions, even though they didn't run in a vm
     out = out.replace(TRAP_PREFIX, 'exception: ' + TRAP_PREFIX)
+
+    # funcref(0) has the index of the function in it, and optimizations can
+    # change that index, so ignore it
+    out = re.sub(r'funcref\([\d\w$+-_:]+\)', 'funcref()', out)
+
     lines = out.splitlines()
     for i in range(len(lines)):
         line = lines[i]
@@ -740,7 +790,7 @@ class CompareVMs(TestCaseHandler):
                 # large and it isn't what we are focused on testing here
                 with no_pass_debug():
                     run(compile_cmd)
-                return run_d8_js('a.out.js')
+                return run_d8_js(abspath('a.out.js'))
 
             def can_run(self, wasm):
                 # quite slow (more steps), so run it less frequently
@@ -759,8 +809,10 @@ class CompareVMs(TestCaseHandler):
                     D8(),
                     D8Liftoff(),
                     D8TurboFan(),
-                    Wasm2C(),
-                    Wasm2C2Wasm()]
+                    # FIXME: Temprorary disable. See issue #4741 for more details
+                    # Wasm2C(),
+                    # Wasm2C2Wasm()
+                    ]
 
     def handle_pair(self, input, before_wasm, after_wasm, opts):
         before = self.run_vms(before_wasm)
@@ -794,7 +846,7 @@ class CompareVMs(TestCaseHandler):
                 compare(before[vm], after[vm], 'CompareVMs between before and after: ' + vm.name)
 
     def can_run_on_feature_opts(self, feature_opts):
-        return all_disallowed(['simd', 'multivalue'])
+        return all_disallowed(['simd', 'multivalue', 'multi-memories'])
 
 
 # Check for determinism - the same command must have the same output.
@@ -804,15 +856,15 @@ class CheckDeterminism(TestCaseHandler):
 
     def handle_pair(self, input, before_wasm, after_wasm, opts):
         # check for determinism
-        run([in_bin('wasm-opt'), before_wasm, '-o', 'b1.wasm'] + opts)
-        run([in_bin('wasm-opt'), before_wasm, '-o', 'b2.wasm'] + opts)
+        run([in_bin('wasm-opt'), before_wasm, '-o', abspath('b1.wasm')] + opts)
+        run([in_bin('wasm-opt'), before_wasm, '-o', abspath('b2.wasm')] + opts)
         b1 = open('b1.wasm', 'rb').read()
         b2 = open('b2.wasm', 'rb').read()
         if (b1 != b2):
-            run([in_bin('wasm-dis'), 'b1.wasm', '-o', 'b1.wat', TYPE_SYSTEM_FLAG])
-            run([in_bin('wasm-dis'), 'b2.wasm', '-o', 'b2.wat', TYPE_SYSTEM_FLAG])
-            t1 = open('b1.wat', 'r').read()
-            t2 = open('b2.wat', 'r').read()
+            run([in_bin('wasm-dis'), abspath('b1.wasm'), '-o', abspath('b1.wat')] + FEATURE_OPTS)
+            run([in_bin('wasm-dis'), abspath('b2.wasm'), '-o', abspath('b2.wat')] + FEATURE_OPTS)
+            t1 = open(abspath('b1.wat'), 'r').read()
+            t2 = open(abspath('b2.wat'), 'r').read()
             compare(t1, t2, 'Output must be deterministic.', verbose=False)
 
 
@@ -929,7 +981,7 @@ class Wasm2JS(TestCaseHandler):
             f.write(glue)
             f.write(main)
             f.write(wrapper)
-        return run_vm([shared.NODEJS, js_file, 'a.wasm'])
+        return run_vm([shared.NODEJS, js_file, abspath('a.wasm')])
 
     def can_run_on_feature_opts(self, feature_opts):
         # TODO: properly handle memory growth. right now the wasm2js handler
@@ -939,7 +991,7 @@ class Wasm2JS(TestCaseHandler):
         # specifically for growth here
         if INITIAL_CONTENTS:
             return False
-        return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc'])
+        return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multi-memories'])
 
 
 class Asyncify(TestCaseHandler):
@@ -947,10 +999,12 @@ class Asyncify(TestCaseHandler):
 
     def handle_pair(self, input, before_wasm, after_wasm, opts):
         # we must legalize in order to run in JS
-        run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', 'async.' + before_wasm] + FEATURE_OPTS)
-        run([in_bin('wasm-opt'), after_wasm, '--legalize-js-interface', '-o', 'async.' + after_wasm] + FEATURE_OPTS)
-        before_wasm = 'async.' + before_wasm
-        after_wasm = 'async.' + after_wasm
+        async_before_wasm = abspath('async.' + os.path.basename(before_wasm))
+        async_after_wasm = abspath('async.' + os.path.basename(after_wasm))
+        run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', async_before_wasm] + FEATURE_OPTS)
+        run([in_bin('wasm-opt'), after_wasm, '--legalize-js-interface', '-o', async_after_wasm] + FEATURE_OPTS)
+        before_wasm = async_before_wasm
+        after_wasm = async_after_wasm
         before = fix_output(run_d8_wasm(before_wasm))
         after = fix_output(run_d8_wasm(after_wasm))
 
@@ -963,7 +1017,7 @@ class Asyncify(TestCaseHandler):
             return
 
         def do_asyncify(wasm):
-            cmd = [in_bin('wasm-opt'), wasm, '--asyncify', '-o', 'async.t.wasm']
+            cmd = [in_bin('wasm-opt'), wasm, '--asyncify', '-o', abspath('async.t.wasm')]
             # if we allow NaNs, running binaryen optimizations and then
             # executing in d8 may lead to different results due to NaN
             # nondeterminism between VMs.
@@ -974,7 +1028,7 @@ class Asyncify(TestCaseHandler):
                     cmd += ['--shrink-level=%d' % random.randint(1, 2)]
             cmd += FEATURE_OPTS
             run(cmd)
-            out = run_d8_wasm('async.t.wasm')
+            out = run_d8_wasm(abspath('async.t.wasm'))
             # ignore the output from the new asyncify API calls - the ones with asserts will trap, too
             for ignore in ['[fuzz-exec] calling asyncify_start_unwind\nexception!\n',
                            '[fuzz-exec] calling asyncify_start_unwind\n',
@@ -993,7 +1047,82 @@ class Asyncify(TestCaseHandler):
         compare(before, after_asyncify, 'Asyncify (before/after_asyncify)')
 
     def can_run_on_feature_opts(self, feature_opts):
-        return all_disallowed(['exception-handling', 'simd', 'tail-call', 'reference-types', 'multivalue', 'gc'])
+        return all_disallowed(['exception-handling', 'simd', 'tail-call', 'reference-types', 'multivalue', 'gc', 'multi-memories'])
+
+
+# Fuzz the interpreter with --fuzz-exec -tnh. The tricky thing with traps-never-
+# happen mode is that if a trap *does* happen then that is undefined behavior,
+# and the optimizer was free to make changes to observable behavior there. The
+# fuzzer therefore needs to ignore code that traps.
+class TrapsNeverHappen(TestCaseHandler):
+    frequency = 1
+
+    def handle_pair(self, input, before_wasm, after_wasm, opts):
+        before = run_bynterp(before_wasm, ['--fuzz-exec-before'])
+        after_wasm_tnh = after_wasm + '.tnh.wasm'
+        run([in_bin('wasm-opt'), before_wasm, '-o', after_wasm_tnh, '-tnh'] + opts + FEATURE_OPTS)
+        after = run_bynterp(after_wasm_tnh, ['--fuzz-exec-before'])
+
+        # if a trap happened, we must stop comparing from that.
+        if TRAP_PREFIX in before:
+            trap_index = before.index(TRAP_PREFIX)
+            # we can't test this function, which the trap is in the middle of
+            # (tnh could move the trap around, so even things before the trap
+            # are unsafe). erase everything from this function's output and
+            # onward, so we only compare the previous trap-free code. first,
+            # find the function call during which the trap happened, by finding
+            # the call line right before us. that is, the output looks like
+            # this:
+            #
+            #   [fuzz-exec] calling foo
+            #   .. stuff happening during foo ..
+            #   [fuzz-exec] calling bar
+            #   .. stuff happening during bar ..
+            #
+            # if the trap happened during bar, the relevant call line is
+            # "[fuzz-exec] calling bar".
+            call_start = before.rfind(FUZZ_EXEC_CALL_PREFIX, 0, trap_index)
+            if call_start < 0:
+                # the trap happened before we called an export, so it occured
+                # during startup (the start function, or memory segment
+                # operations, etc.). in that case there is nothing for us to
+                # compare here; just leave.
+                return
+            # include the line separator in the index, as function names may
+            # be prefixes of each other
+            call_end = before.index(os.linesep, call_start) + 1
+            # we now know the contents of the call line after which the trap
+            # happens, which is something like "[fuzz-exec] calling bar", and
+            # it is unique since it contains the function being called.
+            call_line = before[call_start:call_end]
+            # remove everything from that call line onward.
+            lines_pre = before.count(os.linesep)
+            before = before[:call_start]
+            lines_post = before.count(os.linesep)
+            print(f'ignoring code due to trap (from "{call_line}"), lines to compare goes {lines_pre} => {lines_post} ')
+
+            # also remove the relevant lines from after.
+            after_index = after.index(call_line)
+            after = after[:after_index]
+
+        # some results cannot be compared, so we must filter them out here.
+        def ignore_references(out):
+            ret = []
+            for line in out.splitlines():
+                # only result lines are relevant here, which look like
+                # [fuzz-exec] note result: foo => [...]
+                if FUZZ_EXEC_NOTE_RESULT in line:
+                    # we want to filter out things like "anyref(null)" or
+                    # "[ref null data]".
+                    if 'ref(' in line or 'ref ' in line:
+                        line = line[:line.index('=>') + 2] + ' ?'
+                ret.append(line)
+            return '\n'.join(ret)
+
+        before = fix_output(ignore_references(before))
+        after = fix_output(ignore_references(after))
+
+        compare_between_vms(before, after, 'TrapsNeverHappen')
 
 
 # Check that the text format round-trips without error.
@@ -1005,8 +1134,8 @@ class RoundtripText(TestCaseHandler):
         # names which are very long, causing names to collide and the wast to be
         # invalid
         # FIXME: run name-types by default during load?
-        run([in_bin('wasm-opt'), wasm, '--name-types', '-S', '-o', 'a.wast'] + FEATURE_OPTS)
-        run([in_bin('wasm-opt'), 'a.wast'] + FEATURE_OPTS)
+        run([in_bin('wasm-opt'), wasm, '--name-types', '-S', '-o', abspath('a.wast')] + FEATURE_OPTS)
+        run([in_bin('wasm-opt'), abspath('a.wast')] + FEATURE_OPTS)
 
 
 # The global list of all test case handlers
@@ -1016,6 +1145,7 @@ testcase_handlers = [
     CheckDeterminism(),
     Wasm2JS(),
     Asyncify(),
+    TrapsNeverHappen(),
     # FIXME: Re-enable after https://github.com/WebAssembly/binaryen/issues/3989
     # RoundtripText()
 ]
@@ -1040,7 +1170,7 @@ def test_one(random_input, given_wasm):
     pick_initial_contents()
 
     opts = randomize_opt_flags()
-    print('randomized opts:', ' '.join(opts))
+    print('randomized opts:', '\n  ' + '\n  '.join(opts))
     print()
 
     if given_wasm:
@@ -1048,11 +1178,15 @@ def test_one(random_input, given_wasm):
         # apply properties like not having any NaNs, which the original fuzz
         # wasm had applied. that is, we need to preserve properties like not
         # having nans through reduction.
-        run([in_bin('wasm-opt'), given_wasm, '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS)
+        try:
+            run([in_bin('wasm-opt'), given_wasm, '-o', abspath('a.wasm')] + FUZZ_OPTS + FEATURE_OPTS)
+        except Exception as e:
+            print("Internal error in fuzzer! Could not run given wasm")
+            raise e
     else:
         # emit the target features section so that reduction can work later,
         # without needing to specify the features
-        generate_command = [in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS
+        generate_command = [in_bin('wasm-opt'), random_input, '-ttf', '-o', abspath('a.wasm')] + FUZZ_OPTS + FEATURE_OPTS
         if INITIAL_CONTENTS:
             generate_command += ['--initial-fuzz=' + INITIAL_CONTENTS]
         if PRINT_WATS:
@@ -1067,7 +1201,7 @@ def test_one(random_input, given_wasm):
     update_feature_opts('a.wasm')
 
     # create a second wasm for handlers that want to look at pairs.
-    generate_command = [in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS + FEATURE_OPTS
+    generate_command = [in_bin('wasm-opt'), abspath('a.wasm'), '-o', abspath('b.wasm')] + opts + FUZZ_OPTS + FEATURE_OPTS
     if PRINT_WATS:
         printed = run(generate_command + ['--print'])
         with open('b.printed.wast', 'w') as f:
@@ -1103,7 +1237,7 @@ def test_one(random_input, given_wasm):
 
         # let the testcase handler handle this testcase however it wants. in this case we give it
         # the input and both wasms.
-        testcase_handler.handle_pair(input=random_input, before_wasm='a.wasm', after_wasm='b.wasm', opts=opts + FEATURE_OPTS)
+        testcase_handler.handle_pair(input=random_input, before_wasm=abspath('a.wasm'), after_wasm=abspath('b.wasm'), opts=opts + FEATURE_OPTS)
         print('')
 
     return bytes
@@ -1134,6 +1268,7 @@ opt_choices = [
     ["--dae-optimizing"],
     ["--dce"],
     ["--directize"],
+    ["--discard-global-effects"],
     ["--flatten", "--dfo"],
     ["--duplicate-function-elimination"],
     ["--flatten"],
@@ -1141,8 +1276,15 @@ opt_choices = [
     ["--inlining"],
     ["--inlining-optimizing"],
     ["--flatten", "--simplify-locals-notee-nostructure", "--local-cse"],
+    # note that no pass we run here should add effects to a function, so it is
+    # ok to run this pass and let the passes after it use the effects to
+    # optimize
+    ["--generate-global-effects"],
     ["--global-refining"],
+    ["--gsi"],
     ["--gto"],
+    ["--gufa"],
+    ["--gufa-optimizing"],
     ["--local-cse"],
     ["--heap2local"],
     ["--remove-unused-names", "--heap2local"],
@@ -1152,7 +1294,10 @@ opt_choices = [
     ["--memory-packing"],
     ["--merge-blocks"],
     ['--merge-locals'],
+    ['--monomorphize'],
+    ['--monomorphize-always'],
     ['--once-reduction'],
+    ["--optimize-casts"],
     ["--optimize-instructions"],
     ["--optimize-stack-ir"],
     ["--generate-stack-ir", "--optimize-stack-ir"],
@@ -1194,9 +1339,10 @@ def randomize_opt_flags():
                 continue
             if '--enable-multivalue' in FEATURE_OPTS and '--enable-reference-types' in FEATURE_OPTS:
                 print('avoiding --flatten due to multivalue + reference types not supporting it (spilling of non-nullable tuples)')
+                print('TODO: Resolving https://github.com/WebAssembly/binaryen/issues/4824 may fix this')
                 continue
             if '--gc' not in FEATURE_OPTS:
-                print('avoiding --flatten due to GC not supporting it (spilling of RTTs)')
+                print('avoiding --flatten due to GC not supporting it (spilling of non-nullable locals)')
                 continue
             if INITIAL_CONTENTS and os.path.getsize(INITIAL_CONTENTS) > 2000:
                 print('avoiding --flatten due using a large amount of initial contents, which may blow up')
@@ -1247,9 +1393,17 @@ print('POSSIBLE_FEATURE_OPTS:', POSSIBLE_FEATURE_OPTS)
 # some features depend on other features, so if a required feature is
 # disabled, its dependent features need to be disabled as well.
 IMPLIED_FEATURE_OPTS = {
-    '--disable-reference-types': ['--disable-gc']
+    '--disable-reference-types': ['--disable-gc'],
 }
 
+print('''
+<<< fuzz_opt.py >>>
+''')
+
+if not shared.V8:
+    print('The v8 shell, d8, must be in the path')
+    sys.exit(1)
+
 if __name__ == '__main__':
     # if we are given a seed, run exactly that one testcase. otherwise,
     # run new ones until we fail
@@ -1270,7 +1424,7 @@ if __name__ == '__main__':
     init_important_initial_contents()
 
     seed = time.time() * os.getpid()
-    raw_input_data = 'input.dat'
+    raw_input_data = abspath('input.dat')
     counter = 0
     total_wasm_size = 0
     total_input_size = 0
@@ -1355,7 +1509,7 @@ on valid wasm files.)
                 # testcase (to make reduction simple, save "original.wasm" on
                 # the side, so that we can autoreduce using the name "a.wasm"
                 # which we use internally)
-                original_wasm = os.path.abspath('original.wasm')
+                original_wasm = abspath('original.wasm')
                 shutil.copyfile('a.wasm', original_wasm)
                 # write out a useful reduce.sh
                 auto_init = ''
@@ -1364,10 +1518,8 @@ on valid wasm files.)
                 with open('reduce.sh', 'w') as reduce_sh:
                     reduce_sh.write('''\
 # check the input is even a valid wasm file
-echo "At least one of the next two values should be 0:"
-%(wasm_opt)s %(typesystem)s --detect-features %(temp_wasm)s
-echo "  " $?
-%(wasm_opt)s %(typesystem)s --all-features %(temp_wasm)s
+echo "The following value should be 0:"
+%(wasm_opt)s %(features)s %(temp_wasm)s
 echo "  " $?
 
 # run the command
@@ -1403,9 +1555,9 @@ echo "  " $?
                          'seed': seed,
                          'auto_init': auto_init,
                          'original_wasm': original_wasm,
-                         'temp_wasm': os.path.abspath('t.wasm'),
-                         'typesystem': TYPE_SYSTEM_FLAG,
-                         'reduce_sh': os.path.abspath('reduce.sh')})
+                         'temp_wasm': abspath('t.wasm'),
+                         'features': ' '.join(FEATURE_OPTS),
+                         'reduce_sh': abspath('reduce.sh')})
 
                 print('''\
 ================================================================================
@@ -1426,7 +1578,7 @@ You can reduce the testcase by running this now:
 vvvv
 
 
-%(wasm_reduce)s %(type_system_flag)s %(original_wasm)s '--command=bash %(reduce_sh)s' -t %(temp_wasm)s -w %(working_wasm)s
+%(wasm_reduce)s %(features)s %(original_wasm)s '--command=bash %(reduce_sh)s' -t %(temp_wasm)s -w %(working_wasm)s
 
 
 ^^^^
@@ -1434,9 +1586,8 @@ vvvv
 
 Make sure to verify by eye that the output says something like this:
 
-At least one of the next two values should be 0:
+The following value should be 0:
   0
-  1
 The following value should be 1:
   1
 
@@ -1452,11 +1603,11 @@ After reduction, the reduced file will be in %(working_wasm)s
 ================================================================================
                 ''' % {'seed': seed,
                        'original_wasm': original_wasm,
-                       'temp_wasm': os.path.abspath('t.wasm'),
-                       'working_wasm': os.path.abspath('w.wasm'),
+                       'temp_wasm': abspath('t.wasm'),
+                       'working_wasm': abspath('w.wasm'),
                        'wasm_reduce': in_bin('wasm-reduce'),
-                       'reduce_sh': os.path.abspath('reduce.sh'),
-                       'type_system_flag': TYPE_SYSTEM_FLAG})
+                       'reduce_sh': abspath('reduce.sh'),
+                       'features': ' '.join(FEATURE_OPTS)})
                 break
         if given_seed is not None:
             break
diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js
index d55007b..0f413c2 100644
--- a/scripts/fuzz_shell.js
+++ b/scripts/fuzz_shell.js
@@ -175,7 +175,15 @@ var imports = {
 imports = Asyncify.instrumentImports(imports);
 
 // Create the wasm.
-var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), imports);
+var module = new WebAssembly.Module(binary);
+
+var instance;
+try {
+  instance = new WebAssembly.Instance(module, imports);
+} catch (e) {
+  console.log('exception: failed to instantiate module');
+  quit();
+}
 
 // Handle the exports.
 var exports = instance.exports;
@@ -216,4 +224,3 @@ sortedExports.forEach(function(e) {
 
 // Finish up
 Asyncify.finish();
-
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 5576960..80a62a1 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -43,29 +43,29 @@ instructions = [
     ("data.drop",      "makeDataDrop(s)"),
     ("memory.copy",    "makeMemoryCopy(s)"),
     ("memory.fill",    "makeMemoryFill(s)"),
-    ("i32.load",       "makeLoad(s, Type::i32, /*isAtomic=*/false)"),
-    ("i64.load",       "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("f32.load",       "makeLoad(s, Type::f32, /*isAtomic=*/false)"),
-    ("f64.load",       "makeLoad(s, Type::f64, /*isAtomic=*/false)"),
-    ("i32.load8_s",    "makeLoad(s, Type::i32, /*isAtomic=*/false)"),
-    ("i32.load8_u",    "makeLoad(s, Type::i32, /*isAtomic=*/false)"),
-    ("i32.load16_s",   "makeLoad(s, Type::i32, /*isAtomic=*/false)"),
-    ("i32.load16_u",   "makeLoad(s, Type::i32, /*isAtomic=*/false)"),
-    ("i64.load8_s",    "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.load8_u",    "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.load16_s",   "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.load16_u",   "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.load32_s",   "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.load32_u",   "makeLoad(s, Type::i64, /*isAtomic=*/false)"),
-    ("i32.store",      "makeStore(s, Type::i32, /*isAtomic=*/false)"),
-    ("i64.store",      "makeStore(s, Type::i64, /*isAtomic=*/false)"),
-    ("f32.store",      "makeStore(s, Type::f32, /*isAtomic=*/false)"),
-    ("f64.store",      "makeStore(s, Type::f64, /*isAtomic=*/false)"),
-    ("i32.store8",     "makeStore(s, Type::i32, /*isAtomic=*/false)"),
-    ("i32.store16",    "makeStore(s, Type::i32, /*isAtomic=*/false)"),
-    ("i64.store8",     "makeStore(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.store16",    "makeStore(s, Type::i64, /*isAtomic=*/false)"),
-    ("i64.store32",    "makeStore(s, Type::i64, /*isAtomic=*/false)"),
+    ("i32.load",       "makeLoad(s, Type::i32, /*signed=*/false, 4, /*isAtomic=*/false)"),
+    ("i64.load",       "makeLoad(s, Type::i64, /*signed=*/false, 8, /*isAtomic=*/false)"),
+    ("f32.load",       "makeLoad(s, Type::f32, /*signed=*/false, 4, /*isAtomic=*/false)"),
+    ("f64.load",       "makeLoad(s, Type::f64, /*signed=*/false, 8, /*isAtomic=*/false)"),
+    ("i32.load8_s",    "makeLoad(s, Type::i32, /*signed=*/true, 1, /*isAtomic=*/false)"),
+    ("i32.load8_u",    "makeLoad(s, Type::i32, /*signed=*/false, 1, /*isAtomic=*/false)"),
+    ("i32.load16_s",   "makeLoad(s, Type::i32, /*signed=*/true, 2, /*isAtomic=*/false)"),
+    ("i32.load16_u",   "makeLoad(s, Type::i32, /*signed=*/false, 2, /*isAtomic=*/false)"),
+    ("i64.load8_s",    "makeLoad(s, Type::i64, /*signed=*/true, 1, /*isAtomic=*/false)"),
+    ("i64.load8_u",    "makeLoad(s, Type::i64, /*signed=*/false, 1, /*isAtomic=*/false)"),
+    ("i64.load16_s",   "makeLoad(s, Type::i64, /*signed=*/true, 2, /*isAtomic=*/false)"),
+    ("i64.load16_u",   "makeLoad(s, Type::i64, /*signed=*/false, 2, /*isAtomic=*/false)"),
+    ("i64.load32_s",   "makeLoad(s, Type::i64, /*signed=*/true, 4, /*isAtomic=*/false)"),
+    ("i64.load32_u",   "makeLoad(s, Type::i64, /*signed=*/false, 4, /*isAtomic=*/false)"),
+    ("i32.store",      "makeStore(s, Type::i32, 4, /*isAtomic=*/false)"),
+    ("i64.store",      "makeStore(s, Type::i64, 8, /*isAtomic=*/false)"),
+    ("f32.store",      "makeStore(s, Type::f32, 4, /*isAtomic=*/false)"),
+    ("f64.store",      "makeStore(s, Type::f64, 8, /*isAtomic=*/false)"),
+    ("i32.store8",     "makeStore(s, Type::i32, 1, /*isAtomic=*/false)"),
+    ("i32.store16",    "makeStore(s, Type::i32, 2, /*isAtomic=*/false)"),
+    ("i64.store8",     "makeStore(s, Type::i64, 1, /*isAtomic=*/false)"),
+    ("i64.store16",    "makeStore(s, Type::i64, 2, /*isAtomic=*/false)"),
+    ("i64.store32",    "makeStore(s, Type::i64, 4, /*isAtomic=*/false)"),
     ("memory.size",    "makeMemorySize(s)"),
     ("memory.grow",    "makeMemoryGrow(s)"),
     ("i32.const",      "makeConst(s, Type::i32)"),
@@ -205,69 +205,69 @@ instructions = [
     ("memory.atomic.wait32",    "makeAtomicWait(s, Type::i32)"),
     ("memory.atomic.wait64",    "makeAtomicWait(s, Type::i64)"),
     ("atomic.fence",            "makeAtomicFence(s)"),
-    ("i32.atomic.load8_u",      "makeLoad(s, Type::i32, /*isAtomic=*/true)"),
-    ("i32.atomic.load16_u",     "makeLoad(s, Type::i32, /*isAtomic=*/true)"),
-    ("i32.atomic.load",         "makeLoad(s, Type::i32, /*isAtomic=*/true)"),
-    ("i64.atomic.load8_u",      "makeLoad(s, Type::i64, /*isAtomic=*/true)"),
-    ("i64.atomic.load16_u",     "makeLoad(s, Type::i64, /*isAtomic=*/true)"),
-    ("i64.atomic.load32_u",     "makeLoad(s, Type::i64, /*isAtomic=*/true)"),
-    ("i64.atomic.load",         "makeLoad(s, Type::i64, /*isAtomic=*/true)"),
-    ("i32.atomic.store8",       "makeStore(s, Type::i32, /*isAtomic=*/true)"),
-    ("i32.atomic.store16",      "makeStore(s, Type::i32, /*isAtomic=*/true)"),
-    ("i32.atomic.store",        "makeStore(s, Type::i32, /*isAtomic=*/true)"),
-    ("i64.atomic.store8",       "makeStore(s, Type::i64, /*isAtomic=*/true)"),
-    ("i64.atomic.store16",      "makeStore(s, Type::i64, /*isAtomic=*/true)"),
-    ("i64.atomic.store32",      "makeStore(s, Type::i64, /*isAtomic=*/true)"),
-    ("i64.atomic.store",        "makeStore(s, Type::i64, /*isAtomic=*/true)"),
-    ("i32.atomic.rmw8.add_u",   "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.add_u",  "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.add",      "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.add_u",   "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.add_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.add_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.add",      "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i32.atomic.rmw8.sub_u",   "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.sub_u",  "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.sub",      "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.sub_u",   "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.sub_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.sub_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.sub",      "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i32.atomic.rmw8.and_u",   "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.and_u",  "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.and",      "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.and_u",   "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.and_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.and_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.and",      "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i32.atomic.rmw8.or_u",    "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.or_u",   "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.or",       "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.or_u",    "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.or_u",   "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.or_u",   "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.or",       "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i32.atomic.rmw8.xor_u",   "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.xor_u",  "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.xor",      "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.xor_u",   "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.xor_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.xor_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.xor",      "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i32.atomic.rmw8.xchg_u",  "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.xchg_u", "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.xchg",     "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.xchg_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.xchg_u", "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.xchg_u", "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.xchg",     "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i32.atomic.rmw8.cmpxchg_u",  "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw16.cmpxchg_u", "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i32.atomic.rmw.cmpxchg",     "makeAtomicRMWOrCmpxchg(s, Type::i32)"),
-    ("i64.atomic.rmw8.cmpxchg_u",  "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw16.cmpxchg_u", "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw32.cmpxchg_u", "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
-    ("i64.atomic.rmw.cmpxchg",     "makeAtomicRMWOrCmpxchg(s, Type::i64)"),
+    ("i32.atomic.load8_u",      "makeLoad(s, Type::i32, /*signed=*/false, 1, /*isAtomic=*/true)"),
+    ("i32.atomic.load16_u",     "makeLoad(s, Type::i32, /*signed=*/false, 2, /*isAtomic=*/true)"),
+    ("i32.atomic.load",         "makeLoad(s, Type::i32, /*signed=*/false, 4, /*isAtomic=*/true)"),
+    ("i64.atomic.load8_u",      "makeLoad(s, Type::i64, /*signed=*/false, 1, /*isAtomic=*/true)"),
+    ("i64.atomic.load16_u",     "makeLoad(s, Type::i64, /*signed=*/false, 2, /*isAtomic=*/true)"),
+    ("i64.atomic.load32_u",     "makeLoad(s, Type::i64, /*signed=*/false, 4, /*isAtomic=*/true)"),
+    ("i64.atomic.load",         "makeLoad(s, Type::i64, /*signed=*/false, 8, /*isAtomic=*/true)"),
+    ("i32.atomic.store8",       "makeStore(s, Type::i32, 1, /*isAtomic=*/true)"),
+    ("i32.atomic.store16",      "makeStore(s, Type::i32, 2, /*isAtomic=*/true)"),
+    ("i32.atomic.store",        "makeStore(s, Type::i32, 4, /*isAtomic=*/true)"),
+    ("i64.atomic.store8",       "makeStore(s, Type::i64, 1, /*isAtomic=*/true)"),
+    ("i64.atomic.store16",      "makeStore(s, Type::i64, 2, /*isAtomic=*/true)"),
+    ("i64.atomic.store32",      "makeStore(s, Type::i64, 4, /*isAtomic=*/true)"),
+    ("i64.atomic.store",        "makeStore(s, Type::i64, 8, /*isAtomic=*/true)"),
+    ("i32.atomic.rmw8.add_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i32, 1)"),
+    ("i32.atomic.rmw16.add_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i32, 2)"),
+    ("i32.atomic.rmw.add",      "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i32, 4)"),
+    ("i64.atomic.rmw8.add_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 1)"),
+    ("i64.atomic.rmw16.add_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 2)"),
+    ("i64.atomic.rmw32.add_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 4)"),
+    ("i64.atomic.rmw.add",      "makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 8)"),
+    ("i32.atomic.rmw8.sub_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i32, 1)"),
+    ("i32.atomic.rmw16.sub_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i32, 2)"),
+    ("i32.atomic.rmw.sub",      "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i32, 4)"),
+    ("i64.atomic.rmw8.sub_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 1)"),
+    ("i64.atomic.rmw16.sub_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 2)"),
+    ("i64.atomic.rmw32.sub_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 4)"),
+    ("i64.atomic.rmw.sub",      "makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 8)"),
+    ("i32.atomic.rmw8.and_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i32, 1)"),
+    ("i32.atomic.rmw16.and_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i32, 2)"),
+    ("i32.atomic.rmw.and",      "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i32, 4)"),
+    ("i64.atomic.rmw8.and_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 1)"),
+    ("i64.atomic.rmw16.and_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 2)"),
+    ("i64.atomic.rmw32.and_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 4)"),
+    ("i64.atomic.rmw.and",      "makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 8)"),
+    ("i32.atomic.rmw8.or_u",    "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i32, 1)"),
+    ("i32.atomic.rmw16.or_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i32, 2)"),
+    ("i32.atomic.rmw.or",       "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i32, 4)"),
+    ("i64.atomic.rmw8.or_u",    "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 1)"),
+    ("i64.atomic.rmw16.or_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 2)"),
+    ("i64.atomic.rmw32.or_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 4)"),
+    ("i64.atomic.rmw.or",       "makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 8)"),
+    ("i32.atomic.rmw8.xor_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i32, 1)"),
+    ("i32.atomic.rmw16.xor_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i32, 2)"),
+    ("i32.atomic.rmw.xor",      "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i32, 4)"),
+    ("i64.atomic.rmw8.xor_u",   "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 1)"),
+    ("i64.atomic.rmw16.xor_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 2)"),
+    ("i64.atomic.rmw32.xor_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 4)"),
+    ("i64.atomic.rmw.xor",      "makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 8)"),
+    ("i32.atomic.rmw8.xchg_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i32, 1)"),
+    ("i32.atomic.rmw16.xchg_u", "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i32, 2)"),
+    ("i32.atomic.rmw.xchg",     "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i32, 4)"),
+    ("i64.atomic.rmw8.xchg_u",  "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 1)"),
+    ("i64.atomic.rmw16.xchg_u", "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 2)"),
+    ("i64.atomic.rmw32.xchg_u", "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 4)"),
+    ("i64.atomic.rmw.xchg",     "makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 8)"),
+    ("i32.atomic.rmw8.cmpxchg_u",  "makeAtomicCmpxchg(s, Type::i32, 1)"),
+    ("i32.atomic.rmw16.cmpxchg_u", "makeAtomicCmpxchg(s, Type::i32, 2)"),
+    ("i32.atomic.rmw.cmpxchg",     "makeAtomicCmpxchg(s, Type::i32, 4)"),
+    ("i64.atomic.rmw8.cmpxchg_u",  "makeAtomicCmpxchg(s, Type::i64, 1)"),
+    ("i64.atomic.rmw16.cmpxchg_u", "makeAtomicCmpxchg(s, Type::i64, 2)"),
+    ("i64.atomic.rmw32.cmpxchg_u", "makeAtomicCmpxchg(s, Type::i64, 4)"),
+    ("i64.atomic.rmw.cmpxchg",     "makeAtomicCmpxchg(s, Type::i64, 8)"),
     # nontrapping float-to-int instructions
     ("i32.trunc_sat_f32_s", "makeUnary(s, UnaryOp::TruncSatSFloat32ToInt32)"),
     ("i32.trunc_sat_f32_u", "makeUnary(s, UnaryOp::TruncSatUFloat32ToInt32)"),
@@ -278,8 +278,8 @@ instructions = [
     ("i64.trunc_sat_f64_s", "makeUnary(s, UnaryOp::TruncSatSFloat64ToInt64)"),
     ("i64.trunc_sat_f64_u", "makeUnary(s, UnaryOp::TruncSatUFloat64ToInt64)"),
     # SIMD ops
-    ("v128.load",            "makeLoad(s, Type::v128, /*isAtomic=*/false)"),
-    ("v128.store",           "makeStore(s, Type::v128, /*isAtomic=*/false)"),
+    ("v128.load",            "makeLoad(s, Type::v128, /*signed=*/false, 16, /*isAtomic=*/false)"),
+    ("v128.store",           "makeStore(s, Type::v128, 16, /*isAtomic=*/false)"),
     ("v128.const",           "makeConst(s, Type::v128)"),
     ("i8x16.shuffle",        "makeSIMDShuffle(s)"),
     ("i8x16.splat",          "makeUnary(s, UnaryOp::SplatVecI8x16)"),
@@ -357,14 +357,14 @@ instructions = [
     ("v128.andnot",          "makeBinary(s, BinaryOp::AndNotVec128)"),
     ("v128.any_true",        "makeUnary(s, UnaryOp::AnyTrueVec128)"),
     ("v128.bitselect",       "makeSIMDTernary(s, SIMDTernaryOp::Bitselect)"),
-    ("v128.load8_lane",      "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load8LaneVec128)"),
-    ("v128.load16_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load16LaneVec128)"),
-    ("v128.load32_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load32LaneVec128)"),
-    ("v128.load64_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load64LaneVec128)"),
-    ("v128.store8_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store8LaneVec128)"),
-    ("v128.store16_lane",    "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store16LaneVec128)"),
-    ("v128.store32_lane",    "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store32LaneVec128)"),
-    ("v128.store64_lane",    "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store64LaneVec128)"),
+    ("v128.load8_lane",      "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load8LaneVec128, 1)"),
+    ("v128.load16_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load16LaneVec128, 2)"),
+    ("v128.load32_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load32LaneVec128, 4)"),
+    ("v128.load64_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load64LaneVec128, 8)"),
+    ("v128.store8_lane",     "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store8LaneVec128, 1)"),
+    ("v128.store16_lane",    "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store16LaneVec128, 2)"),
+    ("v128.store32_lane",    "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store32LaneVec128, 4)"),
+    ("v128.store64_lane",    "makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store64LaneVec128, 8)"),
     ("i8x16.popcnt",         "makeUnary(s, UnaryOp::PopcntVecI8x16)"),
     ("i8x16.abs",            "makeUnary(s, UnaryOp::AbsVecI8x16)"),
     ("i8x16.neg",            "makeUnary(s, UnaryOp::NegVecI8x16)"),
@@ -475,18 +475,18 @@ instructions = [
     ("i32x4.trunc_sat_f32x4_u",  "makeUnary(s, UnaryOp::TruncSatUVecF32x4ToVecI32x4)"),
     ("f32x4.convert_i32x4_s",    "makeUnary(s, UnaryOp::ConvertSVecI32x4ToVecF32x4)"),
     ("f32x4.convert_i32x4_u",    "makeUnary(s, UnaryOp::ConvertUVecI32x4ToVecF32x4)"),
-    ("v128.load8_splat",         "makeSIMDLoad(s, SIMDLoadOp::Load8SplatVec128)"),
-    ("v128.load16_splat",        "makeSIMDLoad(s, SIMDLoadOp::Load16SplatVec128)"),
-    ("v128.load32_splat",        "makeSIMDLoad(s, SIMDLoadOp::Load32SplatVec128)"),
-    ("v128.load64_splat",        "makeSIMDLoad(s, SIMDLoadOp::Load64SplatVec128)"),
-    ("v128.load8x8_s",           "makeSIMDLoad(s, SIMDLoadOp::Load8x8SVec128)"),
-    ("v128.load8x8_u",           "makeSIMDLoad(s, SIMDLoadOp::Load8x8UVec128)"),
-    ("v128.load16x4_s",          "makeSIMDLoad(s, SIMDLoadOp::Load16x4SVec128)"),
-    ("v128.load16x4_u",          "makeSIMDLoad(s, SIMDLoadOp::Load16x4UVec128)"),
-    ("v128.load32x2_s",          "makeSIMDLoad(s, SIMDLoadOp::Load32x2SVec128)"),
-    ("v128.load32x2_u",          "makeSIMDLoad(s, SIMDLoadOp::Load32x2UVec128)"),
-    ("v128.load32_zero",         "makeSIMDLoad(s, SIMDLoadOp::Load32ZeroVec128)"),
-    ("v128.load64_zero",         "makeSIMDLoad(s, SIMDLoadOp::Load64ZeroVec128)"),
+    ("v128.load8_splat",         "makeSIMDLoad(s, SIMDLoadOp::Load8SplatVec128, 1)"),
+    ("v128.load16_splat",        "makeSIMDLoad(s, SIMDLoadOp::Load16SplatVec128, 2)"),
+    ("v128.load32_splat",        "makeSIMDLoad(s, SIMDLoadOp::Load32SplatVec128, 4)"),
+    ("v128.load64_splat",        "makeSIMDLoad(s, SIMDLoadOp::Load64SplatVec128, 8)"),
+    ("v128.load8x8_s",           "makeSIMDLoad(s, SIMDLoadOp::Load8x8SVec128, 8)"),
+    ("v128.load8x8_u",           "makeSIMDLoad(s, SIMDLoadOp::Load8x8UVec128, 8)"),
+    ("v128.load16x4_s",          "makeSIMDLoad(s, SIMDLoadOp::Load16x4SVec128, 8)"),
+    ("v128.load16x4_u",          "makeSIMDLoad(s, SIMDLoadOp::Load16x4UVec128, 8)"),
+    ("v128.load32x2_s",          "makeSIMDLoad(s, SIMDLoadOp::Load32x2SVec128, 8)"),
+    ("v128.load32x2_u",          "makeSIMDLoad(s, SIMDLoadOp::Load32x2UVec128, 8)"),
+    ("v128.load32_zero",         "makeSIMDLoad(s, SIMDLoadOp::Load32ZeroVec128, 4)"),
+    ("v128.load64_zero",         "makeSIMDLoad(s, SIMDLoadOp::Load64ZeroVec128, 8)"),
     ("i8x16.narrow_i16x8_s",     "makeBinary(s, BinaryOp::NarrowSVecI16x8ToVecI8x16)"),
     ("i8x16.narrow_i16x8_u",     "makeBinary(s, BinaryOp::NarrowUVecI16x8ToVecI8x16)"),
     ("i16x8.narrow_i32x4_s",     "makeBinary(s, BinaryOp::NarrowSVecI32x4ToVecI16x8)"),
@@ -535,9 +535,7 @@ instructions = [
     ("f64x2.relaxed_max", "makeBinary(s, BinaryOp::RelaxedMaxVecF64x2)"),
     ("i16x8.relaxed_q15mulr_s", "makeBinary(s, BinaryOp::RelaxedQ15MulrSVecI16x8)"),
     ("i16x8.dot_i8x16_i7x16_s", "makeBinary(s, BinaryOp::DotI8x16I7x16SToVecI16x8)"),
-    ("i16x8.dot_i8x16_i7x16_u", "makeBinary(s, BinaryOp::DotI8x16I7x16UToVecI16x8)"),
     ("i32x4.dot_i8x16_i7x16_add_s", "makeSIMDTernary(s, SIMDTernaryOp::DotI8x16I7x16AddSToVecI32x4)"),
-    ("i32x4.dot_i8x16_i7x16_add_u", "makeSIMDTernary(s, SIMDTernaryOp::DotI8x16I7x16AddUToVecI32x4)"),
 
     # reference types instructions
     ("ref.null",             "makeRefNull(s)"),
@@ -569,9 +567,7 @@ instructions = [
     ("i31.new",              "makeI31New(s)"),
     ("i31.get_s",            "makeI31Get(s, true)"),
     ("i31.get_u",            "makeI31Get(s, false)"),
-    ("ref.test",             "makeRefTest(s)"),
     ("ref.test_static",      "makeRefTestStatic(s)"),
-    ("ref.cast",             "makeRefCast(s)"),
     ("ref.cast_static",      "makeRefCastStatic(s)"),
     ("ref.cast_nop_static",  "makeRefCastNopStatic(s)"),
     ("br_on_null",           "makeBrOn(s, BrOnNull)"),
@@ -586,22 +582,16 @@ instructions = [
     ("br_on_non_data",       "makeBrOn(s, BrOnNonData)"),
     ("br_on_i31",            "makeBrOn(s, BrOnI31)"),
     ("br_on_non_i31",        "makeBrOn(s, BrOnNonI31)"),
-    ("rtt.canon",            "makeRttCanon(s)"),
-    ("rtt.sub",              "makeRttSub(s)"),
-    ("rtt.fresh_sub",        "makeRttFreshSub(s)"),
-    ("struct.new_with_rtt",  "makeStructNew(s, false)"),
-    ("struct.new_default_with_rtt", "makeStructNew(s, true)"),
     ("struct.new",           "makeStructNewStatic(s, false)"),
     ("struct.new_default",   "makeStructNewStatic(s, true)"),
     ("struct.get",           "makeStructGet(s)"),
     ("struct.get_s",         "makeStructGet(s, true)"),
     ("struct.get_u",         "makeStructGet(s, false)"),
     ("struct.set",           "makeStructSet(s)"),
-    ("array.new_with_rtt",   "makeArrayNew(s, false)"),
-    ("array.new_default_with_rtt", "makeArrayNew(s, true)"),
     ("array.new",            "makeArrayNewStatic(s, false)"),
     ("array.new_default",    "makeArrayNewStatic(s, true)"),
-    ("array.init",           "makeArrayInit(s)"),
+    ("array.new_data",       "makeArrayNewSeg(s, NewData)"),
+    ("array.new_elem",       "makeArrayNewSeg(s, NewElem)"),
     ("array.init_static",    "makeArrayInitStatic(s)"),
     ("array.get",            "makeArrayGet(s)"),
     ("array.get_s",          "makeArrayGet(s, true)"),
@@ -616,6 +606,34 @@ instructions = [
     ("ref.as_func",          "makeRefAs(s, RefAsFunc)"),
     ("ref.as_data",          "makeRefAs(s, RefAsData)"),
     ("ref.as_i31",           "makeRefAs(s, RefAsI31)"),
+    ("extern.internalize",   "makeRefAs(s, ExternInternalize)"),
+    ("extern.externalize",   "makeRefAs(s, ExternExternalize)"),
+    ("string.new_wtf8",      "makeStringNew(s, StringNewWTF8)"),
+    ("string.new_wtf16",     "makeStringNew(s, StringNewWTF16)"),
+    ("string.new_wtf8_array",  "makeStringNew(s, StringNewWTF8Array)"),
+    ("string.new_wtf16_array", "makeStringNew(s, StringNewWTF16Array)"),
+    ("string.const",         "makeStringConst(s)"),
+    ("string.measure_wtf8",  "makeStringMeasure(s, StringMeasureWTF8)"),
+    ("string.measure_wtf16", "makeStringMeasure(s, StringMeasureWTF16)"),
+    ("string.is_usv_sequence", "makeStringMeasure(s, StringMeasureIsUSV)"),
+    ("string.encode_wtf8",   "makeStringEncode(s, StringEncodeWTF8)"),
+    ("string.encode_wtf16",  "makeStringEncode(s, StringEncodeWTF16)"),
+    ("string.encode_wtf8_array",   "makeStringEncode(s, StringEncodeWTF8Array)"),
+    ("string.encode_wtf16_array",  "makeStringEncode(s, StringEncodeWTF16Array)"),
+    ("string.concat",        "makeStringConcat(s)"),
+    ("string.eq",            "makeStringEq(s)"),
+    ("string.as_wtf8",       "makeStringAs(s, StringAsWTF8)"),
+    ("string.as_wtf16",      "makeStringAs(s, StringAsWTF16)"),
+    ("string.as_iter",       "makeStringAs(s, StringAsIter)"),
+    ("stringview_wtf8.advance",       "makeStringWTF8Advance(s)"),
+    ("stringview_wtf16.get_codeunit", "makeStringWTF16Get(s)"),
+    ("stringview_iter.next",          "makeStringIterNext(s)"),
+    ("stringview_iter.advance",       "makeStringIterMove(s, StringIterMoveAdvance)"),
+    ("stringview_iter.rewind",        "makeStringIterMove(s, StringIterMoveRewind)"),
+    ("stringview_wtf8.slice",         "makeStringSliceWTF(s, StringSliceWTF8)"),
+    ("stringview_wtf16.slice",        "makeStringSliceWTF(s, StringSliceWTF16)"),
+    ("stringview_iter.slice",         "makeStringSliceIter(s)"),
+    ("stringview_wtf16.length",       "makeStringMeasure(s, StringMeasureWTF16View)"),
 ]
 
 
@@ -685,7 +703,7 @@ class Node:
         self.do_insert(inst, inst, expr)
 
 
-def instruction_parser():
+def instruction_parser(new_parser=False):
     """Build a trie out of all the instructions, then emit it as C++ code."""
     trie = Node()
     inst_length = 0
@@ -695,12 +713,30 @@ def instruction_parser():
 
     printer = CodePrinter()
 
-    printer.print_line("char op[{}] = {{'\\0'}};".format(inst_length + 1))
-    printer.print_line("strncpy(op, s[0]->c_str(), {});".format(inst_length))
+    printer.print_line("char buf[{}] = {{}};".format(inst_length + 1))
+
+    if new_parser:
+        printer.print_line("auto str = *keyword;")
+    else:
+        printer.print_line("using namespace std::string_view_literals;")
+        printer.print_line("auto str = s[0]->str().str;")
+
+    printer.print_line("memcpy(buf, str.data(), str.size());")
+    printer.print_line("std::string_view op = {buf, str.size()};")
 
     def print_leaf(expr, inst):
-        printer.print_line("if (strcmp(op, \"{inst}\") == 0) {{ return {expr}; }}"
-                           .format(inst=inst, expr=expr))
+        if new_parser:
+            expr = expr.replace("()", "(ctx, pos)")
+            expr = expr.replace("(s", "(ctx, pos")
+            printer.print_line("if (op == \"{inst}\"sv) {{".format(inst=inst))
+            with printer.indent():
+                printer.print_line("auto ret = {expr};".format(expr=expr))
+                printer.print_line("CHECK_ERR(ret);")
+                printer.print_line("return *ret;")
+            printer.print_line("}")
+        else:
+            printer.print_line("if (op == \"{inst}\"sv) {{ return {expr}; }}"
+                               .format(inst=inst, expr=expr))
         printer.print_line("goto parse_error;")
 
     def emit(node, idx=0):
@@ -729,7 +765,10 @@ def instruction_parser():
     emit(trie)
     printer.print_line("parse_error:")
     with printer.indent():
-        printer.print_line("throw ParseException(std::string(op), s.line, s.col);")
+        if new_parser:
+            printer.print_line("return ctx.in.err(\"unrecognized instruction\");")
+        else:
+            printer.print_line("throw ParseException(std::string(op), s.line, s.col);")
 
 
 def print_header():
@@ -755,6 +794,8 @@ def main():
         sys.exit(1)
     print_header()
     generate_with_guard(instruction_parser, "INSTRUCTION_PARSER")
+    print()
+    generate_with_guard(lambda: instruction_parser(True), "NEW_INSTRUCTION_PARSER")
     print_footer()
 
 
diff --git a/scripts/port_passes_tests_to_lit.py b/scripts/port_passes_tests_to_lit.py
index 4a71f74..1ac5ac8 100755
--- a/scripts/port_passes_tests_to_lit.py
+++ b/scripts/port_passes_tests_to_lit.py
@@ -67,8 +67,8 @@ def port_test(args, test):
     run_line = (f';; RUN: foreach %s %t wasm-opt {" ".join(opts)} -S -o -'
                 ' | filecheck %s')
 
-    notice = (f';; NOTE: This test was ported using port_test.py and could be'
-              ' cleaned up.')
+    notice = (f';; NOTE: This test was ported using'
+              ' port_passes_tests_to_lit.py and could be cleaned up.')
 
     with open(test, 'r') as src_file:
         with open(dest, 'w') as dest_file:
@@ -79,7 +79,10 @@ def port_test(args, test):
             print(src_file.read(), file=dest_file, end='')
 
     update_script = os.path.join(script_dir, 'update_lit_checks.py')
-    subprocess.run([sys.executable, update_script, '-f', '--all-items', dest])
+    cmd = [sys.executable, update_script, '-f', '--all-items', dest]
+    if args.binaryen_bin:
+        cmd += ['--binaryen-bin', args.binaryen_bin]
+    subprocess.check_call(cmd)
 
     if not args.no_delete:
         for f in glob.glob(test.replace('.wast', '.*')):
@@ -88,14 +91,19 @@ def port_test(args, test):
                 continue
             os.remove(f)
             if args.git_add:
-                subprocess.run(['git', 'add', f])
+                subprocess.rcheck_call(['git', 'add', f])
 
     if args.git_add:
-        subprocess.run(['git', 'add', dest])
+        subprocess.check_call(['git', 'add', dest])
 
 
 def main():
     parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument(
+        '--binaryen-bin', dest='binaryen_bin', default='bin',
+        help=('Specifies the path to the Binaryen executables in the CMake build'
+              ' directory. Default: bin/ of current directory (i.e. assume an'
+              ' in-tree build).'))
     parser.add_argument('-f', '--force', action='store_true',
                         help='Overwrite existing lit tests')
     parser.add_argument('--no-delete', action='store_true',
@@ -104,6 +112,7 @@ def main():
                         help='Stage changes')
     parser.add_argument('tests', nargs='+', help='The test files to port')
     args = parser.parse_args()
+    args.binaryen_bin = os.path.abspath(args.binaryen_bin)
 
     for pattern in args.tests:
         for test in glob.glob(pattern, recursive=True):
diff --git a/scripts/test/generate_lld_tests.py b/scripts/test/generate_lld_tests.py
index 1867d1d..254d733 100755
--- a/scripts/test/generate_lld_tests.py
+++ b/scripts/test/generate_lld_tests.py
@@ -30,7 +30,7 @@ def files_with_extensions(path, extensions):
             yield file, ext
 
 
-def generate_wat_files(llvm_bin, emscripten_root):
+def generate_wat_files(llvm_bin, emscripten_sysroot):
     print('\n[ building wat files from C sources... ]\n')
 
     lld_path = os.path.join(shared.options.binaryen_test, 'lld')
@@ -57,13 +57,12 @@ def generate_wat_files(llvm_bin, emscripten_root):
             '-nostdinc',
             '-Xclang', '-nobuiltininc',
             '-Xclang', '-nostdsysteminc',
-            '-Xclang', '-I%s/system/include' % emscripten_root,
+            '-Xclang', '-I%s/include' % emscripten_sysroot,
             '-O1',
         ]
 
         link_cmd = [
             os.path.join(llvm_bin, 'wasm-ld'), '-flavor', 'wasm',
-            '-z', '-stack-size=1048576',
             obj_path, '-o', wasm_path,
             '--allow-undefined',
             '--export', '__wasm_call_ctors',
@@ -81,7 +80,10 @@ def generate_wat_files(llvm_bin, emscripten_root):
             link_cmd.append('-shared')
             link_cmd.append('--experimental-pic')
         else:
-            link_cmd.append('--entry=main')
+            if 'reserved_func_ptr' in src_file:
+                link_cmd.append('--entry=__main_argc_argv')
+            else:
+                link_cmd.append('--entry=main')
 
         if is_64:
             compile_cmd.append('--target=wasm64-emscripten')
diff --git a/scripts/test/lld.py b/scripts/test/lld.py
index 2fc3261..4f1bbc0 100644
--- a/scripts/test/lld.py
+++ b/scripts/test/lld.py
@@ -12,7 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json
 import os
 from . import shared
 from . import support
@@ -26,8 +25,6 @@ def args_for_finalize(filename):
         ret += ['--side-module']
     if 'standalone-wasm' in filename:
         ret += ['--standalone-wasm']
-    if 'no-emit-metadata' in filename:
-        ret += ['--no-emit-metadata']
     return ret
 
 
@@ -59,13 +56,6 @@ def run_test(input_path):
         actual = support.run_command(cmd)
 
         shared.fail_if_not_identical_to_file(actual, expected_file)
-        if ext == '.out' and '--no-emit-metadata' not in cmd:
-            start = actual.find('--BEGIN METADATA --\n')
-            end = actual.find('-- END METADATA --\n')
-            if start == -1 or end == -1:
-                shared.fail_with_error('json metadata tags not found')
-            the_json = actual[start + len('--BEGIN METADATA --\n'):end]
-            json.loads(the_json)
 
         if ext == '.mem.out':
             with open(mem_file) as mf:
diff --git a/scripts/test/node-esm-loader.mjs b/scripts/test/node-esm-loader.mjs
index 8a3b67d..ad58c06 100644
--- a/scripts/test/node-esm-loader.mjs
+++ b/scripts/test/node-esm-loader.mjs
@@ -25,6 +25,8 @@ const specialTestSuiteModules = {
 export async function resolve(specifier, context, defaultResolve) {
   const specialModule = specialTestSuiteModules[specifier];
   if (specialModule) {
+    // This is needed for newer node versions.
+    specialModule.shortCircuit = true;
     return specialModule;
   }
   return defaultResolve(specifier, context, defaultResolve);
diff --git a/scripts/test/shared.py b/scripts/test/shared.py
index d86d802..1d48ddd 100644
--- a/scripts/test/shared.py
+++ b/scripts/test/shared.py
@@ -201,6 +201,7 @@ NATIVEXX = (os.environ.get('CXX') or which('mingw32-g++') or
             which('g++') or which('clang++'))
 NODEJS = os.getenv('NODE', which('node') or which('nodejs'))
 MOZJS = which('mozjs') or which('spidermonkey')
+
 V8 = which('v8') or which('d8')
 
 BINARYEN_INSTALL_DIR = os.path.dirname(options.binaryen_bin)
@@ -261,7 +262,9 @@ V8_OPTS = [
     '--experimental-wasm-compilation-hints',
     '--experimental-wasm-gc',
     '--experimental-wasm-typed-funcref',
-    '--experimental-wasm-memory64'
+    '--experimental-wasm-memory64',
+    '--experimental-wasm-extended-const',
+    '--experimental-wasm-nn-locals',
 ]
 
 # external tools
diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py
index 11f7011..7fdd8eb 100644
--- a/scripts/test/wasm2js.py
+++ b/scripts/test/wasm2js.py
@@ -14,8 +14,8 @@
 
 import os
 
-from scripts.test import shared
-from scripts.test import support
+from . import shared
+from . import support
 
 tests = shared.get_tests(shared.options.binaryen_test)
 # memory64 is not supported in wasm2js yet (but may be with BigInt eventually).
diff --git a/scripts/update_lit_checks.py b/scripts/update_lit_checks.py
index 20a5851..b7b836e 100755
--- a/scripts/update_lit_checks.py
+++ b/scripts/update_lit_checks.py
@@ -40,7 +40,7 @@ MODULE_RE = re.compile(r'^\(module.*$', re.MULTILINE)
 ALL_ITEMS = '|'.join(['type', 'import', 'global', 'memory', 'data', 'table',
                       'elem', 'tag', 'export', 'start', 'func'])
 ITEM_NAME = r'\$?[^\s()]*|"[^\s()]*"'
-ITEM_RE = re.compile(r'(^\s*)\((' + ALL_ITEMS + r')\s+(' + ITEM_NAME + ').*$',
+ITEM_RE = re.compile(r'(?:^\s*\(rec\s*)?(^\s*)\((' + ALL_ITEMS + r')\s+(' + ITEM_NAME + ').*$',
                      re.MULTILINE)
 
 FUZZ_EXEC_FUNC = re.compile(r'^\[fuzz-exec\] calling (?P<name>\S*)$')
diff --git a/scripts/wasm2js.js b/scripts/wasm2js.js
index 4a16184..d76e372 100644
--- a/scripts/wasm2js.js
+++ b/scripts/wasm2js.js
@@ -64,9 +64,9 @@ var WebAssembly = {
     };
     var atob = decodeBase64;
     // Additional imports
-    asmLibraryArg['__tempMemory__'] = 0; // risky!
+    info['env']['__tempMemory__'] = 0; // risky!
     // This will be replaced by the actual wasm2js code.
-    var exports = instantiate(asmLibraryArg, wasmMemory);
+    var exports = instantiate(info, wasmMemory);
     return {
       'exports': exports
     };
@@ -85,7 +85,7 @@ var WebAssembly = {
 
 var tempRet0 = 0;
 
-var asmLibraryArg = {
+var env = {
   log_i32: function(x) {
     console.log('[LoggingExternalInterface logging ' + literal(x, 'i32') + ']');
   },
@@ -113,7 +113,7 @@ var asmLibraryArg = {
   },
   get_i64: function(loc, index, low, high) {
     console.log('get_i64 ' + [loc, index, low, high]);
-    asmLibraryArg['setTempRet0'](high);
+    env['setTempRet0'](high);
     return low;
   },
   get_f32: function(loc, index, value) {
@@ -130,7 +130,7 @@ var asmLibraryArg = {
   },
   set_i64: function(loc, index, low, high) {
     console.log('set_i64 ' + [loc, index, low, high]);
-    asmLibraryArg['setTempRet0'](high);
+    env['setTempRet0'](high);
     return low;
   },
   set_f32: function(loc, index, value) {
@@ -151,7 +151,7 @@ var asmLibraryArg = {
   },
   load_val_i64: function(loc, low, high) {
     console.log('load_val_i64 ' + [loc, low, high]);
-    asmLibraryArg['setTempRet0'](high);
+    env['setTempRet0'](high);
     return low;
   },
   load_val_f32: function(loc, value) {
@@ -172,7 +172,7 @@ var asmLibraryArg = {
   },
   store_val_i64: function(loc, low, high) {
     console.log('store_val_i64 ' + [loc, low, high]);
-    asmLibraryArg['setTempRet0'](high);
+    env['setTempRet0'](high);
     return low;
   },
   store_val_f32: function(loc, value) {
diff --git a/src/abi/js.h b/src/abi/js.h
index 970f7ff..735f71a 100644
--- a/src/abi/js.h
+++ b/src/abi/js.h
@@ -37,27 +37,27 @@ inline std::string getLegalizationPass(LegalizationLevel level) {
 
 namespace wasm2js {
 
-extern cashew::IString SCRATCH_LOAD_I32;
-extern cashew::IString SCRATCH_STORE_I32;
-extern cashew::IString SCRATCH_LOAD_F32;
-extern cashew::IString SCRATCH_STORE_F32;
-extern cashew::IString SCRATCH_LOAD_F64;
-extern cashew::IString SCRATCH_STORE_F64;
-extern cashew::IString MEMORY_INIT;
-extern cashew::IString MEMORY_FILL;
-extern cashew::IString MEMORY_COPY;
-extern cashew::IString DATA_DROP;
-extern cashew::IString ATOMIC_WAIT_I32;
-extern cashew::IString ATOMIC_RMW_I64;
-extern cashew::IString GET_STASHED_BITS;
+extern IString SCRATCH_LOAD_I32;
+extern IString SCRATCH_STORE_I32;
+extern IString SCRATCH_LOAD_F32;
+extern IString SCRATCH_STORE_F32;
+extern IString SCRATCH_LOAD_F64;
+extern IString SCRATCH_STORE_F64;
+extern IString MEMORY_INIT;
+extern IString MEMORY_FILL;
+extern IString MEMORY_COPY;
+extern IString DATA_DROP;
+extern IString ATOMIC_WAIT_I32;
+extern IString ATOMIC_RMW_I64;
+extern IString GET_STASHED_BITS;
+extern IString TRAP;
 
 // The wasm2js helpers let us do things that can't be done without special help,
 // like read and write to scratch memory for purposes of implementing things
 // like reinterpret, etc.
 // The optional "specific" parameter is a specific function we want. If not
 // provided, we create them all.
-inline void ensureHelpers(Module* wasm,
-                          cashew::IString specific = cashew::IString()) {
+inline void ensureHelpers(Module* wasm, IString specific = IString()) {
   auto ensureImport = [&](Name name, Type params, Type results) {
     if (wasm->getFunctionOrNull(name)) {
       return;
@@ -89,15 +89,16 @@ inline void ensureHelpers(Module* wasm,
     {Type::i32, Type::i32, Type::i32, Type::i32, Type::i32, Type::i32},
     Type::i32);
   ensureImport(GET_STASHED_BITS, {}, Type::i32);
+  ensureImport(TRAP, {}, Type::none);
 }
 
-inline bool isHelper(cashew::IString name) {
+inline bool isHelper(IString name) {
   return name == SCRATCH_LOAD_I32 || name == SCRATCH_STORE_I32 ||
          name == SCRATCH_LOAD_F32 || name == SCRATCH_STORE_F32 ||
          name == SCRATCH_LOAD_F64 || name == SCRATCH_STORE_F64 ||
          name == ATOMIC_WAIT_I32 || name == MEMORY_INIT ||
          name == MEMORY_FILL || name == MEMORY_COPY || name == DATA_DROP ||
-         name == ATOMIC_RMW_I64 || name == GET_STASHED_BITS;
+         name == ATOMIC_RMW_I64 || name == GET_STASHED_BITS || name == TRAP;
 }
 
 } // namespace wasm2js
diff --git a/src/abi/stack.h b/src/abi/stack.h
new file mode 100644
index 0000000..93a6e4c
--- /dev/null
+++ b/src/abi/stack.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_abi_stack_h
+#define wasm_abi_stack_h
+
+#include "asmjs/shared-constants.h"
+#include "ir/find_all.h"
+#include "ir/global-utils.h"
+#include "shared-constants.h"
+#include "wasm-builder.h"
+#include "wasm-emscripten.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace ABI {
+
+enum { StackAlign = 16 };
+
+inline Index stackAlign(Index size) {
+  return (size + StackAlign - 1) & -StackAlign;
+}
+
+// Allocate some space on the stack, and assign it to a local.
+// The local will have the same constant value in all the function, so you can
+// just local.get it anywhere there.
+//
+// FIXME: This function assumes that the stack grows upward, per the convention
+// used by fastcomp.  The stack grows downward when using the WASM backend.
+
+inline void
+getStackSpace(Index local, Function* func, Index size, Module& wasm) {
+  auto* stackPointer = getStackPointerGlobal(wasm);
+  if (!stackPointer) {
+    Fatal() << "getStackSpace: failed to find the stack pointer";
+  }
+  // align the size
+  size = stackAlign(size);
+  auto pointerType =
+    !wasm.memories.empty() ? wasm.memories[0]->indexType : Type::i32;
+  // TODO: find existing stack usage, and add on top of that - carefully
+  Builder builder(wasm);
+  auto* block = builder.makeBlock();
+  block->list.push_back(builder.makeLocalSet(
+    local, builder.makeGlobalGet(stackPointer->name, pointerType)));
+  // TODO: add stack max check
+  Expression* added;
+  if (pointerType == Type::i32) {
+    // The stack goes downward in the LLVM wasm backend.
+    added = builder.makeBinary(SubInt32,
+                               builder.makeLocalGet(local, pointerType),
+                               builder.makeConst(int32_t(size)));
+  } else {
+    WASM_UNREACHABLE("unhandled pointerType");
+  }
+  block->list.push_back(builder.makeGlobalSet(stackPointer->name, added));
+  auto makeStackRestore = [&]() {
+    return builder.makeGlobalSet(stackPointer->name,
+                                 builder.makeLocalGet(local, pointerType));
+  };
+  // add stack restores to the returns
+  FindAllPointers<Return> finder(func->body);
+  for (auto** ptr : finder.list) {
+    auto* ret = (*ptr)->cast<Return>();
+    if (ret->value && ret->value->type != Type::unreachable) {
+      // handle the returned value
+      auto* block = builder.makeBlock();
+      auto temp = builder.addVar(func, ret->value->type);
+      block->list.push_back(builder.makeLocalSet(temp, ret->value));
+      block->list.push_back(makeStackRestore());
+      block->list.push_back(
+        builder.makeReturn(builder.makeLocalGet(temp, ret->value->type)));
+      block->finalize();
+      *ptr = block;
+    } else {
+      // restore, then return
+      *ptr = builder.makeSequence(makeStackRestore(), ret);
+    }
+  }
+  // add stack restores to the body
+  if (func->body->type == Type::none) {
+    block->list.push_back(func->body);
+    block->list.push_back(makeStackRestore());
+  } else if (func->body->type == Type::unreachable) {
+    block->list.push_back(func->body);
+    // no need to restore the old stack value, we're gone anyhow
+  } else {
+    // save the return value
+    auto temp = builder.addVar(func, func->getResults());
+    block->list.push_back(builder.makeLocalSet(temp, func->body));
+    block->list.push_back(makeStackRestore());
+    block->list.push_back(builder.makeLocalGet(temp, func->getResults()));
+  }
+  block->finalize();
+  func->body = block;
+}
+
+} // namespace ABI
+
+} // namespace wasm
+
+#endif // wasm_abi_stack_h
diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp
index 38eb029..314c249 100644
--- a/src/asmjs/asm_v_wasm.cpp
+++ b/src/asmjs/asm_v_wasm.cpp
@@ -33,12 +33,6 @@ JsType wasmToJsType(Type type) {
       return JS_INT64;
     case Type::v128:
       WASM_UNREACHABLE("v128 not implemented yet");
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
-      WASM_UNREACHABLE("reference types are not supported by wasm2js");
     case Type::none:
       return JS_NONE;
     case Type::unreachable:
@@ -60,16 +54,6 @@ char getSig(Type type) {
       return 'd';
     case Type::v128:
       return 'V';
-    case Type::funcref:
-      return 'F';
-    case Type::anyref:
-      return 'A';
-    case Type::eqref:
-      return 'Q';
-    case Type::i31ref:
-      return 'I';
-    case Type::dataref:
-      return 'D';
     case Type::none:
       return 'v';
     case Type::unreachable:
diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp
index 20b1481..57b9c4f 100644
--- a/src/asmjs/shared-constants.cpp
+++ b/src/asmjs/shared-constants.cpp
@@ -18,104 +18,104 @@
 
 namespace wasm {
 
-cashew::IString TOPMOST("topmost");
-cashew::IString INT8ARRAY("Int8Array");
-cashew::IString INT16ARRAY("Int16Array");
-cashew::IString INT32ARRAY("Int32Array");
-cashew::IString UINT8ARRAY("Uint8Array");
-cashew::IString UINT16ARRAY("Uint16Array");
-cashew::IString UINT32ARRAY("Uint32Array");
-cashew::IString FLOAT32ARRAY("Float32Array");
-cashew::IString FLOAT64ARRAY("Float64Array");
-cashew::IString ARRAY_BUFFER("ArrayBuffer");
-cashew::IString ASM_MODULE("asmModule");
-cashew::IString MATH("Math");
-cashew::IString IMUL("imul");
-cashew::IString CLZ32("clz32");
-cashew::IString FROUND("fround");
-cashew::IString ASM2WASM("asm2wasm");
-cashew::IString MIN("min");
-cashew::IString MAX("max");
-cashew::IString F64_REM("f64-rem");
-cashew::IString F64_TO_INT("f64-to-int");
-cashew::IString F64_TO_UINT("f64-to-uint");
-cashew::IString F64_TO_INT64("f64-to-int64");
-cashew::IString F64_TO_UINT64("f64-to-uint64");
-cashew::IString F32_TO_INT("f32-to-int");
-cashew::IString F32_TO_UINT("f32-to-uint");
-cashew::IString F32_TO_INT64("f32-to-int64");
-cashew::IString F32_TO_UINT64("f32-to-uint64");
-cashew::IString I32S_DIV("i32s-div");
-cashew::IString I32U_DIV("i32u-div");
-cashew::IString I32S_REM("i32s-rem");
-cashew::IString I32U_REM("i32u-rem");
-cashew::IString ABS("abs");
-cashew::IString FLOOR("floor");
-cashew::IString CEIL("ceil");
-cashew::IString TRUNC("trunc");
-cashew::IString SQRT("sqrt");
-cashew::IString POW("pow");
-cashew::IString I32_TEMP("asm2wasm_i32_temp");
-cashew::IString DEBUGGER("debugger");
-cashew::IString BUFFER("buffer");
-cashew::IString ENV("env");
-cashew::IString STACKTOP("STACKTOP");
-cashew::IString STACK_MAX("STACK_MAX");
-cashew::IString INSTRUMENT("instrument");
-cashew::IString MATH_IMUL("Math_imul");
-cashew::IString MATH_ABS("Math_abs");
-cashew::IString MATH_CEIL("Math_ceil");
-cashew::IString MATH_CLZ32("Math_clz32");
-cashew::IString MATH_FLOOR("Math_floor");
-cashew::IString MATH_TRUNC("Math_trunc");
-cashew::IString MATH_SQRT("Math_sqrt");
-cashew::IString MATH_MIN("Math_min");
-cashew::IString MATH_MAX("Math_max");
-cashew::IString WASM_CTZ32("__wasm_ctz_i32");
-cashew::IString WASM_CTZ64("__wasm_ctz_i64");
-cashew::IString WASM_CLZ32("__wasm_clz_i32");
-cashew::IString WASM_CLZ64("__wasm_clz_i64");
-cashew::IString WASM_POPCNT32("__wasm_popcnt_i32");
-cashew::IString WASM_POPCNT64("__wasm_popcnt_i64");
-cashew::IString WASM_ROTL32("__wasm_rotl_i32");
-cashew::IString WASM_ROTL64("__wasm_rotl_i64");
-cashew::IString WASM_ROTR32("__wasm_rotr_i32");
-cashew::IString WASM_ROTR64("__wasm_rotr_i64");
-cashew::IString WASM_MEMORY_GROW("__wasm_memory_grow");
-cashew::IString WASM_MEMORY_SIZE("__wasm_memory_size");
-cashew::IString WASM_FETCH_HIGH_BITS("__wasm_fetch_high_bits");
-cashew::IString INT64_TO_32_HIGH_BITS("i64toi32_i32$HIGH_BITS");
-cashew::IString WASM_NEAREST_F32("__wasm_nearest_f32");
-cashew::IString WASM_NEAREST_F64("__wasm_nearest_f64");
-cashew::IString WASM_I64_MUL("__wasm_i64_mul");
-cashew::IString WASM_I64_SDIV("__wasm_i64_sdiv");
-cashew::IString WASM_I64_UDIV("__wasm_i64_udiv");
-cashew::IString WASM_I64_SREM("__wasm_i64_srem");
-cashew::IString WASM_I64_UREM("__wasm_i64_urem");
+IString TOPMOST("topmost");
+IString INT8ARRAY("Int8Array");
+IString INT16ARRAY("Int16Array");
+IString INT32ARRAY("Int32Array");
+IString UINT8ARRAY("Uint8Array");
+IString UINT16ARRAY("Uint16Array");
+IString UINT32ARRAY("Uint32Array");
+IString FLOAT32ARRAY("Float32Array");
+IString FLOAT64ARRAY("Float64Array");
+IString ARRAY_BUFFER("ArrayBuffer");
+IString ASM_MODULE("asmModule");
+IString MATH("Math");
+IString IMUL("imul");
+IString CLZ32("clz32");
+IString FROUND("fround");
+IString ASM2WASM("asm2wasm");
+IString MIN("min");
+IString MAX("max");
+IString F64_REM("f64-rem");
+IString F64_TO_INT("f64-to-int");
+IString F64_TO_UINT("f64-to-uint");
+IString F64_TO_INT64("f64-to-int64");
+IString F64_TO_UINT64("f64-to-uint64");
+IString F32_TO_INT("f32-to-int");
+IString F32_TO_UINT("f32-to-uint");
+IString F32_TO_INT64("f32-to-int64");
+IString F32_TO_UINT64("f32-to-uint64");
+IString I32S_DIV("i32s-div");
+IString I32U_DIV("i32u-div");
+IString I32S_REM("i32s-rem");
+IString I32U_REM("i32u-rem");
+IString ABS("abs");
+IString FLOOR("floor");
+IString CEIL("ceil");
+IString TRUNC("trunc");
+IString SQRT("sqrt");
+IString POW("pow");
+IString I32_TEMP("asm2wasm_i32_temp");
+IString DEBUGGER("debugger");
+IString BUFFER("buffer");
+IString ENV("env");
+IString STACKTOP("STACKTOP");
+IString STACK_MAX("STACK_MAX");
+IString INSTRUMENT("instrument");
+IString MATH_IMUL("Math_imul");
+IString MATH_ABS("Math_abs");
+IString MATH_CEIL("Math_ceil");
+IString MATH_CLZ32("Math_clz32");
+IString MATH_FLOOR("Math_floor");
+IString MATH_TRUNC("Math_trunc");
+IString MATH_SQRT("Math_sqrt");
+IString MATH_MIN("Math_min");
+IString MATH_MAX("Math_max");
+IString WASM_CTZ32("__wasm_ctz_i32");
+IString WASM_CTZ64("__wasm_ctz_i64");
+IString WASM_CLZ32("__wasm_clz_i32");
+IString WASM_CLZ64("__wasm_clz_i64");
+IString WASM_POPCNT32("__wasm_popcnt_i32");
+IString WASM_POPCNT64("__wasm_popcnt_i64");
+IString WASM_ROTL32("__wasm_rotl_i32");
+IString WASM_ROTL64("__wasm_rotl_i64");
+IString WASM_ROTR32("__wasm_rotr_i32");
+IString WASM_ROTR64("__wasm_rotr_i64");
+IString WASM_MEMORY_GROW("__wasm_memory_grow");
+IString WASM_MEMORY_SIZE("__wasm_memory_size");
+IString WASM_FETCH_HIGH_BITS("__wasm_fetch_high_bits");
+IString INT64_TO_32_HIGH_BITS("i64toi32_i32$HIGH_BITS");
+IString WASM_NEAREST_F32("__wasm_nearest_f32");
+IString WASM_NEAREST_F64("__wasm_nearest_f64");
+IString WASM_I64_MUL("__wasm_i64_mul");
+IString WASM_I64_SDIV("__wasm_i64_sdiv");
+IString WASM_I64_UDIV("__wasm_i64_udiv");
+IString WASM_I64_SREM("__wasm_i64_srem");
+IString WASM_I64_UREM("__wasm_i64_urem");
 
-cashew::IString ASM_FUNC("asmFunc");
-cashew::IString ABORT_FUNC("abort");
-cashew::IString FUNCTION_TABLE("FUNCTION_TABLE");
-cashew::IString NO_RESULT("wasm2js$noresult"); // no result at all
+IString ASM_FUNC("asmFunc");
+IString FUNCTION_TABLE("FUNCTION_TABLE");
+IString NO_RESULT("wasm2js$noresult"); // no result at all
 // result in an expression, no temp var
-cashew::IString EXPRESSION_RESULT("wasm2js$expresult");
+IString EXPRESSION_RESULT("wasm2js$expresult");
 
 namespace ABI {
 namespace wasm2js {
 
-cashew::IString SCRATCH_LOAD_I32("wasm2js_scratch_load_i32");
-cashew::IString SCRATCH_STORE_I32("wasm2js_scratch_store_i32");
-cashew::IString SCRATCH_LOAD_F32("wasm2js_scratch_load_f32");
-cashew::IString SCRATCH_STORE_F32("wasm2js_scratch_store_f32");
-cashew::IString SCRATCH_LOAD_F64("wasm2js_scratch_load_f64");
-cashew::IString SCRATCH_STORE_F64("wasm2js_scratch_store_f64");
-cashew::IString MEMORY_INIT("wasm2js_memory_init");
-cashew::IString MEMORY_FILL("wasm2js_memory_fill");
-cashew::IString MEMORY_COPY("wasm2js_memory_copy");
-cashew::IString DATA_DROP("wasm2js_data_drop");
-cashew::IString ATOMIC_WAIT_I32("wasm2js_atomic_wait_i32");
-cashew::IString ATOMIC_RMW_I64("wasm2js_atomic_rmw_i64");
-cashew::IString GET_STASHED_BITS("wasm2js_get_stashed_bits");
+IString SCRATCH_LOAD_I32("wasm2js_scratch_load_i32");
+IString SCRATCH_STORE_I32("wasm2js_scratch_store_i32");
+IString SCRATCH_LOAD_F32("wasm2js_scratch_load_f32");
+IString SCRATCH_STORE_F32("wasm2js_scratch_store_f32");
+IString SCRATCH_LOAD_F64("wasm2js_scratch_load_f64");
+IString SCRATCH_STORE_F64("wasm2js_scratch_store_f64");
+IString MEMORY_INIT("wasm2js_memory_init");
+IString MEMORY_FILL("wasm2js_memory_fill");
+IString MEMORY_COPY("wasm2js_memory_copy");
+IString DATA_DROP("wasm2js_data_drop");
+IString ATOMIC_WAIT_I32("wasm2js_atomic_wait_i32");
+IString ATOMIC_RMW_I64("wasm2js_atomic_rmw_i64");
+IString GET_STASHED_BITS("wasm2js_get_stashed_bits");
+IString TRAP("wasm2js_trap");
 
 } // namespace wasm2js
 } // namespace ABI
diff --git a/src/asmjs/shared-constants.h b/src/asmjs/shared-constants.h
index 20edacb..e199b13 100644
--- a/src/asmjs/shared-constants.h
+++ b/src/asmjs/shared-constants.h
@@ -17,90 +17,89 @@
 #ifndef wasm_asmjs_shared_constants_h
 #define wasm_asmjs_shared_constants_h
 
-#include "emscripten-optimizer/istring.h"
+#include "support/istring.h"
 
 namespace wasm {
 
-extern cashew::IString TOPMOST;
-extern cashew::IString INT8ARRAY;
-extern cashew::IString INT16ARRAY;
-extern cashew::IString INT32ARRAY;
-extern cashew::IString UINT8ARRAY;
-extern cashew::IString UINT16ARRAY;
-extern cashew::IString UINT32ARRAY;
-extern cashew::IString FLOAT32ARRAY;
-extern cashew::IString FLOAT64ARRAY;
-extern cashew::IString ARRAY_BUFFER;
-extern cashew::IString ASM_MODULE;
-extern cashew::IString MATH;
-extern cashew::IString IMUL;
-extern cashew::IString CLZ32;
-extern cashew::IString FROUND;
-extern cashew::IString ASM2WASM;
-extern cashew::IString MIN;
-extern cashew::IString MAX;
-extern cashew::IString F64_REM;
-extern cashew::IString F64_TO_INT;
-extern cashew::IString F64_TO_UINT;
-extern cashew::IString F64_TO_INT64;
-extern cashew::IString F64_TO_UINT64;
-extern cashew::IString F32_TO_INT;
-extern cashew::IString F32_TO_UINT;
-extern cashew::IString F32_TO_INT64;
-extern cashew::IString F32_TO_UINT64;
-extern cashew::IString I32S_DIV;
-extern cashew::IString I32U_DIV;
-extern cashew::IString I32S_REM;
-extern cashew::IString I32U_REM;
-extern cashew::IString ABS;
-extern cashew::IString FLOOR;
-extern cashew::IString CEIL;
-extern cashew::IString TRUNC;
-extern cashew::IString SQRT;
-extern cashew::IString POW;
-extern cashew::IString I32_TEMP;
-extern cashew::IString DEBUGGER;
-extern cashew::IString BUFFER;
-extern cashew::IString ENV;
-extern cashew::IString STACKTOP;
-extern cashew::IString STACK_MAX;
-extern cashew::IString INSTRUMENT;
-extern cashew::IString MATH_IMUL;
-extern cashew::IString MATH_ABS;
-extern cashew::IString MATH_CLZ32;
-extern cashew::IString MATH_CEIL;
-extern cashew::IString MATH_FLOOR;
-extern cashew::IString MATH_TRUNC;
-extern cashew::IString MATH_SQRT;
-extern cashew::IString MATH_MIN;
-extern cashew::IString MATH_MAX;
-extern cashew::IString WASM_CTZ32;
-extern cashew::IString WASM_CTZ64;
-extern cashew::IString WASM_CLZ32;
-extern cashew::IString WASM_CLZ64;
-extern cashew::IString WASM_POPCNT32;
-extern cashew::IString WASM_POPCNT64;
-extern cashew::IString WASM_ROTL32;
-extern cashew::IString WASM_ROTL64;
-extern cashew::IString WASM_ROTR32;
-extern cashew::IString WASM_ROTR64;
-extern cashew::IString WASM_MEMORY_GROW;
-extern cashew::IString WASM_MEMORY_SIZE;
-extern cashew::IString WASM_FETCH_HIGH_BITS;
-extern cashew::IString INT64_TO_32_HIGH_BITS;
-extern cashew::IString WASM_NEAREST_F32;
-extern cashew::IString WASM_NEAREST_F64;
-extern cashew::IString WASM_I64_MUL;
-extern cashew::IString WASM_I64_SDIV;
-extern cashew::IString WASM_I64_UDIV;
-extern cashew::IString WASM_I64_SREM;
-extern cashew::IString WASM_I64_UREM;
+extern IString TOPMOST;
+extern IString INT8ARRAY;
+extern IString INT16ARRAY;
+extern IString INT32ARRAY;
+extern IString UINT8ARRAY;
+extern IString UINT16ARRAY;
+extern IString UINT32ARRAY;
+extern IString FLOAT32ARRAY;
+extern IString FLOAT64ARRAY;
+extern IString ARRAY_BUFFER;
+extern IString ASM_MODULE;
+extern IString MATH;
+extern IString IMUL;
+extern IString CLZ32;
+extern IString FROUND;
+extern IString ASM2WASM;
+extern IString MIN;
+extern IString MAX;
+extern IString F64_REM;
+extern IString F64_TO_INT;
+extern IString F64_TO_UINT;
+extern IString F64_TO_INT64;
+extern IString F64_TO_UINT64;
+extern IString F32_TO_INT;
+extern IString F32_TO_UINT;
+extern IString F32_TO_INT64;
+extern IString F32_TO_UINT64;
+extern IString I32S_DIV;
+extern IString I32U_DIV;
+extern IString I32S_REM;
+extern IString I32U_REM;
+extern IString ABS;
+extern IString FLOOR;
+extern IString CEIL;
+extern IString TRUNC;
+extern IString SQRT;
+extern IString POW;
+extern IString I32_TEMP;
+extern IString DEBUGGER;
+extern IString BUFFER;
+extern IString ENV;
+extern IString STACKTOP;
+extern IString STACK_MAX;
+extern IString INSTRUMENT;
+extern IString MATH_IMUL;
+extern IString MATH_ABS;
+extern IString MATH_CLZ32;
+extern IString MATH_CEIL;
+extern IString MATH_FLOOR;
+extern IString MATH_TRUNC;
+extern IString MATH_SQRT;
+extern IString MATH_MIN;
+extern IString MATH_MAX;
+extern IString WASM_CTZ32;
+extern IString WASM_CTZ64;
+extern IString WASM_CLZ32;
+extern IString WASM_CLZ64;
+extern IString WASM_POPCNT32;
+extern IString WASM_POPCNT64;
+extern IString WASM_ROTL32;
+extern IString WASM_ROTL64;
+extern IString WASM_ROTR32;
+extern IString WASM_ROTR64;
+extern IString WASM_MEMORY_GROW;
+extern IString WASM_MEMORY_SIZE;
+extern IString WASM_FETCH_HIGH_BITS;
+extern IString INT64_TO_32_HIGH_BITS;
+extern IString WASM_NEAREST_F32;
+extern IString WASM_NEAREST_F64;
+extern IString WASM_I64_MUL;
+extern IString WASM_I64_SDIV;
+extern IString WASM_I64_UDIV;
+extern IString WASM_I64_SREM;
+extern IString WASM_I64_UREM;
 // wasm2js constants
-extern cashew::IString ASM_FUNC;
-extern cashew::IString ABORT_FUNC;
-extern cashew::IString FUNCTION_TABLE;
-extern cashew::IString NO_RESULT;
-extern cashew::IString EXPRESSION_RESULT;
+extern IString ASM_FUNC;
+extern IString FUNCTION_TABLE;
+extern IString NO_RESULT;
+extern IString EXPRESSION_RESULT;
 } // namespace wasm
 
 #endif // wasm_asmjs_shared_constants_h
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp
index aa9f7e6..5fd5be5 100644
--- a/src/binaryen-c.cpp
+++ b/src/binaryen-c.cpp
@@ -30,6 +30,7 @@
 #include "wasm-builder.h"
 #include "wasm-interpreter.h"
 #include "wasm-s-parser.h"
+#include "wasm-stack.h"
 #include "wasm-validator.h"
 #include "wasm.h"
 #include "wasm2js.h"
@@ -50,67 +51,113 @@ static_assert(sizeof(BinaryenLiteral) == sizeof(Literal),
 BinaryenLiteral toBinaryenLiteral(Literal x) {
   BinaryenLiteral ret;
   ret.type = x.type.getID();
-  TODO_SINGLE_COMPOUND(x.type);
-  switch (x.type.getBasic()) {
-    case Type::i32:
-      ret.i32 = x.geti32();
-      break;
-    case Type::i64:
-      ret.i64 = x.geti64();
-      break;
-    case Type::f32:
-      ret.i32 = x.reinterpreti32();
-      break;
-    case Type::f64:
-      ret.i64 = x.reinterpreti64();
-      break;
-    case Type::v128:
-      memcpy(&ret.v128, x.getv128Ptr(), 16);
-      break;
-    case Type::funcref:
-      ret.func = x.isNull() ? nullptr : x.getFunc().c_str();
-      break;
-    case Type::anyref:
-    case Type::eqref:
-      assert(x.isNull() && "unexpected non-null reference type literal");
-      break;
-    case Type::i31ref:
-      WASM_UNREACHABLE("TODO: i31ref");
-    case Type::dataref:
-      WASM_UNREACHABLE("TODO: dataref");
-    case Type::none:
-    case Type::unreachable:
-      WASM_UNREACHABLE("unexpected type");
+  assert(x.type.isSingle());
+  if (x.type.isBasic()) {
+    switch (x.type.getBasic()) {
+      case Type::i32:
+        ret.i32 = x.geti32();
+        return ret;
+      case Type::i64:
+        ret.i64 = x.geti64();
+        return ret;
+      case Type::f32:
+        ret.i32 = x.reinterpreti32();
+        return ret;
+      case Type::f64:
+        ret.i64 = x.reinterpreti64();
+        return ret;
+      case Type::v128:
+        memcpy(&ret.v128, x.getv128Ptr(), 16);
+        return ret;
+      case Type::none:
+      case Type::unreachable:
+        WASM_UNREACHABLE("unexpected type");
+    }
   }
-  return ret;
+  assert(x.type.isRef());
+  auto heapType = x.type.getHeapType();
+  if (heapType.isBasic()) {
+    switch (heapType.getBasic()) {
+      case HeapType::i31:
+        WASM_UNREACHABLE("TODO: i31");
+      case HeapType::ext:
+      case HeapType::any:
+        WASM_UNREACHABLE("TODO: extern literals");
+      case HeapType::eq:
+      case HeapType::func:
+      case HeapType::data:
+      case HeapType::array:
+        WASM_UNREACHABLE("invalid type");
+      case HeapType::string:
+      case HeapType::stringview_wtf8:
+      case HeapType::stringview_wtf16:
+      case HeapType::stringview_iter:
+        WASM_UNREACHABLE("TODO: string literals");
+      case HeapType::none:
+      case HeapType::noext:
+      case HeapType::nofunc:
+        // Null.
+        return ret;
+    }
+  }
+  if (heapType.isSignature()) {
+    ret.func = x.getFunc().str.data();
+    return ret;
+  }
+  assert(x.isData());
+  WASM_UNREACHABLE("TODO: gc data");
 }
 
 Literal fromBinaryenLiteral(BinaryenLiteral x) {
-  switch (x.type) {
-    case Type::i32:
-      return Literal(x.i32);
-    case Type::i64:
-      return Literal(x.i64);
-    case Type::f32:
-      return Literal(x.i32).castToF32();
-    case Type::f64:
-      return Literal(x.i64).castToF64();
-    case Type::v128:
-      return Literal(x.v128);
-    case Type::funcref:
-      return Literal::makeFunc(x.func);
-    case Type::anyref:
-    case Type::eqref:
-      return Literal::makeNull(Type(x.type));
-    case Type::i31ref:
-      WASM_UNREACHABLE("TODO: i31ref");
-    case Type::dataref:
-      WASM_UNREACHABLE("TODO: dataref");
-    case Type::none:
-    case Type::unreachable:
-      WASM_UNREACHABLE("unexpected type");
+  auto type = Type(x.type);
+  if (type.isBasic()) {
+    switch (type.getBasic()) {
+      case Type::i32:
+        return Literal(x.i32);
+      case Type::i64:
+        return Literal(x.i64);
+      case Type::f32:
+        return Literal(x.i32).castToF32();
+      case Type::f64:
+        return Literal(x.i64).castToF64();
+      case Type::v128:
+        return Literal(x.v128);
+      case Type::none:
+      case Type::unreachable:
+        WASM_UNREACHABLE("unexpected type");
+    }
+  }
+  assert(type.isRef());
+  auto heapType = type.getHeapType();
+  if (heapType.isBasic()) {
+    switch (heapType.getBasic()) {
+      case HeapType::i31:
+        WASM_UNREACHABLE("TODO: i31");
+      case HeapType::ext:
+      case HeapType::any:
+        WASM_UNREACHABLE("TODO: extern literals");
+      case HeapType::eq:
+      case HeapType::func:
+      case HeapType::data:
+      case HeapType::array:
+        WASM_UNREACHABLE("invalid type");
+      case HeapType::string:
+      case HeapType::stringview_wtf8:
+      case HeapType::stringview_wtf16:
+      case HeapType::stringview_iter:
+        WASM_UNREACHABLE("TODO: string literals");
+      case HeapType::none:
+      case HeapType::noext:
+      case HeapType::nofunc:
+        assert(type.isNullable());
+        return Literal::makeNull(heapType);
+    }
+  }
+  if (heapType.isSignature()) {
+    return Literal::makeFunc(Name(x.func), heapType);
   }
-  WASM_UNREACHABLE("invalid type");
+  assert(heapType.isData());
+  WASM_UNREACHABLE("TODO: gc data");
 }
 
 // Mutexes (global for now; in theory if multiple modules
@@ -130,7 +177,6 @@ extern "C" {
 //
 
 // Core types
-// TODO: Deprecate BinaryenTypeExternref?
 
 BinaryenType BinaryenTypeNone(void) { return Type::none; }
 BinaryenType BinaryenTypeInt32(void) { return Type::i32; }
@@ -138,12 +184,48 @@ BinaryenType BinaryenTypeInt64(void) { return Type::i64; }
 BinaryenType BinaryenTypeFloat32(void) { return Type::f32; }
 BinaryenType BinaryenTypeFloat64(void) { return Type::f64; }
 BinaryenType BinaryenTypeVec128(void) { return Type::v128; }
-BinaryenType BinaryenTypeFuncref(void) { return Type::funcref; }
-BinaryenType BinaryenTypeExternref(void) { return Type::anyref; }
-BinaryenType BinaryenTypeAnyref(void) { return Type::anyref; }
-BinaryenType BinaryenTypeEqref(void) { return Type::eqref; }
-BinaryenType BinaryenTypeI31ref(void) { return Type::i31ref; }
-BinaryenType BinaryenTypeDataref(void) { return Type::dataref; }
+BinaryenType BinaryenTypeFuncref(void) {
+  return Type(HeapType::func, Nullable).getID();
+}
+BinaryenType BinaryenTypeExternref(void) {
+  return Type(HeapType::ext, Nullable).getID();
+}
+BinaryenType BinaryenTypeAnyref(void) {
+  return Type(HeapType::any, Nullable).getID();
+}
+BinaryenType BinaryenTypeEqref(void) {
+  return Type(HeapType::eq, Nullable).getID();
+}
+BinaryenType BinaryenTypeI31ref(void) {
+  return Type(HeapType::i31, Nullable).getID();
+}
+BinaryenType BinaryenTypeDataref(void) {
+  return Type(HeapType::data, Nullable).getID();
+}
+BinaryenType BinaryenTypeArrayref(void) {
+  return Type(HeapType::array, Nullable).getID();
+}
+BinaryenType BinaryenTypeStringref() {
+  return Type(HeapType::string, Nullable).getID();
+}
+BinaryenType BinaryenTypeStringviewWTF8() {
+  return Type(HeapType::stringview_wtf8, Nullable).getID();
+}
+BinaryenType BinaryenTypeStringviewWTF16() {
+  return Type(HeapType::stringview_wtf16, Nullable).getID();
+}
+BinaryenType BinaryenTypeStringviewIter() {
+  return Type(HeapType::stringview_iter, Nullable).getID();
+}
+BinaryenType BinaryenTypeNullref() {
+  return Type(HeapType::none, Nullable).getID();
+}
+BinaryenType BinaryenTypeNullExternref(void) {
+  return Type(HeapType::noext, Nullable).getID();
+}
+BinaryenType BinaryenTypeNullFuncref(void) {
+  return Type(HeapType::nofunc, Nullable).getID();
+}
 BinaryenType BinaryenTypeUnreachable(void) { return Type::unreachable; }
 BinaryenType BinaryenTypeAuto(void) { return uintptr_t(-1); }
 
@@ -173,6 +255,175 @@ WASM_DEPRECATED BinaryenType BinaryenFloat32(void) { return Type::f32; }
 WASM_DEPRECATED BinaryenType BinaryenFloat64(void) { return Type::f64; }
 WASM_DEPRECATED BinaryenType BinaryenUndefined(void) { return uint32_t(-1); }
 
+// Packed types
+
+BinaryenPackedType BinaryenPackedTypeNotPacked(void) {
+  return Field::PackedType::not_packed;
+}
+BinaryenPackedType BinaryenPackedTypeInt8(void) {
+  return Field::PackedType::i8;
+}
+BinaryenPackedType BinaryenPackedTypeInt16(void) {
+  return Field::PackedType::i16;
+}
+
+// Heap types
+
+BinaryenHeapType BinaryenHeapTypeExt() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::ext);
+}
+BinaryenHeapType BinaryenHeapTypeFunc() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::func);
+}
+BinaryenHeapType BinaryenHeapTypeAny() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::any);
+}
+BinaryenHeapType BinaryenHeapTypeEq() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::eq);
+}
+BinaryenHeapType BinaryenHeapTypeI31() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::i31);
+}
+BinaryenHeapType BinaryenHeapTypeData() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::data);
+}
+BinaryenHeapType BinaryenHeapTypeArray() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::array);
+}
+BinaryenHeapType BinaryenHeapTypeString() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::string);
+}
+BinaryenHeapType BinaryenHeapTypeStringviewWTF8() {
+  return static_cast<BinaryenHeapType>(
+    HeapType::BasicHeapType::stringview_wtf8);
+}
+BinaryenHeapType BinaryenHeapTypeStringviewWTF16() {
+  return static_cast<BinaryenHeapType>(
+    HeapType::BasicHeapType::stringview_wtf16);
+}
+BinaryenHeapType BinaryenHeapTypeStringviewIter() {
+  return static_cast<BinaryenHeapType>(
+    HeapType::BasicHeapType::stringview_iter);
+}
+BinaryenHeapType BinaryenHeapTypeNone() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::none);
+}
+BinaryenHeapType BinaryenHeapTypeNoext() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::noext);
+}
+BinaryenHeapType BinaryenHeapTypeNofunc() {
+  return static_cast<BinaryenHeapType>(HeapType::BasicHeapType::nofunc);
+}
+
+bool BinaryenHeapTypeIsBasic(BinaryenHeapType heapType) {
+  return HeapType(heapType).isBasic();
+}
+bool BinaryenHeapTypeIsSignature(BinaryenHeapType heapType) {
+  return HeapType(heapType).isSignature();
+}
+bool BinaryenHeapTypeIsStruct(BinaryenHeapType heapType) {
+  return HeapType(heapType).isStruct();
+}
+bool BinaryenHeapTypeIsArray(BinaryenHeapType heapType) {
+  return HeapType(heapType).isArray();
+}
+bool BinaryenHeapTypeIsBottom(BinaryenHeapType heapType) {
+  return HeapType(heapType).isBottom();
+}
+BinaryenHeapType BinaryenHeapTypeGetBottom(BinaryenHeapType heapType) {
+  return static_cast<BinaryenHeapType>(HeapType(heapType).getBottom());
+}
+bool BinaryenHeapTypeIsSubType(BinaryenHeapType left, BinaryenHeapType right) {
+  return HeapType::isSubType(HeapType(left), HeapType(right));
+}
+BinaryenIndex BinaryenStructTypeGetNumFields(BinaryenHeapType heapType) {
+  auto ht = HeapType(heapType);
+  assert(ht.isStruct());
+  return ht.getStruct().fields.size();
+}
+BinaryenType BinaryenStructTypeGetFieldType(BinaryenHeapType heapType,
+                                            BinaryenIndex index) {
+  auto ht = HeapType(heapType);
+  assert(ht.isStruct());
+  auto& fields = ht.getStruct().fields;
+  assert(index < fields.size());
+  return fields[index].type.getID();
+}
+BinaryenPackedType
+BinaryenStructTypeGetFieldPackedType(BinaryenHeapType heapType,
+                                     BinaryenIndex index) {
+  auto ht = HeapType(heapType);
+  assert(ht.isStruct());
+  auto& fields = ht.getStruct().fields;
+  assert(index < fields.size());
+  return fields[index].packedType;
+}
+bool BinaryenStructTypeIsFieldMutable(BinaryenHeapType heapType,
+                                      BinaryenIndex index) {
+  auto ht = HeapType(heapType);
+  assert(ht.isStruct());
+  auto& fields = ht.getStruct().fields;
+  assert(index < fields.size());
+  return fields[index].mutable_;
+}
+BinaryenType BinaryenArrayTypeGetElementType(BinaryenHeapType heapType) {
+  auto ht = HeapType(heapType);
+  assert(ht.isArray());
+  return ht.getArray().element.type.getID();
+}
+BinaryenPackedType
+BinaryenArrayTypeGetElementPackedType(BinaryenHeapType heapType) {
+  auto ht = HeapType(heapType);
+  assert(ht.isArray());
+  return ht.getArray().element.packedType;
+}
+bool BinaryenArrayTypeIsElementMutable(BinaryenHeapType heapType) {
+  auto ht = HeapType(heapType);
+  assert(ht.isArray());
+  return ht.getArray().element.mutable_;
+}
+BinaryenType BinaryenSignatureTypeGetParams(BinaryenHeapType heapType) {
+  auto ht = HeapType(heapType);
+  assert(ht.isSignature());
+  return ht.getSignature().params.getID();
+}
+BinaryenType BinaryenSignatureTypeGetResults(BinaryenHeapType heapType) {
+  auto ht = HeapType(heapType);
+  assert(ht.isSignature());
+  return ht.getSignature().results.getID();
+}
+
+BinaryenHeapType BinaryenTypeGetHeapType(BinaryenType type) {
+  return Type(type).getHeapType().getID();
+}
+bool BinaryenTypeIsNullable(BinaryenType type) {
+  return Type(type).isNullable();
+}
+BinaryenType BinaryenTypeFromHeapType(BinaryenHeapType heapType,
+                                      bool nullable) {
+  return Type(HeapType(heapType),
+              nullable ? Nullability::Nullable : Nullability::NonNullable)
+    .getID();
+}
+
+// TypeSystem
+
+BinaryenTypeSystem BinaryenTypeSystemEquirecursive() {
+  return static_cast<BinaryenTypeSystem>(TypeSystem::Equirecursive);
+}
+BinaryenTypeSystem BinaryenTypeSystemNominal() {
+  return static_cast<BinaryenTypeSystem>(TypeSystem::Nominal);
+}
+BinaryenTypeSystem BinaryenTypeSystemIsorecursive() {
+  return static_cast<BinaryenTypeSystem>(TypeSystem::Isorecursive);
+}
+BinaryenTypeSystem BinaryenGetTypeSystem() {
+  return BinaryenTypeSystem(getTypeSystem());
+}
+void BinaryenSetTypeSystem(BinaryenTypeSystem typeSystem) {
+  setTypeSystem(TypeSystem(typeSystem));
+}
+
 // Expression ids
 
 BinaryenExpressionId BinaryenInvalidId(void) {
@@ -245,15 +496,18 @@ BinaryenFeatures BinaryenFeatureGC(void) {
 BinaryenFeatures BinaryenFeatureMemory64(void) {
   return static_cast<BinaryenFeatures>(FeatureSet::Memory64);
 }
-BinaryenFeatures BinaryenFeatureTypedFunctionReferences(void) {
-  return static_cast<BinaryenFeatures>(FeatureSet::TypedFunctionReferences);
-}
 BinaryenFeatures BinaryenFeatureRelaxedSIMD(void) {
   return static_cast<BinaryenFeatures>(FeatureSet::RelaxedSIMD);
 }
 BinaryenFeatures BinaryenFeatureExtendedConst(void) {
   return static_cast<BinaryenFeatures>(FeatureSet::ExtendedConst);
 }
+BinaryenFeatures BinaryenFeatureStrings(void) {
+  return static_cast<BinaryenFeatures>(FeatureSet::Strings);
+}
+BinaryenFeatures BinaryenFeatureMultiMemories(void) {
+  return static_cast<BinaryenFeatures>(FeatureSet::MultiMemories);
+}
 BinaryenFeatures BinaryenFeatureAll(void) {
   return static_cast<BinaryenFeatures>(FeatureSet::All);
 }
@@ -761,10 +1015,52 @@ BinaryenOp BinaryenRefIsNull(void) { return RefIsNull; }
 BinaryenOp BinaryenRefIsFunc(void) { return RefIsFunc; }
 BinaryenOp BinaryenRefIsData(void) { return RefIsData; }
 BinaryenOp BinaryenRefIsI31(void) { return RefIsI31; }
-BinaryenOp BinaryenRefAsNonNull(void) { return RefAsNonNull; };
+BinaryenOp BinaryenRefAsNonNull(void) { return RefAsNonNull; }
 BinaryenOp BinaryenRefAsFunc(void) { return RefAsFunc; }
-BinaryenOp BinaryenRefAsData(void) { return RefAsData; };
-BinaryenOp BinaryenRefAsI31(void) { return RefAsI31; };
+BinaryenOp BinaryenRefAsData(void) { return RefAsData; }
+BinaryenOp BinaryenRefAsI31(void) { return RefAsI31; }
+BinaryenOp BinaryenRefAsExternInternalize(void) { return ExternInternalize; }
+BinaryenOp BinaryenRefAsExternExternalize(void) { return ExternExternalize; }
+BinaryenOp BinaryenBrOnNull(void) { return BrOnNull; }
+BinaryenOp BinaryenBrOnNonNull(void) { return BrOnNonNull; }
+BinaryenOp BinaryenBrOnCast(void) { return BrOnCast; }
+BinaryenOp BinaryenBrOnCastFail(void) { return BrOnCastFail; };
+BinaryenOp BinaryenBrOnFunc(void) { return BrOnFunc; }
+BinaryenOp BinaryenBrOnNonFunc(void) { return BrOnNonFunc; }
+BinaryenOp BinaryenBrOnData(void) { return BrOnData; }
+BinaryenOp BinaryenBrOnNonData(void) { return BrOnNonData; }
+BinaryenOp BinaryenBrOnI31(void) { return BrOnI31; }
+BinaryenOp BinaryenBrOnNonI31(void) { return BrOnNonI31; }
+BinaryenOp BinaryenStringNewUTF8(void) { return StringNewUTF8; }
+BinaryenOp BinaryenStringNewWTF8(void) { return StringNewWTF8; }
+BinaryenOp BinaryenStringNewReplace(void) { return StringNewReplace; }
+BinaryenOp BinaryenStringNewWTF16(void) { return StringNewWTF16; }
+BinaryenOp BinaryenStringNewUTF8Array(void) { return StringNewUTF8Array; }
+BinaryenOp BinaryenStringNewWTF8Array(void) { return StringNewWTF8Array; }
+BinaryenOp BinaryenStringNewReplaceArray(void) { return StringNewReplaceArray; }
+BinaryenOp BinaryenStringNewWTF16Array(void) { return StringNewWTF16Array; }
+BinaryenOp BinaryenStringMeasureUTF8(void) { return StringMeasureUTF8; }
+BinaryenOp BinaryenStringMeasureWTF8(void) { return StringMeasureWTF8; }
+BinaryenOp BinaryenStringMeasureWTF16(void) { return StringMeasureWTF16; }
+BinaryenOp BinaryenStringMeasureIsUSV(void) { return StringMeasureIsUSV; }
+BinaryenOp BinaryenStringMeasureWTF16View(void) {
+  return StringMeasureWTF16View;
+}
+BinaryenOp BinaryenStringEncodeUTF8(void) { return StringEncodeUTF8; }
+BinaryenOp BinaryenStringEncodeWTF8(void) { return StringEncodeWTF8; }
+BinaryenOp BinaryenStringEncodeWTF16(void) { return StringEncodeWTF16; }
+BinaryenOp BinaryenStringEncodeUTF8Array(void) { return StringEncodeUTF8Array; }
+BinaryenOp BinaryenStringEncodeWTF8Array(void) { return StringEncodeWTF8Array; }
+BinaryenOp BinaryenStringEncodeWTF16Array(void) {
+  return StringEncodeWTF16Array;
+}
+BinaryenOp BinaryenStringAsWTF8(void) { return StringAsWTF8; }
+BinaryenOp BinaryenStringAsWTF16(void) { return StringAsWTF16; }
+BinaryenOp BinaryenStringAsIter(void) { return StringAsIter; }
+BinaryenOp BinaryenStringIterMoveAdvance(void) { return StringIterMoveAdvance; }
+BinaryenOp BinaryenStringIterMoveRewind(void) { return StringIterMoveRewind; }
+BinaryenOp BinaryenStringSliceWTF8(void) { return StringSliceWTF8; }
+BinaryenOp BinaryenStringSliceWTF16(void) { return StringSliceWTF16; }
 
 BinaryenExpressionRef BinaryenBlock(BinaryenModuleRef module,
                                     const char* name,
@@ -931,20 +1227,38 @@ BinaryenExpressionRef BinaryenGlobalSet(BinaryenModuleRef module,
   return static_cast<Expression*>(
     Builder(*(Module*)module).makeGlobalSet(name, (Expression*)value));
 }
+
+// All memory instructions should pass their memory name parameter through this
+// helper function. It maintains compatibility for when JS calls memory
+// instructions that don't specify a memory name (send null), by assuming the
+// singly defined memory is the intended one. This function takes in the memory
+// name passed to API functions to avoid duplicating the nullptr logic check in
+// each instruction
+static Name getMemoryName(BinaryenModuleRef module, const char* memoryName) {
+  if (memoryName == nullptr && module->memories.size() == 1) {
+    return module->memories[0]->name;
+  }
+
+  return memoryName;
+}
+
 BinaryenExpressionRef BinaryenLoad(BinaryenModuleRef module,
                                    uint32_t bytes,
                                    bool signed_,
                                    uint32_t offset,
                                    uint32_t align,
                                    BinaryenType type,
-                                   BinaryenExpressionRef ptr) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeLoad(bytes,
-                                              !!signed_,
-                                              offset,
-                                              align ? align : bytes,
-                                              (Expression*)ptr,
-                                              Type(type)));
+                                   BinaryenExpressionRef ptr,
+                                   const char* memoryName) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeLoad(bytes,
+                !!signed_,
+                offset,
+                align ? align : bytes,
+                (Expression*)ptr,
+                Type(type),
+                getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenStore(BinaryenModuleRef module,
                                     uint32_t bytes,
@@ -952,14 +1266,17 @@ BinaryenExpressionRef BinaryenStore(BinaryenModuleRef module,
                                     uint32_t align,
                                     BinaryenExpressionRef ptr,
                                     BinaryenExpressionRef value,
-                                    BinaryenType type) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeStore(bytes,
-                                               offset,
-                                               align ? align : bytes,
-                                               (Expression*)ptr,
-                                               (Expression*)value,
-                                               Type(type)));
+                                    BinaryenType type,
+                                    const char* memoryName) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStore(bytes,
+                 offset,
+                 align ? align : bytes,
+                 (Expression*)ptr,
+                 (Expression*)value,
+                 Type(type),
+                 getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenConst(BinaryenModuleRef module,
                                     BinaryenLiteral value) {
@@ -1006,13 +1323,28 @@ BinaryenExpressionRef BinaryenReturn(BinaryenModuleRef module,
   auto* ret = Builder(*(Module*)module).makeReturn((Expression*)value);
   return static_cast<Expression*>(ret);
 }
-BinaryenExpressionRef BinaryenMemorySize(BinaryenModuleRef module) {
-  auto* ret = Builder(*(Module*)module).makeMemorySize();
+
+static Builder::MemoryInfo getMemoryInfo(bool memoryIs64) {
+  return memoryIs64 ? Builder::MemoryInfo::Memory64
+                    : Builder::MemoryInfo::Memory32;
+}
+
+BinaryenExpressionRef BinaryenMemorySize(BinaryenModuleRef module,
+                                         const char* memoryName,
+                                         bool memoryIs64) {
+  auto* ret = Builder(*(Module*)module)
+                .makeMemorySize(getMemoryName(module, memoryName),
+                                getMemoryInfo(memoryIs64));
   return static_cast<Expression*>(ret);
 }
 BinaryenExpressionRef BinaryenMemoryGrow(BinaryenModuleRef module,
-                                         BinaryenExpressionRef delta) {
-  auto* ret = Builder(*(Module*)module).makeMemoryGrow((Expression*)delta);
+                                         BinaryenExpressionRef delta,
+                                         const char* memoryName,
+                                         bool memoryIs64) {
+  auto* ret = Builder(*(Module*)module)
+                .makeMemoryGrow((Expression*)delta,
+                                getMemoryName(module, memoryName),
+                                getMemoryInfo(memoryIs64));
   return static_cast<Expression*>(ret);
 }
 BinaryenExpressionRef BinaryenNop(BinaryenModuleRef module) {
@@ -1025,21 +1357,31 @@ BinaryenExpressionRef BinaryenAtomicLoad(BinaryenModuleRef module,
                                          uint32_t bytes,
                                          uint32_t offset,
                                          BinaryenType type,
-                                         BinaryenExpressionRef ptr) {
+                                         BinaryenExpressionRef ptr,
+                                         const char* memoryName) {
   return static_cast<Expression*>(
     Builder(*(Module*)module)
-      .makeAtomicLoad(bytes, offset, (Expression*)ptr, Type(type)));
+      .makeAtomicLoad(bytes,
+                      offset,
+                      (Expression*)ptr,
+                      Type(type),
+                      getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenAtomicStore(BinaryenModuleRef module,
                                           uint32_t bytes,
                                           uint32_t offset,
                                           BinaryenExpressionRef ptr,
                                           BinaryenExpressionRef value,
-                                          BinaryenType type) {
+                                          BinaryenType type,
+                                          const char* memoryName) {
   return static_cast<Expression*>(
     Builder(*(Module*)module)
-      .makeAtomicStore(
-        bytes, offset, (Expression*)ptr, (Expression*)value, Type(type)));
+      .makeAtomicStore(bytes,
+                       offset,
+                       (Expression*)ptr,
+                       (Expression*)value,
+                       Type(type),
+                       getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenAtomicRMW(BinaryenModuleRef module,
                                         BinaryenOp op,
@@ -1047,14 +1389,17 @@ BinaryenExpressionRef BinaryenAtomicRMW(BinaryenModuleRef module,
                                         BinaryenIndex offset,
                                         BinaryenExpressionRef ptr,
                                         BinaryenExpressionRef value,
-                                        BinaryenType type) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeAtomicRMW(AtomicRMWOp(op),
-                                                   bytes,
-                                                   offset,
-                                                   (Expression*)ptr,
-                                                   (Expression*)value,
-                                                   Type(type)));
+                                        BinaryenType type,
+                                        const char* memoryName) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeAtomicRMW(AtomicRMWOp(op),
+                     bytes,
+                     offset,
+                     (Expression*)ptr,
+                     (Expression*)value,
+                     Type(type),
+                     getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenAtomicCmpxchg(BinaryenModuleRef module,
                                             BinaryenIndex bytes,
@@ -1062,33 +1407,43 @@ BinaryenExpressionRef BinaryenAtomicCmpxchg(BinaryenModuleRef module,
                                             BinaryenExpressionRef ptr,
                                             BinaryenExpressionRef expected,
                                             BinaryenExpressionRef replacement,
-                                            BinaryenType type) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeAtomicCmpxchg(bytes,
-                                                       offset,
-                                                       (Expression*)ptr,
-                                                       (Expression*)expected,
-                                                       (Expression*)replacement,
-                                                       Type(type)));
+                                            BinaryenType type,
+                                            const char* memoryName) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeAtomicCmpxchg(bytes,
+                         offset,
+                         (Expression*)ptr,
+                         (Expression*)expected,
+                         (Expression*)replacement,
+                         Type(type),
+                         getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenAtomicWait(BinaryenModuleRef module,
                                          BinaryenExpressionRef ptr,
                                          BinaryenExpressionRef expected,
                                          BinaryenExpressionRef timeout,
-                                         BinaryenType expectedType) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeAtomicWait((Expression*)ptr,
-                                                    (Expression*)expected,
-                                                    (Expression*)timeout,
-                                                    Type(expectedType),
-                                                    0));
+                                         BinaryenType expectedType,
+                                         const char* memoryName) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeAtomicWait((Expression*)ptr,
+                      (Expression*)expected,
+                      (Expression*)timeout,
+                      Type(expectedType),
+                      0,
+                      getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenAtomicNotify(BinaryenModuleRef module,
                                            BinaryenExpressionRef ptr,
-                                           BinaryenExpressionRef notifyCount) {
+                                           BinaryenExpressionRef notifyCount,
+                                           const char* memoryName) {
   return static_cast<Expression*>(
     Builder(*(Module*)module)
-      .makeAtomicNotify((Expression*)ptr, (Expression*)notifyCount, 0));
+      .makeAtomicNotify((Expression*)ptr,
+                        (Expression*)notifyCount,
+                        0,
+                        getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenAtomicFence(BinaryenModuleRef module) {
   return static_cast<Expression*>(Builder(*(Module*)module).makeAtomicFence());
@@ -1144,11 +1499,15 @@ BinaryenExpressionRef BinaryenSIMDLoad(BinaryenModuleRef module,
                                        BinaryenOp op,
                                        uint32_t offset,
                                        uint32_t align,
-                                       BinaryenExpressionRef ptr) {
+                                       BinaryenExpressionRef ptr,
+                                       const char* memoryName) {
   return static_cast<Expression*>(
     Builder(*(Module*)module)
-      .makeSIMDLoad(
-        SIMDLoadOp(op), Address(offset), Address(align), (Expression*)ptr));
+      .makeSIMDLoad(SIMDLoadOp(op),
+                    Address(offset),
+                    Address(align),
+                    (Expression*)ptr,
+                    getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenSIMDLoadStoreLane(BinaryenModuleRef module,
                                                 BinaryenOp op,
@@ -1156,7 +1515,8 @@ BinaryenExpressionRef BinaryenSIMDLoadStoreLane(BinaryenModuleRef module,
                                                 uint32_t align,
                                                 uint8_t index,
                                                 BinaryenExpressionRef ptr,
-                                                BinaryenExpressionRef vec) {
+                                                BinaryenExpressionRef vec,
+                                                const char* memoryName) {
   return static_cast<Expression*>(
     Builder(*(Module*)module)
       .makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp(op),
@@ -1164,17 +1524,22 @@ BinaryenExpressionRef BinaryenSIMDLoadStoreLane(BinaryenModuleRef module,
                              Address(align),
                              index,
                              (Expression*)ptr,
-                             (Expression*)vec));
+                             (Expression*)vec,
+                             getMemoryName(module, memoryName)));
 }
 BinaryenExpressionRef BinaryenMemoryInit(BinaryenModuleRef module,
                                          uint32_t segment,
                                          BinaryenExpressionRef dest,
                                          BinaryenExpressionRef offset,
-                                         BinaryenExpressionRef size) {
+                                         BinaryenExpressionRef size,
+                                         const char* memoryName) {
   return static_cast<Expression*>(
     Builder(*(Module*)module)
-      .makeMemoryInit(
-        segment, (Expression*)dest, (Expression*)offset, (Expression*)size));
+      .makeMemoryInit(segment,
+                      (Expression*)dest,
+                      (Expression*)offset,
+                      (Expression*)size,
+                      getMemoryName(module, memoryName)));
 }
 
 BinaryenExpressionRef BinaryenDataDrop(BinaryenModuleRef module,
@@ -1186,21 +1551,29 @@ BinaryenExpressionRef BinaryenDataDrop(BinaryenModuleRef module,
 BinaryenExpressionRef BinaryenMemoryCopy(BinaryenModuleRef module,
                                          BinaryenExpressionRef dest,
                                          BinaryenExpressionRef source,
-                                         BinaryenExpressionRef size) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeMemoryCopy((Expression*)dest,
-                                                    (Expression*)source,
-                                                    (Expression*)size));
+                                         BinaryenExpressionRef size,
+                                         const char* destMemory,
+                                         const char* sourceMemory) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeMemoryCopy((Expression*)dest,
+                      (Expression*)source,
+                      (Expression*)size,
+                      getMemoryName(module, destMemory),
+                      getMemoryName(module, sourceMemory)));
 }
 
 BinaryenExpressionRef BinaryenMemoryFill(BinaryenModuleRef module,
                                          BinaryenExpressionRef dest,
                                          BinaryenExpressionRef value,
-                                         BinaryenExpressionRef size) {
-  return static_cast<Expression*>(Builder(*(Module*)module)
-                                    .makeMemoryFill((Expression*)dest,
-                                                    (Expression*)value,
-                                                    (Expression*)size));
+                                         BinaryenExpressionRef size,
+                                         const char* memoryName) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeMemoryFill((Expression*)dest,
+                      (Expression*)value,
+                      (Expression*)size,
+                      getMemoryName(module, memoryName)));
 }
 
 BinaryenExpressionRef BinaryenTupleMake(BinaryenModuleRef module,
@@ -1230,7 +1603,8 @@ BinaryenExpressionRef BinaryenRefNull(BinaryenModuleRef module,
                                       BinaryenType type) {
   Type type_(type);
   assert(type_.isNullable());
-  return static_cast<Expression*>(Builder(*(Module*)module).makeRefNull(type_));
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeRefNull(type_.getHeapType()));
 }
 
 BinaryenExpressionRef BinaryenRefIs(BinaryenModuleRef module,
@@ -1355,19 +1729,234 @@ BinaryenExpressionRef BinaryenI31Get(BinaryenModuleRef module,
   return static_cast<Expression*>(
     Builder(*(Module*)module).makeI31Get((Expression*)i31, signed_ != 0));
 }
-
-// TODO (gc): ref.test
-// TODO (gc): ref.cast
-// TODO (gc): br_on_cast
-// TODO (gc): rtt.canon
-// TODO (gc): rtt.sub
-// TODO (gc): struct.new
-// TODO (gc): struct.get
-// TODO (gc): struct.set
-// TODO (gc): array.new
-// TODO (gc): array.get
-// TODO (gc): array.set
-// TODO (gc): array.len
+BinaryenExpressionRef BinaryenCallRef(BinaryenModuleRef module,
+                                      BinaryenExpressionRef target,
+                                      BinaryenExpressionRef* operands,
+                                      BinaryenIndex numOperands,
+                                      BinaryenType type,
+                                      bool isReturn) {
+  std::vector<Expression*> args;
+  for (BinaryenIndex i = 0; i < numOperands; i++) {
+    args.push_back((Expression*)operands[i]);
+  }
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeCallRef((Expression*)target, args, Type(type), isReturn));
+}
+BinaryenExpressionRef BinaryenRefTest(BinaryenModuleRef module,
+                                      BinaryenExpressionRef ref,
+                                      BinaryenHeapType intendedType) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeRefTest((Expression*)ref, HeapType(intendedType)));
+}
+BinaryenExpressionRef BinaryenRefCast(BinaryenModuleRef module,
+                                      BinaryenExpressionRef ref,
+                                      BinaryenHeapType intendedType) {
+  return static_cast<Expression*>(Builder(*(Module*)module)
+                                    .makeRefCast((Expression*)ref,
+                                                 HeapType(intendedType),
+                                                 RefCast::Safety::Safe));
+}
+BinaryenExpressionRef BinaryenBrOn(BinaryenModuleRef module,
+                                   BinaryenOp op,
+                                   const char* name,
+                                   BinaryenExpressionRef ref,
+                                   BinaryenHeapType intendedType) {
+  Builder builder(*(Module*)module);
+  return static_cast<Expression*>(
+    intendedType ? builder.makeBrOn(
+                     BrOnOp(op), name, (Expression*)ref, HeapType(intendedType))
+                 : builder.makeBrOn(BrOnOp(op), name, (Expression*)ref));
+}
+BinaryenExpressionRef BinaryenStructNew(BinaryenModuleRef module,
+                                        BinaryenExpressionRef* operands,
+                                        BinaryenIndex numOperands,
+                                        BinaryenHeapType type) {
+  std::vector<Expression*> args;
+  for (BinaryenIndex i = 0; i < numOperands; i++) {
+    args.push_back((Expression*)operands[i]);
+  }
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeStructNew(HeapType(type), args));
+}
+BinaryenExpressionRef BinaryenStructGet(BinaryenModuleRef module,
+                                        BinaryenIndex index,
+                                        BinaryenExpressionRef ref,
+                                        BinaryenType type,
+                                        bool signed_) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStructGet(index, (Expression*)ref, Type(type), signed_));
+}
+BinaryenExpressionRef BinaryenStructSet(BinaryenModuleRef module,
+                                        BinaryenIndex index,
+                                        BinaryenExpressionRef ref,
+                                        BinaryenExpressionRef value) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStructSet(index, (Expression*)ref, (Expression*)value));
+}
+BinaryenExpressionRef BinaryenArrayNew(BinaryenModuleRef module,
+                                       BinaryenHeapType type,
+                                       BinaryenExpressionRef size,
+                                       BinaryenExpressionRef init) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeArrayNew(HeapType(type), (Expression*)size, (Expression*)init));
+}
+BinaryenExpressionRef BinaryenArrayInit(BinaryenModuleRef module,
+                                        BinaryenHeapType type,
+                                        BinaryenExpressionRef* values,
+                                        BinaryenIndex numValues) {
+  std::vector<Expression*> vals;
+  for (BinaryenIndex i = 0; i < numValues; i++) {
+    vals.push_back((Expression*)values[i]);
+  }
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeArrayInit(HeapType(type), vals));
+}
+BinaryenExpressionRef BinaryenArrayGet(BinaryenModuleRef module,
+                                       BinaryenExpressionRef ref,
+                                       BinaryenExpressionRef index,
+                                       BinaryenType type,
+                                       bool signed_) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeArrayGet((Expression*)ref, (Expression*)index, Type(type), signed_));
+}
+BinaryenExpressionRef BinaryenArraySet(BinaryenModuleRef module,
+                                       BinaryenExpressionRef ref,
+                                       BinaryenExpressionRef index,
+                                       BinaryenExpressionRef value) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeArraySet((Expression*)ref, (Expression*)index, (Expression*)value));
+}
+BinaryenExpressionRef BinaryenArrayLen(BinaryenModuleRef module,
+                                       BinaryenExpressionRef ref) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeArrayLen((Expression*)ref));
+}
+BinaryenExpressionRef BinaryenArrayCopy(BinaryenModuleRef module,
+                                        BinaryenExpressionRef destRef,
+                                        BinaryenExpressionRef destIndex,
+                                        BinaryenExpressionRef srcRef,
+                                        BinaryenExpressionRef srcIndex,
+                                        BinaryenExpressionRef length) {
+  return static_cast<Expression*>(Builder(*(Module*)module)
+                                    .makeArrayCopy((Expression*)destRef,
+                                                   (Expression*)destIndex,
+                                                   (Expression*)srcRef,
+                                                   (Expression*)srcIndex,
+                                                   (Expression*)length));
+}
+BinaryenExpressionRef BinaryenStringNew(BinaryenModuleRef module,
+                                        BinaryenOp op,
+                                        BinaryenExpressionRef ptr,
+                                        BinaryenExpressionRef length,
+                                        BinaryenExpressionRef start,
+                                        BinaryenExpressionRef end) {
+  Builder builder(*(Module*)module);
+  return static_cast<Expression*>(
+    length ? builder.makeStringNew(
+               StringNewOp(op), (Expression*)ptr, (Expression*)length)
+           : builder.makeStringNew(StringNewOp(op),
+                                   (Expression*)ptr,
+                                   (Expression*)start,
+                                   (Expression*)end));
+}
+BinaryenExpressionRef BinaryenStringConst(BinaryenModuleRef module,
+                                          const char* name) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeStringConst(name));
+}
+BinaryenExpressionRef BinaryenStringMeasure(BinaryenModuleRef module,
+                                            BinaryenOp op,
+                                            BinaryenExpressionRef ref) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStringMeasure(StringMeasureOp(op), (Expression*)ref));
+}
+BinaryenExpressionRef BinaryenStringEncode(BinaryenModuleRef module,
+                                           BinaryenOp op,
+                                           BinaryenExpressionRef ref,
+                                           BinaryenExpressionRef ptr,
+                                           BinaryenExpressionRef start) {
+  return static_cast<Expression*>(Builder(*(Module*)module)
+                                    .makeStringEncode(StringEncodeOp(op),
+                                                      (Expression*)ref,
+                                                      (Expression*)ptr,
+                                                      (Expression*)start));
+}
+BinaryenExpressionRef BinaryenStringConcat(BinaryenModuleRef module,
+                                           BinaryenExpressionRef left,
+                                           BinaryenExpressionRef right) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStringConcat((Expression*)left, (Expression*)right));
+}
+BinaryenExpressionRef BinaryenStringEq(BinaryenModuleRef module,
+                                       BinaryenExpressionRef left,
+                                       BinaryenExpressionRef right) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStringEq((Expression*)left, (Expression*)right));
+}
+BinaryenExpressionRef BinaryenStringAs(BinaryenModuleRef module,
+                                       BinaryenOp op,
+                                       BinaryenExpressionRef ref) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeStringAs(StringAsOp(op), (Expression*)ref));
+}
+BinaryenExpressionRef BinaryenStringWTF8Advance(BinaryenModuleRef module,
+                                                BinaryenExpressionRef ref,
+                                                BinaryenExpressionRef pos,
+                                                BinaryenExpressionRef bytes) {
+  return static_cast<Expression*>(Builder(*(Module*)module)
+                                    .makeStringWTF8Advance((Expression*)ref,
+                                                           (Expression*)pos,
+                                                           (Expression*)bytes));
+}
+BinaryenExpressionRef BinaryenStringWTF16Get(BinaryenModuleRef module,
+                                             BinaryenExpressionRef ref,
+                                             BinaryenExpressionRef pos) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStringWTF16Get((Expression*)ref, (Expression*)pos));
+}
+BinaryenExpressionRef BinaryenStringIterNext(BinaryenModuleRef module,
+                                             BinaryenExpressionRef ref) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module).makeStringIterNext((Expression*)ref));
+}
+BinaryenExpressionRef BinaryenStringIterMove(BinaryenModuleRef module,
+                                             BinaryenOp op,
+                                             BinaryenExpressionRef ref,
+                                             BinaryenExpressionRef num) {
+  return static_cast<Expression*>(Builder(*(Module*)module)
+                                    .makeStringIterMove(StringIterMoveOp(op),
+                                                        (Expression*)ref,
+                                                        (Expression*)num));
+}
+BinaryenExpressionRef BinaryenStringSliceWTF(BinaryenModuleRef module,
+                                             BinaryenOp op,
+                                             BinaryenExpressionRef ref,
+                                             BinaryenExpressionRef start,
+                                             BinaryenExpressionRef end) {
+  return static_cast<Expression*>(Builder(*(Module*)module)
+                                    .makeStringSliceWTF(StringSliceWTFOp(op),
+                                                        (Expression*)ref,
+                                                        (Expression*)start,
+                                                        (Expression*)end));
+}
+BinaryenExpressionRef BinaryenStringSliceIter(BinaryenModuleRef module,
+                                              BinaryenExpressionRef ref,
+                                              BinaryenExpressionRef num) {
+  return static_cast<Expression*>(
+    Builder(*(Module*)module)
+      .makeStringSliceIter((Expression*)ref, (Expression*)num));
+}
 
 // Expression utility
 
@@ -1398,7 +1987,7 @@ BinaryenExpressionRef BinaryenExpressionCopy(BinaryenExpressionRef expr,
 const char* BinaryenBlockGetName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Block>());
-  return static_cast<Block*>(expression)->name.c_str();
+  return static_cast<Block*>(expression)->name.str.data();
 }
 void BinaryenBlockSetName(BinaryenExpressionRef expr, const char* name) {
   auto* expression = (Expression*)expr;
@@ -1493,7 +2082,7 @@ void BinaryenIfSetIfFalse(BinaryenExpressionRef expr,
 const char* BinaryenLoopGetName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Loop>());
-  return static_cast<Loop*>(expression)->name.c_str();
+  return static_cast<Loop*>(expression)->name.str.data();
 }
 void BinaryenLoopSetName(BinaryenExpressionRef expr, const char* name) {
   auto* expression = (Expression*)expr;
@@ -1517,7 +2106,7 @@ void BinaryenLoopSetBody(BinaryenExpressionRef expr,
 const char* BinaryenBreakGetName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Break>());
-  return static_cast<Break*>(expression)->name.c_str();
+  return static_cast<Break*>(expression)->name.str.data();
 }
 void BinaryenBreakSetName(BinaryenExpressionRef expr, const char* name) {
   auto* expression = (Expression*)expr;
@@ -1560,7 +2149,7 @@ const char* BinaryenSwitchGetNameAt(BinaryenExpressionRef expr,
   auto* expression = (Expression*)expr;
   assert(expression->is<Switch>());
   assert(index < static_cast<Switch*>(expression)->targets.size());
-  return static_cast<Switch*>(expression)->targets[index].c_str();
+  return static_cast<Switch*>(expression)->targets[index].str.data();
 }
 void BinaryenSwitchSetNameAt(BinaryenExpressionRef expr,
                              BinaryenIndex index,
@@ -1593,12 +2182,12 @@ const char* BinaryenSwitchRemoveNameAt(BinaryenExpressionRef expr,
                                        BinaryenIndex index) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Switch>());
-  return static_cast<Switch*>(expression)->targets.removeAt(index).c_str();
+  return static_cast<Switch*>(expression)->targets.removeAt(index).str.data();
 }
 const char* BinaryenSwitchGetDefaultName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Switch>());
-  return static_cast<Switch*>(expression)->default_.c_str();
+  return static_cast<Switch*>(expression)->default_.str.data();
 }
 void BinaryenSwitchSetDefaultName(BinaryenExpressionRef expr,
                                   const char* name) {
@@ -1635,7 +2224,7 @@ void BinaryenSwitchSetValue(BinaryenExpressionRef expr,
 const char* BinaryenCallGetTarget(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Call>());
-  return static_cast<Call*>(expression)->target.c_str();
+  return static_cast<Call*>(expression)->target.str.data();
 }
 void BinaryenCallSetTarget(BinaryenExpressionRef expr, const char* target) {
   auto* expression = (Expression*)expr;
@@ -1716,7 +2305,7 @@ void BinaryenCallIndirectSetTarget(BinaryenExpressionRef expr,
 const char* BinaryenCallIndirectGetTable(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<CallIndirect>());
-  return static_cast<CallIndirect*>(expression)->table.c_str();
+  return static_cast<CallIndirect*>(expression)->table.str.data();
 }
 void BinaryenCallIndirectSetTable(BinaryenExpressionRef expr,
                                   const char* table) {
@@ -1856,7 +2445,7 @@ void BinaryenLocalSetSetValue(BinaryenExpressionRef expr,
 const char* BinaryenGlobalGetGetName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<GlobalGet>());
-  return static_cast<GlobalGet*>(expression)->name.c_str();
+  return static_cast<GlobalGet*>(expression)->name.str.data();
 }
 void BinaryenGlobalGetSetName(BinaryenExpressionRef expr, const char* name) {
   auto* expression = (Expression*)expr;
@@ -1868,7 +2457,7 @@ void BinaryenGlobalGetSetName(BinaryenExpressionRef expr, const char* name) {
 const char* BinaryenGlobalSetGetName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<GlobalSet>());
-  return static_cast<GlobalSet*>(expression)->name.c_str();
+  return static_cast<GlobalSet*>(expression)->name.str.data();
 }
 void BinaryenGlobalSetSetName(BinaryenExpressionRef expr, const char* name) {
   auto* expression = (Expression*)expr;
@@ -1892,7 +2481,7 @@ void BinaryenGlobalSetSetValue(BinaryenExpressionRef expr,
 const char* BinaryenTableGetGetTable(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<TableGet>());
-  return static_cast<TableGet*>(expression)->table.c_str();
+  return static_cast<TableGet*>(expression)->table.str.data();
 }
 void BinaryenTableGetSetTable(BinaryenExpressionRef expr, const char* table) {
   auto* expression = (Expression*)expr;
@@ -1916,7 +2505,7 @@ void BinaryenTableGetSetIndex(BinaryenExpressionRef expr,
 const char* BinaryenTableSetGetTable(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<TableSet>());
-  return static_cast<TableSet*>(expression)->table.c_str();
+  return static_cast<TableSet*>(expression)->table.str.data();
 }
 void BinaryenTableSetSetTable(BinaryenExpressionRef expr, const char* table) {
   auto* expression = (Expression*)expr;
@@ -1952,7 +2541,7 @@ void BinaryenTableSetSetValue(BinaryenExpressionRef expr,
 const char* BinaryenTableSizeGetTable(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<TableSize>());
-  return static_cast<TableSize*>(expression)->table.c_str();
+  return static_cast<TableSize*>(expression)->table.str.data();
 }
 void BinaryenTableSizeSetTable(BinaryenExpressionRef expr, const char* table) {
   auto* expression = (Expression*)expr;
@@ -1964,7 +2553,7 @@ void BinaryenTableSizeSetTable(BinaryenExpressionRef expr, const char* table) {
 const char* BinaryenTableGrowGetTable(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<TableGrow>());
-  return static_cast<TableGrow*>(expression)->table.c_str();
+  return static_cast<TableGrow*>(expression)->table.str.data();
 }
 void BinaryenTableGrowSetTable(BinaryenExpressionRef expr, const char* table) {
   auto* expression = (Expression*)expr;
@@ -3053,7 +3642,7 @@ void BinaryenRefAsSetValue(BinaryenExpressionRef expr,
 const char* BinaryenRefFuncGetFunc(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<RefFunc>());
-  return static_cast<RefFunc*>(expression)->func.c_str();
+  return static_cast<RefFunc*>(expression)->func.str.data();
 }
 void BinaryenRefFuncSetFunc(BinaryenExpressionRef expr, const char* funcName) {
   auto* expression = (Expression*)expr;
@@ -3087,7 +3676,7 @@ void BinaryenRefEqSetRight(BinaryenExpressionRef expr,
 const char* BinaryenTryGetName(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Try>());
-  return static_cast<Try*>(expression)->name.c_str();
+  return static_cast<Try*>(expression)->name.str.data();
 }
 void BinaryenTrySetName(BinaryenExpressionRef expr, const char* name) {
   auto* expression = (Expression*)expr;
@@ -3121,7 +3710,7 @@ const char* BinaryenTryGetCatchTagAt(BinaryenExpressionRef expr,
   auto* expression = (Expression*)expr;
   assert(expression->is<Try>());
   assert(index < static_cast<Try*>(expression)->catchTags.size());
-  return static_cast<Try*>(expression)->catchTags[index].c_str();
+  return static_cast<Try*>(expression)->catchTags[index].str.data();
 }
 void BinaryenTrySetCatchTagAt(BinaryenExpressionRef expr,
                               BinaryenIndex index,
@@ -3154,7 +3743,7 @@ const char* BinaryenTryRemoveCatchTagAt(BinaryenExpressionRef expr,
                                         BinaryenIndex index) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Try>());
-  return static_cast<Try*>(expression)->catchTags.removeAt(index).c_str();
+  return static_cast<Try*>(expression)->catchTags.removeAt(index).str.data();
 }
 BinaryenExpressionRef BinaryenTryGetCatchBodyAt(BinaryenExpressionRef expr,
                                                 BinaryenIndex index) {
@@ -3205,7 +3794,7 @@ bool BinaryenTryHasCatchAll(BinaryenExpressionRef expr) {
 const char* BinaryenTryGetDelegateTarget(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Try>());
-  return static_cast<Try*>(expression)->delegateTarget.c_str();
+  return static_cast<Try*>(expression)->delegateTarget.str.data();
 }
 void BinaryenTrySetDelegateTarget(BinaryenExpressionRef expr,
                                   const char* delegateTarget) {
@@ -3222,7 +3811,7 @@ bool BinaryenTryIsDelegate(BinaryenExpressionRef expr) {
 const char* BinaryenThrowGetTag(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Throw>());
-  return static_cast<Throw*>(expression)->tag.c_str();
+  return static_cast<Throw*>(expression)->tag.str.data();
 }
 void BinaryenThrowSetTag(BinaryenExpressionRef expr, const char* tagName) {
   auto* expression = (Expression*)expr;
@@ -3279,7 +3868,7 @@ BinaryenExpressionRef BinaryenThrowRemoveOperandAt(BinaryenExpressionRef expr,
 const char* BinaryenRethrowGetTarget(BinaryenExpressionRef expr) {
   auto* expression = (Expression*)expr;
   assert(expression->is<Rethrow>());
-  return static_cast<Rethrow*>(expression)->target.c_str();
+  return static_cast<Rethrow*>(expression)->target.str.data();
 }
 void BinaryenRethrowSetTarget(BinaryenExpressionRef expr, const char* target) {
   auto* expression = (Expression*)expr;
@@ -3395,63 +3984,967 @@ void BinaryenI31GetSetSigned(BinaryenExpressionRef expr, bool signed_) {
   assert(expression->is<I31Get>());
   static_cast<I31Get*>(expression)->signed_ = signed_ != 0;
 }
-
-// Functions
-
-BinaryenFunctionRef BinaryenAddFunction(BinaryenModuleRef module,
-                                        const char* name,
-                                        BinaryenType params,
-                                        BinaryenType results,
-                                        BinaryenType* varTypes,
-                                        BinaryenIndex numVarTypes,
-                                        BinaryenExpressionRef body) {
-  auto* ret = new Function;
-  ret->setExplicitName(name);
-  // TODO: Take a HeapType rather than params and results.
-  ret->type = Signature(Type(params), Type(results));
-  for (BinaryenIndex i = 0; i < numVarTypes; i++) {
-    ret->vars.push_back(Type(varTypes[i]));
-  }
-  ret->body = (Expression*)body;
-
-  // Lock. This can be called from multiple threads at once, and is a
-  // point where they all access and modify the module.
-  {
-    std::lock_guard<std::mutex> lock(BinaryenFunctionMutex);
-    ((Module*)module)->addFunction(ret);
-  }
-
-  return ret;
+// CallRef
+BinaryenIndex BinaryenCallRefGetNumOperands(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  return static_cast<CallRef*>(expression)->operands.size();
 }
-BinaryenFunctionRef BinaryenGetFunction(BinaryenModuleRef module,
-                                        const char* name) {
-  return ((Module*)module)->getFunctionOrNull(name);
+BinaryenExpressionRef BinaryenCallRefGetOperandAt(BinaryenExpressionRef expr,
+                                                  BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  assert(index < static_cast<CallRef*>(expression)->operands.size());
+  return static_cast<CallRef*>(expression)->operands[index];
 }
-void BinaryenRemoveFunction(BinaryenModuleRef module, const char* name) {
-  ((Module*)module)->removeFunction(name);
+void BinaryenCallRefSetOperandAt(BinaryenExpressionRef expr,
+                                 BinaryenIndex index,
+                                 BinaryenExpressionRef operandExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  assert(index < static_cast<CallRef*>(expression)->operands.size());
+  assert(operandExpr);
+  static_cast<CallRef*>(expression)->operands[index] = (Expression*)operandExpr;
 }
-BinaryenIndex BinaryenGetNumFunctions(BinaryenModuleRef module) {
-  return ((Module*)module)->functions.size();
+BinaryenIndex BinaryenCallRefAppendOperand(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef operandExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  assert(operandExpr);
+  auto& list = static_cast<CallRef*>(expression)->operands;
+  auto index = list.size();
+  list.push_back((Expression*)operandExpr);
+  return index;
 }
-BinaryenFunctionRef BinaryenGetFunctionByIndex(BinaryenModuleRef module,
-                                               BinaryenIndex index) {
-  const auto& functions = ((Module*)module)->functions;
-  if (functions.size() <= index) {
-    Fatal() << "invalid function index.";
-  }
-  return functions[index].get();
+void BinaryenCallRefInsertOperandAt(BinaryenExpressionRef expr,
+                                    BinaryenIndex index,
+                                    BinaryenExpressionRef operandExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  assert(operandExpr);
+  static_cast<CallRef*>(expression)
+    ->operands.insertAt(index, (Expression*)operandExpr);
 }
-
-// Globals
-
-BinaryenGlobalRef BinaryenAddGlobal(BinaryenModuleRef module,
-                                    const char* name,
-                                    BinaryenType type,
-                                    bool mutable_,
-                                    BinaryenExpressionRef init) {
-  auto* ret = new Global();
-  ret->setExplicitName(name);
-  ret->type = Type(type);
+BinaryenExpressionRef BinaryenCallRefRemoveOperandAt(BinaryenExpressionRef expr,
+                                                     BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  return static_cast<CallRef*>(expression)->operands.removeAt(index);
+}
+BinaryenExpressionRef BinaryenCallRefGetTarget(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  return static_cast<CallRef*>(expression)->target;
+}
+void BinaryenCallRefSetTarget(BinaryenExpressionRef expr,
+                              BinaryenExpressionRef targetExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  assert(targetExpr);
+  static_cast<CallRef*>(expression)->target = (Expression*)targetExpr;
+}
+bool BinaryenCallRefIsReturn(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  return static_cast<CallRef*>(expression)->isReturn;
+}
+void BinaryenCallRefSetReturn(BinaryenExpressionRef expr, bool isReturn) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<CallRef>());
+  static_cast<CallRef*>(expression)->isReturn = isReturn;
+}
+// RefTest
+BinaryenExpressionRef BinaryenRefTestGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefTest>());
+  return static_cast<RefTest*>(expression)->ref;
+}
+void BinaryenRefTestSetRef(BinaryenExpressionRef expr,
+                           BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefTest>());
+  assert(refExpr);
+  static_cast<RefTest*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenHeapType BinaryenRefTestGetIntendedType(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefTest>());
+  return static_cast<RefTest*>(expression)->intendedType.getID();
+}
+void BinaryenRefTestSetIntendedType(BinaryenExpressionRef expr,
+                                    BinaryenHeapType intendedType) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefTest>());
+  static_cast<RefTest*>(expression)->intendedType = HeapType(intendedType);
+}
+// RefCast
+BinaryenExpressionRef BinaryenRefCastGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefCast>());
+  return static_cast<RefCast*>(expression)->ref;
+}
+void BinaryenRefCastSetRef(BinaryenExpressionRef expr,
+                           BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefCast>());
+  assert(refExpr);
+  static_cast<RefCast*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenHeapType BinaryenRefCastGetIntendedType(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefCast>());
+  return static_cast<RefCast*>(expression)->intendedType.getID();
+}
+void BinaryenRefCastSetIntendedType(BinaryenExpressionRef expr,
+                                    BinaryenHeapType intendedType) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<RefCast>());
+  static_cast<RefCast*>(expression)->intendedType = HeapType(intendedType);
+}
+// BrOn
+BinaryenOp BinaryenBrOnGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  return static_cast<BrOn*>(expression)->op;
+}
+void BinaryenBrOnSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  static_cast<BrOn*>(expression)->op = BrOnOp(op);
+}
+const char* BinaryenBrOnGetName(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  return static_cast<BrOn*>(expression)->name.str.data();
+}
+void BinaryenBrOnSetName(BinaryenExpressionRef expr, const char* nameStr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  assert(nameStr);
+  static_cast<BrOn*>(expression)->name = nameStr;
+}
+BinaryenExpressionRef BinaryenBrOnGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  return static_cast<BrOn*>(expression)->ref;
+}
+void BinaryenBrOnSetRef(BinaryenExpressionRef expr,
+                        BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  assert(refExpr);
+  static_cast<BrOn*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenHeapType BinaryenBrOnGetIntendedType(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  return static_cast<BrOn*>(expression)->intendedType.getID();
+}
+void BinaryenBrOnSetIntendedType(BinaryenExpressionRef expr,
+                                 BinaryenHeapType intendedType) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<BrOn>());
+  static_cast<BrOn*>(expression)->intendedType = HeapType(intendedType);
+}
+// StructNew
+BinaryenIndex BinaryenStructNewGetNumOperands(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructNew>());
+  return static_cast<StructNew*>(expression)->operands.size();
+}
+BinaryenExpressionRef BinaryenStructNewGetOperandAt(BinaryenExpressionRef expr,
+                                                    BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructNew>());
+  assert(index < static_cast<StructNew*>(expression)->operands.size());
+  return static_cast<StructNew*>(expression)->operands[index];
+}
+void BinaryenStructNewSetOperandAt(BinaryenExpressionRef expr,
+                                   BinaryenIndex index,
+                                   BinaryenExpressionRef operandExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructNew>());
+  assert(index < static_cast<StructNew*>(expression)->operands.size());
+  assert(operandExpr);
+  static_cast<StructNew*>(expression)->operands[index] =
+    (Expression*)operandExpr;
+}
+BinaryenIndex
+BinaryenStructNewAppendOperand(BinaryenExpressionRef expr,
+                               BinaryenExpressionRef operandExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructNew>());
+  assert(operandExpr);
+  auto& list = static_cast<StructNew*>(expression)->operands;
+  auto index = list.size();
+  list.push_back((Expression*)operandExpr);
+  return index;
+}
+void BinaryenStructNewInsertOperandAt(BinaryenExpressionRef expr,
+                                      BinaryenIndex index,
+                                      BinaryenExpressionRef operandExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructNew>());
+  assert(operandExpr);
+  static_cast<StructNew*>(expression)
+    ->operands.insertAt(index, (Expression*)operandExpr);
+}
+BinaryenExpressionRef
+BinaryenStructNewRemoveOperandAt(BinaryenExpressionRef expr,
+                                 BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructNew>());
+  return static_cast<StructNew*>(expression)->operands.removeAt(index);
+}
+// StructGet
+BinaryenIndex BinaryenStructGetGetIndex(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructGet>());
+  return static_cast<StructGet*>(expression)->index;
+}
+void BinaryenStructGetSetIndex(BinaryenExpressionRef expr,
+                               BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructGet>());
+  static_cast<StructGet*>(expression)->index = index;
+}
+BinaryenExpressionRef BinaryenStructGetGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructGet>());
+  return static_cast<StructGet*>(expression)->ref;
+}
+void BinaryenStructGetSetRef(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructGet>());
+  assert(refExpr);
+  static_cast<StructGet*>(expression)->ref = (Expression*)refExpr;
+}
+bool BinaryenStructGetIsSigned(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructGet>());
+  return static_cast<StructGet*>(expression)->signed_;
+}
+void BinaryenStructGetSetSigned(BinaryenExpressionRef expr, bool signed_) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructGet>());
+  static_cast<StructGet*>(expression)->signed_ = signed_;
+}
+// StructSet
+BinaryenIndex BinaryenStructSetGetIndex(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructSet>());
+  return static_cast<StructSet*>(expression)->index;
+}
+void BinaryenStructSetSetIndex(BinaryenExpressionRef expr,
+                               BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructSet>());
+  static_cast<StructSet*>(expression)->index = index;
+}
+BinaryenExpressionRef BinaryenStructSetGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructSet>());
+  return static_cast<StructSet*>(expression)->ref;
+}
+void BinaryenStructSetSetRef(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructSet>());
+  assert(refExpr);
+  static_cast<StructSet*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef BinaryenStructSetGetValue(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructSet>());
+  return static_cast<StructSet*>(expression)->value;
+}
+void BinaryenStructSetSetValue(BinaryenExpressionRef expr,
+                               BinaryenExpressionRef valueExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StructSet>());
+  assert(valueExpr);
+  static_cast<StructSet*>(expression)->value = (Expression*)valueExpr;
+}
+// ArrayNew
+BinaryenExpressionRef BinaryenArrayNewGetInit(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayNew>());
+  return static_cast<ArrayNew*>(expression)->init;
+}
+void BinaryenArrayNewSetInit(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef initExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayNew>());
+  // may be null
+  static_cast<ArrayNew*>(expression)->init = (Expression*)initExpr;
+}
+BinaryenExpressionRef BinaryenArrayNewGetSize(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayNew>());
+  return static_cast<ArrayNew*>(expression)->size;
+}
+void BinaryenArrayNewSetSize(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef sizeExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayNew>());
+  assert(sizeExpr);
+  static_cast<ArrayNew*>(expression)->size = (Expression*)sizeExpr;
+}
+// ArrayInit
+BinaryenIndex BinaryenArrayInitGetNumValues(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayInit>());
+  return static_cast<ArrayInit*>(expression)->values.size();
+}
+BinaryenExpressionRef BinaryenArrayInitGetValueAt(BinaryenExpressionRef expr,
+                                                  BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayInit>());
+  assert(index < static_cast<ArrayInit*>(expression)->values.size());
+  return static_cast<ArrayInit*>(expression)->values[index];
+}
+void BinaryenArrayInitSetValueAt(BinaryenExpressionRef expr,
+                                 BinaryenIndex index,
+                                 BinaryenExpressionRef valueExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayInit>());
+  assert(index < static_cast<ArrayInit*>(expression)->values.size());
+  assert(valueExpr);
+  static_cast<ArrayInit*>(expression)->values[index] = (Expression*)valueExpr;
+}
+BinaryenIndex BinaryenArrayInitAppendValue(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef valueExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayInit>());
+  assert(valueExpr);
+  auto& list = static_cast<ArrayInit*>(expression)->values;
+  auto index = list.size();
+  list.push_back((Expression*)valueExpr);
+  return index;
+}
+void BinaryenArrayInitInsertValueAt(BinaryenExpressionRef expr,
+                                    BinaryenIndex index,
+                                    BinaryenExpressionRef valueExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayInit>());
+  assert(valueExpr);
+  static_cast<ArrayInit*>(expression)
+    ->values.insertAt(index, (Expression*)valueExpr);
+}
+BinaryenExpressionRef BinaryenArrayInitRemoveValueAt(BinaryenExpressionRef expr,
+                                                     BinaryenIndex index) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayInit>());
+  return static_cast<ArrayInit*>(expression)->values.removeAt(index);
+}
+// ArrayGet
+BinaryenExpressionRef BinaryenArrayGetGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayGet>());
+  return static_cast<ArrayGet*>(expression)->ref;
+}
+void BinaryenArrayGetSetRef(BinaryenExpressionRef expr,
+                            BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayGet>());
+  assert(refExpr);
+  static_cast<ArrayGet*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef BinaryenArrayGetGetIndex(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayGet>());
+  return static_cast<ArrayGet*>(expression)->index;
+}
+void BinaryenArrayGetSetIndex(BinaryenExpressionRef expr,
+                              BinaryenExpressionRef indexExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayGet>());
+  assert(indexExpr);
+  static_cast<ArrayGet*>(expression)->index = (Expression*)indexExpr;
+}
+bool BinaryenArrayGetIsSigned(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayGet>());
+  return static_cast<ArrayGet*>(expression)->signed_;
+}
+void BinaryenArrayGetSetSigned(BinaryenExpressionRef expr, bool signed_) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayGet>());
+  static_cast<ArrayGet*>(expression)->signed_ = signed_;
+}
+// ArraySet
+BinaryenExpressionRef BinaryenArraySetGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArraySet>());
+  return static_cast<ArraySet*>(expression)->ref;
+}
+void BinaryenArraySetSetRef(BinaryenExpressionRef expr,
+                            BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArraySet>());
+  assert(refExpr);
+  static_cast<ArraySet*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef BinaryenArraySetGetIndex(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArraySet>());
+  return static_cast<ArraySet*>(expression)->index;
+}
+void BinaryenArraySetSetIndex(BinaryenExpressionRef expr,
+                              BinaryenExpressionRef indexExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArraySet>());
+  assert(indexExpr);
+  static_cast<ArraySet*>(expression)->index = (Expression*)indexExpr;
+}
+BinaryenExpressionRef BinaryenArraySetGetValue(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArraySet>());
+  return static_cast<ArraySet*>(expression)->value;
+}
+void BinaryenArraySetSetValue(BinaryenExpressionRef expr,
+                              BinaryenExpressionRef valueExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArraySet>());
+  assert(valueExpr);
+  static_cast<ArraySet*>(expression)->value = (Expression*)valueExpr;
+}
+// ArrayLen
+BinaryenExpressionRef BinaryenArrayLenGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayLen>());
+  return static_cast<ArrayLen*>(expression)->ref;
+}
+void BinaryenArrayLenSetRef(BinaryenExpressionRef expr,
+                            BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayLen>());
+  assert(refExpr);
+  static_cast<ArrayLen*>(expression)->ref = (Expression*)refExpr;
+}
+// ArrayCopy
+BinaryenExpressionRef BinaryenArrayCopyGetDestRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  return static_cast<ArrayCopy*>(expression)->destRef;
+}
+void BinaryenArrayCopySetDestRef(BinaryenExpressionRef expr,
+                                 BinaryenExpressionRef destRefExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  assert(destRefExpr);
+  static_cast<ArrayCopy*>(expression)->destRef = (Expression*)destRefExpr;
+}
+BinaryenExpressionRef
+BinaryenArrayCopyGetDestIndex(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  return static_cast<ArrayCopy*>(expression)->destIndex;
+}
+void BinaryenArrayCopySetDestIndex(BinaryenExpressionRef expr,
+                                   BinaryenExpressionRef destIndexExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  assert(destIndexExpr);
+  static_cast<ArrayCopy*>(expression)->destIndex = (Expression*)destIndexExpr;
+}
+BinaryenExpressionRef BinaryenArrayCopyGetSrcRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  return static_cast<ArrayCopy*>(expression)->srcRef;
+}
+void BinaryenArrayCopySetSrcRef(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef srcRefExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  assert(srcRefExpr);
+  static_cast<ArrayCopy*>(expression)->srcRef = (Expression*)srcRefExpr;
+}
+BinaryenExpressionRef BinaryenArrayCopyGetSrcIndex(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  return static_cast<ArrayCopy*>(expression)->srcIndex;
+}
+void BinaryenArrayCopySetSrcIndex(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef srcIndexExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  assert(srcIndexExpr);
+  static_cast<ArrayCopy*>(expression)->srcIndex = (Expression*)srcIndexExpr;
+}
+BinaryenExpressionRef BinaryenArrayCopyGetLength(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  return static_cast<ArrayCopy*>(expression)->length;
+}
+void BinaryenArrayCopySetLength(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef lengthExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<ArrayCopy>());
+  assert(lengthExpr);
+  static_cast<ArrayCopy*>(expression)->length = (Expression*)lengthExpr;
+}
+// StringNew
+BinaryenOp BinaryenStringNewGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  return static_cast<StringNew*>(expression)->op;
+}
+void BinaryenStringNewSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  static_cast<StringNew*>(expression)->op = StringNewOp(op);
+}
+BinaryenExpressionRef BinaryenStringNewGetPtr(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  return static_cast<StringNew*>(expression)->ptr;
+}
+void BinaryenStringNewSetPtr(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef ptrExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  assert(ptrExpr);
+  static_cast<StringNew*>(expression)->ptr = (Expression*)ptrExpr;
+}
+BinaryenExpressionRef BinaryenStringNewGetLength(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  return static_cast<StringNew*>(expression)->length;
+}
+void BinaryenStringNewSetLength(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef lengthExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  // may be null (linear memory only)
+  static_cast<StringNew*>(expression)->length = (Expression*)lengthExpr;
+}
+BinaryenExpressionRef BinaryenStringNewGetStart(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  return static_cast<StringNew*>(expression)->start;
+}
+void BinaryenStringNewSetStart(BinaryenExpressionRef expr,
+                               BinaryenExpressionRef startExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  // may be null (GC only)
+  static_cast<StringNew*>(expression)->start = (Expression*)startExpr;
+}
+BinaryenExpressionRef BinaryenStringNewGetEnd(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  return static_cast<StringNew*>(expression)->end;
+}
+void BinaryenStringNewSetEnd(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef endExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringNew>());
+  // may be null (GC only)
+  static_cast<StringNew*>(expression)->end = (Expression*)endExpr;
+}
+// StringConst
+const char* BinaryenStringConstGetString(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringConst>());
+  return static_cast<StringConst*>(expression)->string.str.data();
+}
+void BinaryenStringConstSetString(BinaryenExpressionRef expr,
+                                  const char* stringStr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringConst>());
+  assert(stringStr);
+  static_cast<StringConst*>(expression)->string = stringStr;
+}
+// StringMeasure
+BinaryenOp BinaryenStringMeasureGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringMeasure>());
+  return static_cast<StringMeasure*>(expression)->op;
+}
+void BinaryenStringMeasureSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringMeasure>());
+  static_cast<StringMeasure*>(expression)->op = StringMeasureOp(op);
+}
+BinaryenExpressionRef BinaryenStringMeasureGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringMeasure>());
+  return static_cast<StringMeasure*>(expression)->ref;
+}
+void BinaryenStringMeasureSetRef(BinaryenExpressionRef expr,
+                                 BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringMeasure>());
+  assert(refExpr);
+  static_cast<StringMeasure*>(expression)->ref = (Expression*)refExpr;
+}
+// StringEncode
+BinaryenOp BinaryenStringEncodeGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  return static_cast<StringEncode*>(expression)->op;
+}
+void BinaryenStringEncodeSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  static_cast<StringEncode*>(expression)->op = StringEncodeOp(op);
+}
+BinaryenExpressionRef BinaryenStringEncodeGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  return static_cast<StringEncode*>(expression)->ref;
+}
+void BinaryenStringEncodeSetRef(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  assert(refExpr);
+  static_cast<StringEncode*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef BinaryenStringEncodeGetPtr(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  return static_cast<StringEncode*>(expression)->ptr;
+}
+void BinaryenStringEncodeSetPtr(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef ptrExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  assert(ptrExpr);
+  static_cast<StringEncode*>(expression)->ptr = (Expression*)ptrExpr;
+}
+BinaryenExpressionRef BinaryenStringEncodeGetStart(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  return static_cast<StringEncode*>(expression)->start;
+}
+void BinaryenStringEncodeSetStart(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef startExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEncode>());
+  // may be null (GC only)
+  static_cast<StringEncode*>(expression)->start = (Expression*)startExpr;
+}
+// StringConcat
+BinaryenExpressionRef BinaryenStringConcatGetLeft(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringConcat>());
+  return static_cast<StringConcat*>(expression)->left;
+}
+void BinaryenStringConcatSetLeft(BinaryenExpressionRef expr,
+                                 BinaryenExpressionRef leftExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringConcat>());
+  assert(leftExpr);
+  static_cast<StringConcat*>(expression)->left = (Expression*)leftExpr;
+}
+BinaryenExpressionRef BinaryenStringConcatGetRight(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringConcat>());
+  return static_cast<StringConcat*>(expression)->right;
+}
+void BinaryenStringConcatSetRight(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef rightExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringConcat>());
+  assert(rightExpr);
+  static_cast<StringConcat*>(expression)->right = (Expression*)rightExpr;
+}
+// StringEq
+BinaryenExpressionRef BinaryenStringEqGetLeft(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEq>());
+  return static_cast<StringEq*>(expression)->left;
+}
+void BinaryenStringEqSetLeft(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef leftExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEq>());
+  assert(leftExpr);
+  static_cast<StringEq*>(expression)->left = (Expression*)leftExpr;
+}
+BinaryenExpressionRef BinaryenStringEqGetRight(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEq>());
+  return static_cast<StringEq*>(expression)->right;
+}
+void BinaryenStringEqSetRight(BinaryenExpressionRef expr,
+                              BinaryenExpressionRef rightExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringEq>());
+  assert(rightExpr);
+  static_cast<StringEq*>(expression)->right = (Expression*)rightExpr;
+}
+// StringAs
+BinaryenOp BinaryenStringAsGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringAs>());
+  return static_cast<StringAs*>(expression)->op;
+}
+void BinaryenStringAsSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringAs>());
+  static_cast<StringAs*>(expression)->op = StringAsOp(op);
+}
+BinaryenExpressionRef BinaryenStringAsGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringAs>());
+  return static_cast<StringAs*>(expression)->ref;
+}
+void BinaryenStringAsSetRef(BinaryenExpressionRef expr,
+                            BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringAs>());
+  assert(refExpr);
+  static_cast<StringAs*>(expression)->ref = (Expression*)refExpr;
+}
+// StringWTF8Advance
+BinaryenExpressionRef
+BinaryenStringWTF8AdvanceGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF8Advance>());
+  return static_cast<StringWTF8Advance*>(expression)->ref;
+}
+void BinaryenStringWTF8AdvanceSetRef(BinaryenExpressionRef expr,
+                                     BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF8Advance>());
+  assert(refExpr);
+  static_cast<StringWTF8Advance*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef
+BinaryenStringWTF8AdvanceGetPos(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF8Advance>());
+  return static_cast<StringWTF8Advance*>(expression)->pos;
+}
+void BinaryenStringWTF8AdvanceSetPos(BinaryenExpressionRef expr,
+                                     BinaryenExpressionRef posExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF8Advance>());
+  assert(posExpr);
+  static_cast<StringWTF8Advance*>(expression)->pos = (Expression*)posExpr;
+}
+BinaryenExpressionRef
+BinaryenStringWTF8AdvanceGetBytes(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF8Advance>());
+  return static_cast<StringWTF8Advance*>(expression)->bytes;
+}
+void BinaryenStringWTF8AdvanceSetBytes(BinaryenExpressionRef expr,
+                                       BinaryenExpressionRef bytesExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF8Advance>());
+  assert(bytesExpr);
+  static_cast<StringWTF8Advance*>(expression)->bytes = (Expression*)bytesExpr;
+}
+// StringWTF16Get
+BinaryenExpressionRef BinaryenStringWTF16GetGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF16Get>());
+  return static_cast<StringWTF16Get*>(expression)->ref;
+}
+void BinaryenStringWTF16GetSetRef(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF16Get>());
+  assert(refExpr);
+  static_cast<StringWTF16Get*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef BinaryenStringWTF16GetGetPos(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF16Get>());
+  return static_cast<StringWTF16Get*>(expression)->pos;
+}
+void BinaryenStringWTF16GetSetPos(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef posExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringWTF16Get>());
+  assert(posExpr);
+  static_cast<StringWTF16Get*>(expression)->pos = (Expression*)posExpr;
+}
+// StringIterNext
+BinaryenExpressionRef BinaryenStringIterNextGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterNext>());
+  return static_cast<StringIterNext*>(expression)->ref;
+}
+void BinaryenStringIterNextSetRef(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterNext>());
+  assert(refExpr);
+  static_cast<StringIterNext*>(expression)->ref = (Expression*)refExpr;
+}
+// StringIterMove
+BinaryenOp BinaryenStringIterMoveGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterMove>());
+  return static_cast<StringIterMove*>(expression)->op;
+}
+void BinaryenStringIterMoveSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterMove>());
+  static_cast<StringIterMove*>(expression)->op = StringIterMoveOp(op);
+}
+BinaryenExpressionRef BinaryenStringIterMoveGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterMove>());
+  return static_cast<StringIterMove*>(expression)->ref;
+}
+void BinaryenStringIterMoveSetRef(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterMove>());
+  assert(refExpr);
+  static_cast<StringIterMove*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef BinaryenStringIterMoveGetNum(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterMove>());
+  return static_cast<StringIterMove*>(expression)->num;
+}
+void BinaryenStringIterMoveSetNum(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef numExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringIterMove>());
+  assert(numExpr);
+  static_cast<StringIterMove*>(expression)->num = (Expression*)numExpr;
+}
+// StringSliceWTF
+BinaryenOp BinaryenStringSliceWTFGetOp(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  return static_cast<StringSliceWTF*>(expression)->op;
+}
+void BinaryenStringSliceWTFSetOp(BinaryenExpressionRef expr, BinaryenOp op) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  static_cast<StringSliceWTF*>(expression)->op = StringSliceWTFOp(op);
+}
+BinaryenExpressionRef BinaryenStringSliceWTFGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  return static_cast<StringSliceWTF*>(expression)->ref;
+}
+void BinaryenStringSliceWTFSetRef(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  assert(refExpr);
+  static_cast<StringSliceWTF*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef
+BinaryenStringSliceWTFGetStart(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  return static_cast<StringSliceWTF*>(expression)->start;
+}
+void BinaryenStringSliceWTFSetStart(BinaryenExpressionRef expr,
+                                    BinaryenExpressionRef startExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  assert(startExpr);
+  static_cast<StringSliceWTF*>(expression)->start = (Expression*)startExpr;
+}
+BinaryenExpressionRef BinaryenStringSliceWTFGetEnd(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  return static_cast<StringSliceWTF*>(expression)->end;
+}
+void BinaryenStringSliceWTFSetEnd(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef endExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceWTF>());
+  assert(endExpr);
+  static_cast<StringSliceWTF*>(expression)->end = (Expression*)endExpr;
+}
+// StringSliceIter
+BinaryenExpressionRef
+BinaryenStringSliceIterGetRef(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceIter>());
+  return static_cast<StringSliceIter*>(expression)->ref;
+}
+void BinaryenStringSliceIterSetRef(BinaryenExpressionRef expr,
+                                   BinaryenExpressionRef refExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceIter>());
+  assert(refExpr);
+  static_cast<StringSliceIter*>(expression)->ref = (Expression*)refExpr;
+}
+BinaryenExpressionRef
+BinaryenStringSliceIterGetNum(BinaryenExpressionRef expr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceIter>());
+  return static_cast<StringSliceIter*>(expression)->num;
+}
+void BinaryenStringSliceIterSetNum(BinaryenExpressionRef expr,
+                                   BinaryenExpressionRef numExpr) {
+  auto* expression = (Expression*)expr;
+  assert(expression->is<StringSliceIter>());
+  assert(numExpr);
+  static_cast<StringSliceIter*>(expression)->num = (Expression*)numExpr;
+}
+
+// Functions
+
+BinaryenFunctionRef BinaryenAddFunction(BinaryenModuleRef module,
+                                        const char* name,
+                                        BinaryenType params,
+                                        BinaryenType results,
+                                        BinaryenType* varTypes,
+                                        BinaryenIndex numVarTypes,
+                                        BinaryenExpressionRef body) {
+  auto* ret = new Function;
+  ret->setExplicitName(name);
+  // TODO: Take a HeapType rather than params and results.
+  ret->type = Signature(Type(params), Type(results));
+  for (BinaryenIndex i = 0; i < numVarTypes; i++) {
+    ret->vars.push_back(Type(varTypes[i]));
+  }
+  ret->body = (Expression*)body;
+
+  // Lock. This can be called from multiple threads at once, and is a
+  // point where they all access and modify the module.
+  {
+    std::lock_guard<std::mutex> lock(BinaryenFunctionMutex);
+    ((Module*)module)->addFunction(ret);
+  }
+
+  return ret;
+}
+BinaryenFunctionRef BinaryenGetFunction(BinaryenModuleRef module,
+                                        const char* name) {
+  return ((Module*)module)->getFunctionOrNull(name);
+}
+void BinaryenRemoveFunction(BinaryenModuleRef module, const char* name) {
+  ((Module*)module)->removeFunction(name);
+}
+BinaryenIndex BinaryenGetNumFunctions(BinaryenModuleRef module) {
+  return ((Module*)module)->functions.size();
+}
+BinaryenFunctionRef BinaryenGetFunctionByIndex(BinaryenModuleRef module,
+                                               BinaryenIndex index) {
+  const auto& functions = ((Module*)module)->functions;
+  if (functions.size() <= index) {
+    Fatal() << "invalid function index.";
+  }
+  return functions[index].get();
+}
+
+// Globals
+
+BinaryenGlobalRef BinaryenAddGlobal(BinaryenModuleRef module,
+                                    const char* name,
+                                    BinaryenType type,
+                                    bool mutable_,
+                                    BinaryenExpressionRef init) {
+  auto* ret = new Global();
+  ret->setExplicitName(name);
+  ret->type = Type(type);
   ret->mutable_ = mutable_;
   ret->init = (Expression*)init;
   ((Module*)module)->addGlobal(ret);
@@ -3504,33 +4997,56 @@ void BinaryenAddFunctionImport(BinaryenModuleRef module,
                                const char* externalBaseName,
                                BinaryenType params,
                                BinaryenType results) {
-  auto* ret = new Function();
-  ret->name = internalName;
-  ret->module = externalModuleName;
-  ret->base = externalBaseName;
-  // TODO: Take a HeapType rather than params and results.
-  ret->type = Signature(Type(params), Type(results));
-  ((Module*)module)->addFunction(ret);
+  auto* func = ((Module*)module)->getFunctionOrNull(internalName);
+  if (func == nullptr) {
+    auto func = make_unique<Function>();
+    func->name = internalName;
+    func->module = externalModuleName;
+    func->base = externalBaseName;
+    // TODO: Take a HeapType rather than params and results.
+    func->type = Signature(Type(params), Type(results));
+    ((Module*)module)->addFunction(std::move(func));
+  } else {
+    // already exists so just set module and base
+    func->module = externalModuleName;
+    func->base = externalBaseName;
+  }
 }
 void BinaryenAddTableImport(BinaryenModuleRef module,
                             const char* internalName,
                             const char* externalModuleName,
                             const char* externalBaseName) {
-  auto table = std::make_unique<Table>();
-  table->name = internalName;
-  table->module = externalModuleName;
-  table->base = externalBaseName;
-  ((Module*)module)->addTable(std::move(table));
+  auto* table = ((Module*)module)->getTableOrNull(internalName);
+  if (table == nullptr) {
+    auto table = make_unique<Table>();
+    table->name = internalName;
+    table->module = externalModuleName;
+    table->base = externalBaseName;
+    ((Module*)module)->addTable(std::move(table));
+  } else {
+    // already exists so just set module and base
+    table->module = externalModuleName;
+    table->base = externalBaseName;
+  }
 }
 void BinaryenAddMemoryImport(BinaryenModuleRef module,
                              const char* internalName,
                              const char* externalModuleName,
                              const char* externalBaseName,
                              uint8_t shared) {
-  auto& memory = ((Module*)module)->memory;
-  memory.module = externalModuleName;
-  memory.base = externalBaseName;
-  memory.shared = shared;
+  auto* memory = ((Module*)module)->getMemoryOrNull(internalName);
+  if (memory == nullptr) {
+    auto memory = make_unique<Memory>();
+    memory->name = internalName;
+    memory->module = externalModuleName;
+    memory->base = externalBaseName;
+    memory->shared = shared;
+    ((Module*)module)->addMemory(std::move(memory));
+  } else {
+    // already exists so just set module and base
+    memory->module = externalModuleName;
+    memory->base = externalBaseName;
+  }
 }
 void BinaryenAddGlobalImport(BinaryenModuleRef module,
                              const char* internalName,
@@ -3538,13 +5054,20 @@ void BinaryenAddGlobalImport(BinaryenModuleRef module,
                              const char* externalBaseName,
                              BinaryenType globalType,
                              bool mutable_) {
-  auto* ret = new Global();
-  ret->name = internalName;
-  ret->module = externalModuleName;
-  ret->base = externalBaseName;
-  ret->type = Type(globalType);
-  ret->mutable_ = mutable_;
-  ((Module*)module)->addGlobal(ret);
+  auto* glob = ((Module*)module)->getGlobalOrNull(internalName);
+  if (glob == nullptr) {
+    auto glob = make_unique<Global>();
+    glob->name = internalName;
+    glob->module = externalModuleName;
+    glob->base = externalBaseName;
+    glob->type = Type(globalType);
+    glob->mutable_ = mutable_;
+    ((Module*)module)->addGlobal(std::move(glob));
+  } else {
+    // already exists so just set module and base
+    glob->module = externalModuleName;
+    glob->base = externalBaseName;
+  }
 }
 void BinaryenAddTagImport(BinaryenModuleRef module,
                           const char* internalName,
@@ -3552,12 +5075,19 @@ void BinaryenAddTagImport(BinaryenModuleRef module,
                           const char* externalBaseName,
                           BinaryenType params,
                           BinaryenType results) {
-  auto* ret = new Tag();
-  ret->name = internalName;
-  ret->module = externalModuleName;
-  ret->base = externalBaseName;
-  ret->sig = Signature(Type(params), Type(results));
-  ((Module*)module)->addTag(ret);
+  auto* tag = ((Module*)module)->getGlobalOrNull(internalName);
+  if (tag == nullptr) {
+    auto tag = make_unique<Tag>();
+    tag->name = internalName;
+    tag->module = externalModuleName;
+    tag->base = externalBaseName;
+    tag->sig = Signature(Type(params), Type(results));
+    ((Module*)module)->addTag(std::move(tag));
+  } else {
+    // already exists so just set module and base
+    tag->module = externalModuleName;
+    tag->base = externalBaseName;
+  }
 }
 
 // Exports
@@ -3736,13 +5266,13 @@ const char* BinaryenElementSegmentGetData(BinaryenElementSegmentRef elem,
   if (data[dataId]->is<RefNull>()) {
     return NULL;
   } else if (auto* get = data[dataId]->dynCast<RefFunc>()) {
-    return get->func.c_str();
+    return get->func.str.data();
   } else {
     Fatal() << "invalid expression in segment data.";
   }
 }
 
-// Memory. One per module
+// Memory.
 
 void BinaryenSetMemory(BinaryenModuleRef module,
                        BinaryenIndex initial,
@@ -3753,37 +5283,48 @@ void BinaryenSetMemory(BinaryenModuleRef module,
                        BinaryenExpressionRef* segmentOffsets,
                        BinaryenIndex* segmentSizes,
                        BinaryenIndex numSegments,
-                       bool shared) {
-  auto* wasm = (Module*)module;
-  wasm->memory.initial = initial;
-  wasm->memory.max = int32_t(maximum); // Make sure -1 extends.
-  wasm->memory.exists = true;
-  wasm->memory.shared = shared;
+                       bool shared,
+                       bool memory64,
+                       const char* name) {
+  auto memory = std::make_unique<Memory>();
+  memory->name = name ? name : "0";
+  memory->initial = initial;
+  memory->max = int32_t(maximum); // Make sure -1 extends.
+  memory->shared = shared;
+  memory->indexType = memory64 ? Type::i64 : Type::i32;
   if (exportName) {
     auto memoryExport = make_unique<Export>();
     memoryExport->name = exportName;
-    memoryExport->value = Name::fromInt(0);
+    memoryExport->value = memory->name;
     memoryExport->kind = ExternalKind::Memory;
-    wasm->addExport(memoryExport.release());
+    ((Module*)module)->addExport(memoryExport.release());
   }
+  ((Module*)module)->removeDataSegments([&](DataSegment* curr) {
+    return true;
+  });
   for (BinaryenIndex i = 0; i < numSegments; i++) {
-    wasm->memory.segments.emplace_back(Name(),
-                                       segmentPassive[i],
-                                       (Expression*)segmentOffsets[i],
-                                       segments[i],
-                                       segmentSizes[i]);
+    auto curr = Builder::makeDataSegment(Name::fromInt(i),
+                                         memory->name,
+                                         segmentPassive[i],
+                                         (Expression*)segmentOffsets[i],
+                                         segments[i],
+                                         segmentSizes[i]);
+    curr->hasExplicitName = false;
+    ((Module*)module)->addDataSegment(std::move(curr));
   }
+  ((Module*)module)->removeMemories([&](Memory* curr) { return true; });
+  ((Module*)module)->addMemory(std::move(memory));
 }
 
 // Memory segments
 
 uint32_t BinaryenGetNumMemorySegments(BinaryenModuleRef module) {
-  return ((Module*)module)->memory.segments.size();
+  return ((Module*)module)->dataSegments.size();
 }
 uint32_t BinaryenGetMemorySegmentByteOffset(BinaryenModuleRef module,
                                             BinaryenIndex id) {
   auto* wasm = (Module*)module;
-  if (wasm->memory.segments.size() <= id) {
+  if (wasm->dataSegments.size() <= id) {
     Fatal() << "invalid segment id.";
   }
 
@@ -3796,13 +5337,13 @@ uint32_t BinaryenGetMemorySegmentByteOffset(BinaryenModuleRef module,
     return false;
   };
 
-  const auto& segment = wasm->memory.segments[id];
+  const auto& segment = wasm->dataSegments[id];
 
   int64_t ret;
-  if (globalOffset(segment.offset, ret)) {
+  if (globalOffset(segment->offset, ret)) {
     return ret;
   }
-  if (auto* get = segment.offset->dynCast<GlobalGet>()) {
+  if (auto* get = segment->offset->dynCast<GlobalGet>()) {
     Global* global = wasm->getGlobal(get->name);
     if (globalOffset(global->init, ret)) {
       return ret;
@@ -3812,31 +5353,122 @@ uint32_t BinaryenGetMemorySegmentByteOffset(BinaryenModuleRef module,
   Fatal() << "non-constant offsets aren't supported yet";
   return 0;
 }
+bool BinaryenHasMemory(BinaryenModuleRef module) {
+  return !((Module*)module)->memories.empty();
+}
+BinaryenIndex BinaryenMemoryGetInitial(BinaryenModuleRef module,
+                                       const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  return memory->initial;
+}
+bool BinaryenMemoryHasMax(BinaryenModuleRef module, const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  return memory->hasMax();
+}
+BinaryenIndex BinaryenMemoryGetMax(BinaryenModuleRef module, const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  return memory->max;
+}
+const char* BinaryenMemoryImportGetModule(BinaryenModuleRef module,
+                                          const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  if (memory->imported()) {
+    return memory->module.str.data();
+  } else {
+    return "";
+  }
+}
+const char* BinaryenMemoryImportGetBase(BinaryenModuleRef module,
+                                        const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  if (memory->imported()) {
+    return memory->base.str.data();
+  } else {
+    return "";
+  }
+}
+bool BinaryenMemoryIsShared(BinaryenModuleRef module, const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  return memory->shared;
+}
+bool BinaryenMemoryIs64(BinaryenModuleRef module, const char* name) {
+  // Maintaining compatibility for instructions with a single memory
+  if (name == nullptr && module->memories.size() == 1) {
+    name = module->memories[0]->name.str.data();
+  }
+  auto* memory = ((Module*)module)->getMemoryOrNull(name);
+  if (memory == nullptr) {
+    Fatal() << "invalid memory '" << name << "'.";
+  }
+  return memory->is64();
+}
 size_t BinaryenGetMemorySegmentByteLength(BinaryenModuleRef module,
                                           BinaryenIndex id) {
-  const auto& segments = ((Module*)module)->memory.segments;
+  const auto& segments = ((Module*)module)->dataSegments;
   if (segments.size() <= id) {
     Fatal() << "invalid segment id.";
   }
-  return segments[id].data.size();
+  return segments[id]->data.size();
 }
 bool BinaryenGetMemorySegmentPassive(BinaryenModuleRef module,
                                      BinaryenIndex id) {
-  const auto& segments = ((Module*)module)->memory.segments;
+  const auto& segments = ((Module*)module)->dataSegments;
   if (segments.size() <= id) {
     Fatal() << "invalid segment id.";
   }
-  return segments[id].isPassive;
+  return segments[id]->isPassive;
 }
 void BinaryenCopyMemorySegmentData(BinaryenModuleRef module,
                                    BinaryenIndex id,
                                    char* buffer) {
-  const auto& segments = ((Module*)module)->memory.segments;
+  const auto& segments = ((Module*)module)->dataSegments;
   if (segments.size() <= id) {
     Fatal() << "invalid segment id.";
   }
   const auto& segment = segments[id];
-  std::copy(segment.data.cbegin(), segment.data.cend(), buffer);
+  std::copy(segment->data.cbegin(), segment->data.cend(), buffer);
 }
 
 // Start function. One per module
@@ -3877,6 +5509,10 @@ void BinaryenModulePrint(BinaryenModuleRef module) {
   std::cout << *(Module*)module;
 }
 
+void BinaryenModulePrintStackIR(BinaryenModuleRef module, bool optimize) {
+  wasm::printStackIR(std::cout, (Module*)module, optimize);
+}
+
 void BinaryenModulePrintAsmjs(BinaryenModuleRef module) {
   auto* wasm = (Module*)module;
   Wasm2JSBuilder::Flags flags;
@@ -3950,7 +5586,7 @@ const char* BinaryenGetPassArgument(const char* key) {
     return nullptr;
   }
   // internalize the string so it remains valid while the module is
-  return Name(it->second).c_str();
+  return Name(it->second).str.data();
 }
 
 void BinaryenSetPassArgument(const char* key, const char* value) {
@@ -4059,6 +5695,22 @@ size_t BinaryenModuleWriteText(BinaryenModuleRef module,
   return std::min(outputSize, temp.size());
 }
 
+size_t BinaryenModuleWriteStackIR(BinaryenModuleRef module,
+                                  char* output,
+                                  size_t outputSize,
+                                  bool optimize) {
+  // use a stringstream as an std::ostream. Extract the std::string
+  // representation, and then store in the output.
+  std::stringstream ss;
+  wasm::printStackIR(ss, (Module*)module, optimize);
+
+  const auto temp = ss.str();
+  const auto ctemp = temp.c_str();
+
+  strncpy(output, ctemp, outputSize);
+  return std::min(outputSize, temp.size());
+}
+
 BinaryenBufferSizes BinaryenModuleWriteWithSourceMap(BinaryenModuleRef module,
                                                      const char* url,
                                                      char* output,
@@ -4087,25 +5739,42 @@ BinaryenModuleAllocateAndWrite(BinaryenModuleRef module,
   char* sourceMap = nullptr;
   if (sourceMapUrl) {
     auto str = os.str();
-    sourceMap = (char*)malloc(str.length() + 1);
-    std::copy_n(str.c_str(), str.length() + 1, sourceMap);
+    const size_t len = str.length() + 1;
+    sourceMap = (char*)malloc(len);
+    std::copy_n(str.c_str(), len, sourceMap);
   }
   return {binary, buffer.size(), sourceMap};
 }
 
 char* BinaryenModuleAllocateAndWriteText(BinaryenModuleRef module) {
-  std::stringstream ss;
+  std::ostringstream os;
   bool colors = Colors::isEnabled();
 
   Colors::setEnabled(false); // do not use colors for writing
-  ss << *(Module*)module;
+  os << *(Module*)module;
   Colors::setEnabled(colors); // restore colors state
 
-  const std::string out = ss.str();
-  const int len = out.length() + 1;
-  char* cout = (char*)malloc(len);
-  strncpy(cout, out.c_str(), len);
-  return cout;
+  auto str = os.str();
+  const size_t len = str.length() + 1;
+  char* output = (char*)malloc(len);
+  std::copy_n(str.c_str(), len, output);
+  return output;
+}
+
+char* BinaryenModuleAllocateAndWriteStackIR(BinaryenModuleRef module,
+                                            bool optimize) {
+  std::ostringstream os;
+  bool colors = Colors::isEnabled();
+
+  Colors::setEnabled(false); // do not use colors for writing
+  wasm::printStackIR(os, (Module*)module, optimize);
+  Colors::setEnabled(colors); // restore colors state
+
+  auto str = os.str();
+  const size_t len = str.length() + 1;
+  char* output = (char*)malloc(len);
+  std::copy_n(str.c_str(), len, output);
+  return output;
 }
 
 BinaryenModuleRef BinaryenModuleRead(char* input, size_t inputSize) {
@@ -4152,7 +5821,7 @@ const char* BinaryenModuleGetDebugInfoFileName(BinaryenModuleRef module,
 // TODO: add BinaryenFunctionGetType
 
 const char* BinaryenFunctionGetName(BinaryenFunctionRef func) {
-  return ((Function*)func)->name.c_str();
+  return ((Function*)func)->name.str.data();
 }
 BinaryenType BinaryenFunctionGetParams(BinaryenFunctionRef func) {
   return ((Function*)func)->getParams().getID();
@@ -4178,7 +5847,7 @@ bool BinaryenFunctionHasLocalName(BinaryenFunctionRef func,
 }
 const char* BinaryenFunctionGetLocalName(BinaryenFunctionRef func,
                                          BinaryenIndex index) {
-  return ((Function*)func)->getLocalName(index).str;
+  return ((Function*)func)->getLocalName(index).str.data();
 }
 void BinaryenFunctionSetLocalName(BinaryenFunctionRef func,
                                   BinaryenIndex index,
@@ -4228,7 +5897,7 @@ void BinaryenFunctionSetDebugLocation(BinaryenFunctionRef func,
 //
 
 const char* BinaryenTableGetName(BinaryenTableRef table) {
-  return ((Table*)table)->name.c_str();
+  return ((Table*)table)->name.str.data();
 }
 void BinaryenTableSetName(BinaryenTableRef table, const char* name) {
   ((Table*)table)->name = name;
@@ -4253,14 +5922,14 @@ void BinaryenTableSetMax(BinaryenTableRef table, BinaryenIndex max) {
 // =========== ElementSegment operations ===========
 //
 const char* BinaryenElementSegmentGetName(BinaryenElementSegmentRef elem) {
-  return ((ElementSegment*)elem)->name.c_str();
+  return ((ElementSegment*)elem)->name.str.data();
 }
 void BinaryenElementSegmentSetName(BinaryenElementSegmentRef elem,
                                    const char* name) {
   ((ElementSegment*)elem)->name = name;
 }
 const char* BinaryenElementSegmentGetTable(BinaryenElementSegmentRef elem) {
-  return ((ElementSegment*)elem)->table.c_str();
+  return ((ElementSegment*)elem)->table.str.data();
 }
 void BinaryenElementSegmentSetTable(BinaryenElementSegmentRef elem,
                                     const char* table) {
@@ -4275,7 +5944,7 @@ bool BinaryenElementSegmentIsPassive(BinaryenElementSegmentRef elem) {
 //
 
 const char* BinaryenGlobalGetName(BinaryenGlobalRef global) {
-  return ((Global*)global)->name.c_str();
+  return ((Global*)global)->name.str.data();
 }
 BinaryenType BinaryenGlobalGetType(BinaryenGlobalRef global) {
   return ((Global*)global)->type.getID();
@@ -4292,7 +5961,7 @@ BinaryenExpressionRef BinaryenGlobalGetInitExpr(BinaryenGlobalRef global) {
 //
 
 const char* BinaryenTagGetName(BinaryenTagRef tag) {
-  return ((Tag*)tag)->name.c_str();
+  return ((Tag*)tag)->name.str.data();
 }
 BinaryenType BinaryenTagGetParams(BinaryenTagRef tag) {
   return ((Tag*)tag)->sig.params.getID();
@@ -4309,7 +5978,7 @@ BinaryenType BinaryenTagGetResults(BinaryenTagRef tag) {
 const char* BinaryenFunctionImportGetModule(BinaryenFunctionRef import) {
   auto* func = (Function*)import;
   if (func->imported()) {
-    return func->module.c_str();
+    return func->module.str.data();
   } else {
     return "";
   }
@@ -4317,7 +5986,7 @@ const char* BinaryenFunctionImportGetModule(BinaryenFunctionRef import) {
 const char* BinaryenTableImportGetModule(BinaryenTableRef import) {
   auto* table = (Table*)import;
   if (table->imported()) {
-    return table->module.c_str();
+    return table->module.str.data();
   } else {
     return "";
   }
@@ -4325,7 +5994,7 @@ const char* BinaryenTableImportGetModule(BinaryenTableRef import) {
 const char* BinaryenGlobalImportGetModule(BinaryenGlobalRef import) {
   auto* global = (Global*)import;
   if (global->imported()) {
-    return global->module.c_str();
+    return global->module.str.data();
   } else {
     return "";
   }
@@ -4333,7 +6002,7 @@ const char* BinaryenGlobalImportGetModule(BinaryenGlobalRef import) {
 const char* BinaryenTagImportGetModule(BinaryenTagRef import) {
   auto* tag = (Tag*)import;
   if (tag->imported()) {
-    return tag->module.c_str();
+    return tag->module.str.data();
   } else {
     return "";
   }
@@ -4341,7 +6010,7 @@ const char* BinaryenTagImportGetModule(BinaryenTagRef import) {
 const char* BinaryenFunctionImportGetBase(BinaryenFunctionRef import) {
   auto* func = (Function*)import;
   if (func->imported()) {
-    return func->base.c_str();
+    return func->base.str.data();
   } else {
     return "";
   }
@@ -4349,7 +6018,7 @@ const char* BinaryenFunctionImportGetBase(BinaryenFunctionRef import) {
 const char* BinaryenTableImportGetBase(BinaryenTableRef import) {
   auto* table = (Table*)import;
   if (table->imported()) {
-    return table->base.c_str();
+    return table->base.str.data();
   } else {
     return "";
   }
@@ -4357,7 +6026,7 @@ const char* BinaryenTableImportGetBase(BinaryenTableRef import) {
 const char* BinaryenGlobalImportGetBase(BinaryenGlobalRef import) {
   auto* global = (Global*)import;
   if (global->imported()) {
-    return global->base.c_str();
+    return global->base.str.data();
   } else {
     return "";
   }
@@ -4365,7 +6034,7 @@ const char* BinaryenGlobalImportGetBase(BinaryenGlobalRef import) {
 const char* BinaryenTagImportGetBase(BinaryenTagRef import) {
   auto* tag = (Tag*)import;
   if (tag->imported()) {
-    return tag->base.c_str();
+    return tag->base.str.data();
   } else {
     return "";
   }
@@ -4379,10 +6048,10 @@ BinaryenExternalKind BinaryenExportGetKind(BinaryenExportRef export_) {
   return BinaryenExternalKind(((Export*)export_)->kind);
 }
 const char* BinaryenExportGetName(BinaryenExportRef export_) {
-  return ((Export*)export_)->name.c_str();
+  return ((Export*)export_)->name.str.data();
 }
 const char* BinaryenExportGetValue(BinaryenExportRef export_) {
-  return ((Export*)export_)->value.c_str();
+  return ((Export*)export_)->value.str.data();
 }
 
 //
@@ -4610,6 +6279,154 @@ ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner,
   return ret;
 }
 
+//
+// ========= Type builder =========
+//
+
+TypeBuilderErrorReason TypeBuilderErrorReasonSelfSupertype() {
+  return static_cast<TypeBuilderErrorReason>(
+    TypeBuilder::ErrorReason::SelfSupertype);
+}
+TypeBuilderErrorReason TypeBuilderErrorReasonInvalidSupertype() {
+  return static_cast<TypeBuilderErrorReason>(
+    TypeBuilder::ErrorReason::InvalidSupertype);
+}
+TypeBuilderErrorReason TypeBuilderErrorReasonForwardSupertypeReference() {
+  return static_cast<TypeBuilderErrorReason>(
+    TypeBuilder::ErrorReason::ForwardSupertypeReference);
+}
+TypeBuilderErrorReason TypeBuilderErrorReasonForwardChildReference() {
+  return static_cast<TypeBuilderErrorReason>(
+    TypeBuilder::ErrorReason::ForwardChildReference);
+}
+
+TypeBuilderRef TypeBuilderCreate(BinaryenIndex size) {
+  return static_cast<TypeBuilderRef>(new TypeBuilder(size));
+}
+void TypeBuilderGrow(TypeBuilderRef builder, BinaryenIndex count) {
+  ((TypeBuilder*)builder)->grow(count);
+}
+BinaryenIndex TypeBuilderGetSize(TypeBuilderRef builder) {
+  return ((TypeBuilder*)builder)->size();
+}
+void TypeBuilderSetBasicHeapType(TypeBuilderRef builder,
+                                 BinaryenIndex index,
+                                 BinaryenBasicHeapType basicHeapType) {
+  ((TypeBuilder*)builder)
+    ->setHeapType(index, HeapType::BasicHeapType(basicHeapType));
+}
+void TypeBuilderSetSignatureType(TypeBuilderRef builder,
+                                 BinaryenIndex index,
+                                 BinaryenType paramTypes,
+                                 BinaryenType resultTypes) {
+  ((TypeBuilder*)builder)
+    ->setHeapType(index, Signature(Type(paramTypes), Type(resultTypes)));
+}
+void TypeBuilderSetStructType(TypeBuilderRef builder,
+                              BinaryenIndex index,
+                              BinaryenType* fieldTypes,
+                              BinaryenPackedType* fieldPackedTypes,
+                              bool* fieldMutables,
+                              int numFields) {
+  auto* B = (TypeBuilder*)builder;
+  FieldList fields;
+  for (int cur = 0; cur < numFields; ++cur) {
+    Field field(Type(fieldTypes[cur]),
+                fieldMutables[cur] ? Mutability::Mutable
+                                   : Mutability::Immutable);
+    if (field.type == Type::i32) {
+      field.packedType = Field::PackedType(fieldPackedTypes[cur]);
+    } else {
+      assert(fieldPackedTypes[cur] == Field::PackedType::not_packed);
+    }
+    fields.push_back(field);
+  }
+  B->setHeapType(index, Struct(fields));
+}
+void TypeBuilderSetArrayType(TypeBuilderRef builder,
+                             BinaryenIndex index,
+                             BinaryenType elementType,
+                             BinaryenPackedType elementPackedType,
+                             int elementMutable) {
+  auto* B = (TypeBuilder*)builder;
+  Field element(Type(elementType),
+                elementMutable ? Mutability::Mutable : Mutability::Immutable);
+  if (element.type == Type::i32) {
+    element.packedType = Field::PackedType(elementPackedType);
+  } else {
+    assert(elementPackedType == Field::PackedType::not_packed);
+  }
+  B->setHeapType(index, Array(element));
+}
+bool TypeBuilderIsBasic(TypeBuilderRef builder, BinaryenIndex index) {
+  return ((TypeBuilder*)builder)->isBasic(index);
+}
+BinaryenBasicHeapType TypeBuilderGetBasic(TypeBuilderRef builder,
+                                          BinaryenIndex index) {
+  return BinaryenBasicHeapType(((TypeBuilder*)builder)->getBasic(index));
+}
+BinaryenHeapType TypeBuilderGetTempHeapType(TypeBuilderRef builder,
+                                            BinaryenIndex index) {
+  return ((TypeBuilder*)builder)->getTempHeapType(index).getID();
+}
+BinaryenType TypeBuilderGetTempTupleType(TypeBuilderRef builder,
+                                         BinaryenType* types,
+                                         BinaryenIndex numTypes) {
+  TypeList typeList(numTypes);
+  for (BinaryenIndex cur = 0; cur < numTypes; ++cur) {
+    typeList[cur] = Type(types[cur]);
+  }
+  return ((TypeBuilder*)builder)->getTempTupleType(Tuple(typeList)).getID();
+}
+BinaryenType TypeBuilderGetTempRefType(TypeBuilderRef builder,
+                                       BinaryenHeapType heapType,
+                                       int nullable) {
+  return ((TypeBuilder*)builder)
+    ->getTempRefType(HeapType(heapType), nullable ? Nullable : NonNullable)
+    .getID();
+}
+void TypeBuilderSetSubType(TypeBuilderRef builder,
+                           BinaryenIndex index,
+                           BinaryenHeapType superType) {
+  ((TypeBuilder*)builder)->setSubType(index, HeapType(superType));
+}
+void TypeBuilderCreateRecGroup(TypeBuilderRef builder,
+                               BinaryenIndex index,
+                               BinaryenIndex length) {
+  ((TypeBuilder*)builder)->createRecGroup(index, length);
+}
+bool TypeBuilderBuildAndDispose(TypeBuilderRef builder,
+                                BinaryenHeapType* heapTypes,
+                                BinaryenIndex* errorIndex,
+                                TypeBuilderErrorReason* errorReason) {
+  auto* B = (TypeBuilder*)builder;
+  auto result = B->build();
+  if (auto err = result.getError()) {
+    *errorIndex = err->index;
+    *errorReason = static_cast<TypeBuilderErrorReason>(err->reason);
+    delete B;
+    return false;
+  }
+  auto types = *result;
+  for (size_t cur = 0; cur < types.size(); ++cur) {
+    heapTypes[cur] = types[cur].getID();
+  }
+  delete B;
+  return true;
+}
+
+void BinaryenModuleSetTypeName(BinaryenModuleRef module,
+                               BinaryenHeapType heapType,
+                               const char* name) {
+  ((Module*)module)->typeNames[HeapType(heapType)].name = name;
+}
+void BinaryenModuleSetFieldName(BinaryenModuleRef module,
+                                BinaryenHeapType heapType,
+                                BinaryenIndex index,
+                                const char* name) {
+  ((Module*)module)->typeNames[HeapType(heapType)].fieldNames[index] = name;
+}
+
 //
 // ========= Utilities =========
 //
diff --git a/src/binaryen-c.h b/src/binaryen-c.h
index ed0f01f..8da248f 100644
--- a/src/binaryen-c.h
+++ b/src/binaryen-c.h
@@ -105,6 +105,14 @@ BINARYEN_API BinaryenType BinaryenTypeAnyref(void);
 BINARYEN_API BinaryenType BinaryenTypeEqref(void);
 BINARYEN_API BinaryenType BinaryenTypeI31ref(void);
 BINARYEN_API BinaryenType BinaryenTypeDataref(void);
+BINARYEN_API BinaryenType BinaryenTypeArrayref(void);
+BINARYEN_API BinaryenType BinaryenTypeStringref(void);
+BINARYEN_API BinaryenType BinaryenTypeStringviewWTF8(void);
+BINARYEN_API BinaryenType BinaryenTypeStringviewWTF16(void);
+BINARYEN_API BinaryenType BinaryenTypeStringviewIter(void);
+BINARYEN_API BinaryenType BinaryenTypeNullref(void);
+BINARYEN_API BinaryenType BinaryenTypeNullExternref(void);
+BINARYEN_API BinaryenType BinaryenTypeNullFuncref(void);
 BINARYEN_API BinaryenType BinaryenTypeUnreachable(void);
 // Not a real type. Used as the last parameter to BinaryenBlock to let
 // the API figure out the type instead of providing one.
@@ -121,6 +129,75 @@ WASM_DEPRECATED BinaryenType BinaryenFloat32(void);
 WASM_DEPRECATED BinaryenType BinaryenFloat64(void);
 WASM_DEPRECATED BinaryenType BinaryenUndefined(void);
 
+// Packed types (call to get the value of each; you can cache them)
+
+typedef uint32_t BinaryenPackedType;
+
+BINARYEN_API BinaryenPackedType BinaryenPackedTypeNotPacked(void);
+BINARYEN_API BinaryenPackedType BinaryenPackedTypeInt8(void);
+BINARYEN_API BinaryenPackedType BinaryenPackedTypeInt16(void);
+
+// Heap types
+
+typedef uintptr_t BinaryenHeapType;
+
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeExt(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeFunc(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeAny(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeEq(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeI31(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeData(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeArray(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeString(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeStringviewWTF8(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeStringviewWTF16(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeStringviewIter(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeNone(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeNoext(void);
+BINARYEN_API BinaryenHeapType BinaryenHeapTypeNofunc(void);
+
+BINARYEN_API bool BinaryenHeapTypeIsBasic(BinaryenHeapType heapType);
+BINARYEN_API bool BinaryenHeapTypeIsSignature(BinaryenHeapType heapType);
+BINARYEN_API bool BinaryenHeapTypeIsStruct(BinaryenHeapType heapType);
+BINARYEN_API bool BinaryenHeapTypeIsArray(BinaryenHeapType heapType);
+BINARYEN_API bool BinaryenHeapTypeIsBottom(BinaryenHeapType heapType);
+BINARYEN_API BinaryenHeapType
+BinaryenHeapTypeGetBottom(BinaryenHeapType heapType);
+BINARYEN_API bool BinaryenHeapTypeIsSubType(BinaryenHeapType left,
+                                            BinaryenHeapType right);
+BINARYEN_API BinaryenIndex
+BinaryenStructTypeGetNumFields(BinaryenHeapType heapType);
+BINARYEN_API BinaryenType
+BinaryenStructTypeGetFieldType(BinaryenHeapType heapType, BinaryenIndex index);
+BINARYEN_API BinaryenPackedType BinaryenStructTypeGetFieldPackedType(
+  BinaryenHeapType heapType, BinaryenIndex index);
+BINARYEN_API bool BinaryenStructTypeIsFieldMutable(BinaryenHeapType heapType,
+                                                   BinaryenIndex index);
+BINARYEN_API BinaryenType
+BinaryenArrayTypeGetElementType(BinaryenHeapType heapType);
+BINARYEN_API BinaryenPackedType
+BinaryenArrayTypeGetElementPackedType(BinaryenHeapType heapType);
+BINARYEN_API bool BinaryenArrayTypeIsElementMutable(BinaryenHeapType heapType);
+BINARYEN_API BinaryenType
+BinaryenSignatureTypeGetParams(BinaryenHeapType heapType);
+BINARYEN_API BinaryenType
+BinaryenSignatureTypeGetResults(BinaryenHeapType heapType);
+
+BINARYEN_API BinaryenHeapType BinaryenTypeGetHeapType(BinaryenType type);
+BINARYEN_API bool BinaryenTypeIsNullable(BinaryenType type);
+BINARYEN_API BinaryenType BinaryenTypeFromHeapType(BinaryenHeapType heapType,
+                                                   bool nullable);
+
+// TypeSystem
+
+typedef uint32_t BinaryenTypeSystem;
+
+BINARYEN_API BinaryenTypeSystem BinaryenTypeSystemEquirecursive(void);
+BINARYEN_API BinaryenTypeSystem BinaryenTypeSystemNominal(void);
+BINARYEN_API BinaryenTypeSystem BinaryenTypeSystemIsorecursive(void);
+BINARYEN_API BinaryenTypeSystem BinaryenGetTypeSystem(void);
+BINARYEN_API void BinaryenSetTypeSystem(BinaryenTypeSystem typeSystem);
+
 // Expression ids (call to get the value of each; you can cache them)
 
 typedef uint32_t BinaryenExpressionId;
@@ -160,9 +237,10 @@ BINARYEN_API BinaryenFeatures BinaryenFeatureReferenceTypes(void);
 BINARYEN_API BinaryenFeatures BinaryenFeatureMultivalue(void);
 BINARYEN_API BinaryenFeatures BinaryenFeatureGC(void);
 BINARYEN_API BinaryenFeatures BinaryenFeatureMemory64(void);
-BINARYEN_API BinaryenFeatures BinaryenFeatureTypedFunctionReferences(void);
 BINARYEN_API BinaryenFeatures BinaryenFeatureRelaxedSIMD(void);
 BINARYEN_API BinaryenFeatures BinaryenFeatureExtendedConst(void);
+BINARYEN_API BinaryenFeatures BinaryenFeatureStrings(void);
+BINARYEN_API BinaryenFeatures BinaryenFeatureMultiMemories(void);
 BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void);
 
 // Modules
@@ -601,6 +679,44 @@ BINARYEN_API BinaryenOp BinaryenRefAsNonNull(void);
 BINARYEN_API BinaryenOp BinaryenRefAsFunc(void);
 BINARYEN_API BinaryenOp BinaryenRefAsData(void);
 BINARYEN_API BinaryenOp BinaryenRefAsI31(void);
+BINARYEN_API BinaryenOp BinaryenRefAsExternInternalize(void);
+BINARYEN_API BinaryenOp BinaryenRefAsExternExternalize(void);
+BINARYEN_API BinaryenOp BinaryenBrOnNull(void);
+BINARYEN_API BinaryenOp BinaryenBrOnNonNull(void);
+BINARYEN_API BinaryenOp BinaryenBrOnCast(void);
+BINARYEN_API BinaryenOp BinaryenBrOnCastFail(void);
+BINARYEN_API BinaryenOp BinaryenBrOnFunc(void);
+BINARYEN_API BinaryenOp BinaryenBrOnNonFunc(void);
+BINARYEN_API BinaryenOp BinaryenBrOnData(void);
+BINARYEN_API BinaryenOp BinaryenBrOnNonData(void);
+BINARYEN_API BinaryenOp BinaryenBrOnI31(void);
+BINARYEN_API BinaryenOp BinaryenBrOnNonI31(void);
+BINARYEN_API BinaryenOp BinaryenStringNewUTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringNewWTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringNewReplace(void);
+BINARYEN_API BinaryenOp BinaryenStringNewWTF16(void);
+BINARYEN_API BinaryenOp BinaryenStringNewUTF8Array(void);
+BINARYEN_API BinaryenOp BinaryenStringNewWTF8Array(void);
+BINARYEN_API BinaryenOp BinaryenStringNewReplaceArray(void);
+BINARYEN_API BinaryenOp BinaryenStringNewWTF16Array(void);
+BINARYEN_API BinaryenOp BinaryenStringMeasureUTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringMeasureWTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringMeasureWTF16(void);
+BINARYEN_API BinaryenOp BinaryenStringMeasureIsUSV(void);
+BINARYEN_API BinaryenOp BinaryenStringMeasureWTF16View(void);
+BINARYEN_API BinaryenOp BinaryenStringEncodeUTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringEncodeWTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringEncodeWTF16(void);
+BINARYEN_API BinaryenOp BinaryenStringEncodeUTF8Array(void);
+BINARYEN_API BinaryenOp BinaryenStringEncodeWTF8Array(void);
+BINARYEN_API BinaryenOp BinaryenStringEncodeWTF16Array(void);
+BINARYEN_API BinaryenOp BinaryenStringAsWTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringAsWTF16(void);
+BINARYEN_API BinaryenOp BinaryenStringAsIter(void);
+BINARYEN_API BinaryenOp BinaryenStringIterMoveAdvance(void);
+BINARYEN_API BinaryenOp BinaryenStringIterMoveRewind(void);
+BINARYEN_API BinaryenOp BinaryenStringSliceWTF8(void);
+BINARYEN_API BinaryenOp BinaryenStringSliceWTF16(void);
 
 BINARYEN_REF(Expression);
 
@@ -704,7 +820,8 @@ BINARYEN_API BinaryenExpressionRef BinaryenLoad(BinaryenModuleRef module,
                                                 uint32_t offset,
                                                 uint32_t align,
                                                 BinaryenType type,
-                                                BinaryenExpressionRef ptr);
+                                                BinaryenExpressionRef ptr,
+                                                const char* memoryName);
 // Store: align can be 0, in which case it will be the natural alignment (equal
 // to bytes)
 BINARYEN_API BinaryenExpressionRef BinaryenStore(BinaryenModuleRef module,
@@ -713,7 +830,8 @@ BINARYEN_API BinaryenExpressionRef BinaryenStore(BinaryenModuleRef module,
                                                  uint32_t align,
                                                  BinaryenExpressionRef ptr,
                                                  BinaryenExpressionRef value,
-                                                 BinaryenType type);
+                                                 BinaryenType type,
+                                                 const char* memoryName);
 BINARYEN_API BinaryenExpressionRef BinaryenConst(BinaryenModuleRef module,
                                                  struct BinaryenLiteral value);
 BINARYEN_API BinaryenExpressionRef BinaryenUnary(BinaryenModuleRef module,
@@ -734,25 +852,31 @@ BINARYEN_API BinaryenExpressionRef BinaryenDrop(BinaryenModuleRef module,
 // Return: value can be NULL
 BINARYEN_API BinaryenExpressionRef BinaryenReturn(BinaryenModuleRef module,
                                                   BinaryenExpressionRef value);
-BINARYEN_API BinaryenExpressionRef BinaryenMemorySize(BinaryenModuleRef module);
-BINARYEN_API BinaryenExpressionRef
-BinaryenMemoryGrow(BinaryenModuleRef module, BinaryenExpressionRef delta);
+BINARYEN_API BinaryenExpressionRef BinaryenMemorySize(BinaryenModuleRef module,
+                                                      const char* memoryName,
+                                                      bool memoryIs64);
+BINARYEN_API BinaryenExpressionRef
+BinaryenMemoryGrow(BinaryenModuleRef module,
+                   BinaryenExpressionRef delta,
+                   const char* memoryName,
+                   bool memoryIs64);
 BINARYEN_API BinaryenExpressionRef BinaryenNop(BinaryenModuleRef module);
 BINARYEN_API BinaryenExpressionRef
 BinaryenUnreachable(BinaryenModuleRef module);
-BINARYEN_API BinaryenExpressionRef
-BinaryenAtomicLoad(BinaryenModuleRef module,
-                   uint32_t bytes,
-                   uint32_t offset,
-                   BinaryenType type,
-                   BinaryenExpressionRef ptr);
+BINARYEN_API BinaryenExpressionRef BinaryenAtomicLoad(BinaryenModuleRef module,
+                                                      uint32_t bytes,
+                                                      uint32_t offset,
+                                                      BinaryenType type,
+                                                      BinaryenExpressionRef ptr,
+                                                      const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenAtomicStore(BinaryenModuleRef module,
                     uint32_t bytes,
                     uint32_t offset,
                     BinaryenExpressionRef ptr,
                     BinaryenExpressionRef value,
-                    BinaryenType type);
+                    BinaryenType type,
+                    const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenAtomicRMW(BinaryenModuleRef module,
                   BinaryenOp op,
@@ -760,7 +884,8 @@ BinaryenAtomicRMW(BinaryenModuleRef module,
                   BinaryenIndex offset,
                   BinaryenExpressionRef ptr,
                   BinaryenExpressionRef value,
-                  BinaryenType type);
+                  BinaryenType type,
+                  const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenAtomicCmpxchg(BinaryenModuleRef module,
                       BinaryenIndex bytes,
@@ -768,17 +893,20 @@ BinaryenAtomicCmpxchg(BinaryenModuleRef module,
                       BinaryenExpressionRef ptr,
                       BinaryenExpressionRef expected,
                       BinaryenExpressionRef replacement,
-                      BinaryenType type);
+                      BinaryenType type,
+                      const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenAtomicWait(BinaryenModuleRef module,
                    BinaryenExpressionRef ptr,
                    BinaryenExpressionRef expected,
                    BinaryenExpressionRef timeout,
-                   BinaryenType type);
+                   BinaryenType type,
+                   const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenAtomicNotify(BinaryenModuleRef module,
                      BinaryenExpressionRef ptr,
-                     BinaryenExpressionRef notifyCount);
+                     BinaryenExpressionRef notifyCount,
+                     const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenAtomicFence(BinaryenModuleRef module);
 BINARYEN_API BinaryenExpressionRef
@@ -811,7 +939,8 @@ BINARYEN_API BinaryenExpressionRef BinaryenSIMDLoad(BinaryenModuleRef module,
                                                     BinaryenOp op,
                                                     uint32_t offset,
                                                     uint32_t align,
-                                                    BinaryenExpressionRef ptr);
+                                                    BinaryenExpressionRef ptr,
+                                                    const char* name);
 BINARYEN_API BinaryenExpressionRef
 BinaryenSIMDLoadStoreLane(BinaryenModuleRef module,
                           BinaryenOp op,
@@ -819,25 +948,30 @@ BinaryenSIMDLoadStoreLane(BinaryenModuleRef module,
                           uint32_t align,
                           uint8_t index,
                           BinaryenExpressionRef ptr,
-                          BinaryenExpressionRef vec);
+                          BinaryenExpressionRef vec,
+                          const char* memoryName);
 BINARYEN_API BinaryenExpressionRef
 BinaryenMemoryInit(BinaryenModuleRef module,
                    uint32_t segment,
                    BinaryenExpressionRef dest,
                    BinaryenExpressionRef offset,
-                   BinaryenExpressionRef size);
+                   BinaryenExpressionRef size,
+                   const char* memoryName);
 BINARYEN_API BinaryenExpressionRef BinaryenDataDrop(BinaryenModuleRef module,
                                                     uint32_t segment);
 BINARYEN_API BinaryenExpressionRef
 BinaryenMemoryCopy(BinaryenModuleRef module,
                    BinaryenExpressionRef dest,
                    BinaryenExpressionRef source,
-                   BinaryenExpressionRef size);
+                   BinaryenExpressionRef size,
+                   const char* destMemory,
+                   const char* sourceMemory);
 BINARYEN_API BinaryenExpressionRef
 BinaryenMemoryFill(BinaryenModuleRef module,
                    BinaryenExpressionRef dest,
                    BinaryenExpressionRef value,
-                   BinaryenExpressionRef size);
+                   BinaryenExpressionRef size,
+                   const char* memoryName);
 BINARYEN_API BinaryenExpressionRef BinaryenRefNull(BinaryenModuleRef module,
                                                    BinaryenType type);
 BINARYEN_API BinaryenExpressionRef BinaryenRefIs(BinaryenModuleRef module,
@@ -898,18 +1032,126 @@ BINARYEN_API BinaryenExpressionRef BinaryenI31New(BinaryenModuleRef module,
 BINARYEN_API BinaryenExpressionRef BinaryenI31Get(BinaryenModuleRef module,
                                                   BinaryenExpressionRef i31,
                                                   bool signed_);
-// TODO (gc): ref.test
-// TODO (gc): ref.cast
-// TODO (gc): br_on_cast
-// TODO (gc): rtt.canon
-// TODO (gc): rtt.sub
-// TODO (gc): struct.new
-// TODO (gc): struct.get
-// TODO (gc): struct.set
-// TODO (gc): array.new
-// TODO (gc): array.get
-// TODO (gc): array.set
-// TODO (gc): array.len
+BINARYEN_API BinaryenExpressionRef
+BinaryenCallRef(BinaryenModuleRef module,
+                BinaryenExpressionRef target,
+                BinaryenExpressionRef* operands,
+                BinaryenIndex numOperands,
+                BinaryenType type,
+                bool isReturn);
+BINARYEN_API BinaryenExpressionRef
+BinaryenRefTest(BinaryenModuleRef module,
+                BinaryenExpressionRef ref,
+                BinaryenHeapType intendedType);
+BINARYEN_API BinaryenExpressionRef
+BinaryenRefCast(BinaryenModuleRef module,
+                BinaryenExpressionRef ref,
+                BinaryenHeapType intendedType);
+BINARYEN_API BinaryenExpressionRef BinaryenBrOn(BinaryenModuleRef module,
+                                                BinaryenOp op,
+                                                const char* name,
+                                                BinaryenExpressionRef ref,
+                                                BinaryenHeapType intendedType);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStructNew(BinaryenModuleRef module,
+                  BinaryenExpressionRef* operands,
+                  BinaryenIndex numOperands,
+                  BinaryenHeapType type);
+BINARYEN_API BinaryenExpressionRef BinaryenStructGet(BinaryenModuleRef module,
+                                                     BinaryenIndex index,
+                                                     BinaryenExpressionRef ref,
+                                                     BinaryenType type,
+                                                     bool signed_);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStructSet(BinaryenModuleRef module,
+                  BinaryenIndex index,
+                  BinaryenExpressionRef ref,
+                  BinaryenExpressionRef value);
+BINARYEN_API BinaryenExpressionRef BinaryenArrayNew(BinaryenModuleRef module,
+                                                    BinaryenHeapType type,
+                                                    BinaryenExpressionRef size,
+                                                    BinaryenExpressionRef init);
+
+// TODO: BinaryenArrayNewSeg
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayInit(BinaryenModuleRef module,
+                  BinaryenHeapType type,
+                  BinaryenExpressionRef* values,
+                  BinaryenIndex numValues);
+BINARYEN_API BinaryenExpressionRef BinaryenArrayGet(BinaryenModuleRef module,
+                                                    BinaryenExpressionRef ref,
+                                                    BinaryenExpressionRef index,
+                                                    BinaryenType type,
+                                                    bool signed_);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArraySet(BinaryenModuleRef module,
+                 BinaryenExpressionRef ref,
+                 BinaryenExpressionRef index,
+                 BinaryenExpressionRef value);
+BINARYEN_API BinaryenExpressionRef BinaryenArrayLen(BinaryenModuleRef module,
+                                                    BinaryenExpressionRef ref);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayCopy(BinaryenModuleRef module,
+                  BinaryenExpressionRef destRef,
+                  BinaryenExpressionRef destIndex,
+                  BinaryenExpressionRef srcRef,
+                  BinaryenExpressionRef srcIndex,
+                  BinaryenExpressionRef length);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringNew(BinaryenModuleRef module,
+                  BinaryenOp op,
+                  BinaryenExpressionRef ptr,
+                  BinaryenExpressionRef length,
+                  BinaryenExpressionRef start,
+                  BinaryenExpressionRef end);
+BINARYEN_API BinaryenExpressionRef BinaryenStringConst(BinaryenModuleRef module,
+                                                       const char* name);
+BINARYEN_API BinaryenExpressionRef BinaryenStringMeasure(
+  BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef ref);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEncode(BinaryenModuleRef module,
+                     BinaryenOp op,
+                     BinaryenExpressionRef ref,
+                     BinaryenExpressionRef ptr,
+                     BinaryenExpressionRef start);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringConcat(BinaryenModuleRef module,
+                     BinaryenExpressionRef left,
+                     BinaryenExpressionRef right);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEq(BinaryenModuleRef module,
+                 BinaryenExpressionRef left,
+                 BinaryenExpressionRef right);
+BINARYEN_API BinaryenExpressionRef BinaryenStringAs(BinaryenModuleRef module,
+                                                    BinaryenOp op,
+                                                    BinaryenExpressionRef ref);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF8Advance(BinaryenModuleRef module,
+                          BinaryenExpressionRef ref,
+                          BinaryenExpressionRef pos,
+                          BinaryenExpressionRef bytes);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF16Get(BinaryenModuleRef module,
+                       BinaryenExpressionRef ref,
+                       BinaryenExpressionRef pos);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringIterNext(BinaryenModuleRef module, BinaryenExpressionRef ref);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringIterMove(BinaryenModuleRef module,
+                       BinaryenOp op,
+                       BinaryenExpressionRef ref,
+                       BinaryenExpressionRef num);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceWTF(BinaryenModuleRef module,
+                       BinaryenOp op,
+                       BinaryenExpressionRef ref,
+                       BinaryenExpressionRef start,
+                       BinaryenExpressionRef end);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceIter(BinaryenModuleRef module,
+                        BinaryenExpressionRef ref,
+                        BinaryenExpressionRef num);
 
 // Expression
 
@@ -2090,6 +2332,378 @@ BINARYEN_API bool BinaryenI31GetIsSigned(BinaryenExpressionRef expr);
 BINARYEN_API void BinaryenI31GetSetSigned(BinaryenExpressionRef expr,
                                           bool signed_);
 
+// CallRef
+
+BINARYEN_API BinaryenIndex
+BinaryenCallRefGetNumOperands(BinaryenExpressionRef expr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenCallRefGetOperandAt(BinaryenExpressionRef expr, BinaryenIndex index);
+BINARYEN_API void
+BinaryenCallRefSetOperandAt(BinaryenExpressionRef expr,
+                            BinaryenIndex index,
+                            BinaryenExpressionRef operandExpr);
+BINARYEN_API BinaryenIndex BinaryenCallRefAppendOperand(
+  BinaryenExpressionRef expr, BinaryenExpressionRef operandExpr);
+BINARYEN_API void
+BinaryenCallRefInsertOperandAt(BinaryenExpressionRef expr,
+                               BinaryenIndex index,
+                               BinaryenExpressionRef operandExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenCallRefRemoveOperandAt(BinaryenExpressionRef expr, BinaryenIndex index);
+BINARYEN_API BinaryenExpressionRef
+BinaryenCallRefGetTarget(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenCallRefSetTarget(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef targetExpr);
+BINARYEN_API bool BinaryenCallRefIsReturn(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenCallRefSetReturn(BinaryenExpressionRef expr,
+                                           bool isReturn);
+
+// RefTest
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenRefTestGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenRefTestSetRef(BinaryenExpressionRef expr,
+                                        BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenHeapType
+BinaryenRefTestGetIntendedType(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenRefTestSetIntendedType(BinaryenExpressionRef expr,
+                                                 BinaryenHeapType intendedType);
+
+// RefCast
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenRefCastGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenRefCastSetRef(BinaryenExpressionRef expr,
+                                        BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenHeapType
+BinaryenRefCastGetIntendedType(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenRefCastSetIntendedType(BinaryenExpressionRef expr,
+                                                 BinaryenHeapType intendedType);
+
+// BrOn
+
+BINARYEN_API BinaryenOp BinaryenBrOnGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenBrOnSetOp(BinaryenExpressionRef expr, BinaryenOp op);
+BINARYEN_API const char* BinaryenBrOnGetName(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenBrOnSetName(BinaryenExpressionRef expr,
+                                      const char* nameStr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenBrOnGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenBrOnSetRef(BinaryenExpressionRef expr,
+                                     BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenHeapType
+BinaryenBrOnGetIntendedType(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenBrOnSetIntendedType(BinaryenExpressionRef expr,
+                                              BinaryenHeapType intendedType);
+
+// StructNew
+
+BINARYEN_API BinaryenIndex
+BinaryenStructNewGetNumOperands(BinaryenExpressionRef expr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStructNewGetOperandAt(BinaryenExpressionRef expr, BinaryenIndex index);
+BINARYEN_API void
+BinaryenStructNewSetOperandAt(BinaryenExpressionRef expr,
+                              BinaryenIndex index,
+                              BinaryenExpressionRef operandExpr);
+BINARYEN_API BinaryenIndex BinaryenStructNewAppendOperand(
+  BinaryenExpressionRef expr, BinaryenExpressionRef operandExpr);
+BINARYEN_API void
+BinaryenStructNewInsertOperandAt(BinaryenExpressionRef expr,
+                                 BinaryenIndex index,
+                                 BinaryenExpressionRef operandExpr);
+BINARYEN_API BinaryenExpressionRef BinaryenStructNewRemoveOperandAt(
+  BinaryenExpressionRef expr, BinaryenIndex index);
+
+// StructGet
+
+BINARYEN_API BinaryenIndex
+BinaryenStructGetGetIndex(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStructGetSetIndex(BinaryenExpressionRef expr,
+                                            BinaryenIndex index);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStructGetGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStructGetSetRef(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef refExpr);
+BINARYEN_API bool BinaryenStructGetIsSigned(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStructGetSetSigned(BinaryenExpressionRef expr,
+                                             bool signed_);
+
+// StructSet
+
+BINARYEN_API BinaryenIndex
+BinaryenStructSetGetIndex(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStructSetSetIndex(BinaryenExpressionRef expr,
+                                            BinaryenIndex index);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStructSetGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStructSetSetRef(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStructSetGetValue(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStructSetSetValue(BinaryenExpressionRef expr,
+                                            BinaryenExpressionRef valueExpr);
+
+// ArrayNew
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayNewGetInit(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayNewSetInit(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef initExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayNewGetSize(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayNewSetSize(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef sizeExpr);
+
+// ArrayInit
+
+BINARYEN_API BinaryenIndex
+BinaryenArrayInitGetNumValues(BinaryenExpressionRef expr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayInitGetValueAt(BinaryenExpressionRef expr, BinaryenIndex index);
+BINARYEN_API void BinaryenArrayInitSetValueAt(BinaryenExpressionRef expr,
+                                              BinaryenIndex index,
+                                              BinaryenExpressionRef valueExpr);
+BINARYEN_API BinaryenIndex BinaryenArrayInitAppendValue(
+  BinaryenExpressionRef expr, BinaryenExpressionRef valueExpr);
+BINARYEN_API void
+BinaryenArrayInitInsertValueAt(BinaryenExpressionRef expr,
+                               BinaryenIndex index,
+                               BinaryenExpressionRef valueExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayInitRemoveValueAt(BinaryenExpressionRef expr, BinaryenIndex index);
+
+// ArrayGet
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayGetGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayGetSetRef(BinaryenExpressionRef expr,
+                                         BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayGetGetIndex(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayGetSetIndex(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef indexExpr);
+BINARYEN_API bool BinaryenArrayGetIsSigned(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayGetSetSigned(BinaryenExpressionRef expr,
+                                            bool signed_);
+
+// ArraySet
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenArraySetGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArraySetSetRef(BinaryenExpressionRef expr,
+                                         BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArraySetGetIndex(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArraySetSetIndex(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef indexExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArraySetGetValue(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArraySetSetValue(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef valueExpr);
+
+// ArrayLen
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayLenGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayLenSetRef(BinaryenExpressionRef expr,
+                                         BinaryenExpressionRef refExpr);
+
+// ArrayCopy
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayCopyGetDestRef(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenArrayCopySetDestRef(BinaryenExpressionRef expr,
+                            BinaryenExpressionRef destRefExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayCopyGetDestIndex(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenArrayCopySetDestIndex(BinaryenExpressionRef expr,
+                              BinaryenExpressionRef destIndexExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayCopyGetSrcRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayCopySetSrcRef(BinaryenExpressionRef expr,
+                                             BinaryenExpressionRef srcRefExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayCopyGetSrcIndex(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenArrayCopySetSrcIndex(BinaryenExpressionRef expr,
+                             BinaryenExpressionRef srcIndexExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenArrayCopyGetLength(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenArrayCopySetLength(BinaryenExpressionRef expr,
+                                             BinaryenExpressionRef lengthExpr);
+
+// StringNew
+
+BINARYEN_API BinaryenOp BinaryenStringNewGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringNewSetOp(BinaryenExpressionRef expr,
+                                         BinaryenOp op);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringNewGetPtr(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringNewSetPtr(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef ptrExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringNewGetLength(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringNewSetLength(BinaryenExpressionRef expr,
+                                             BinaryenExpressionRef lengthExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringNewGetStart(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringNewSetStart(BinaryenExpressionRef expr,
+                                            BinaryenExpressionRef startExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringNewGetEnd(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringNewSetEnd(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef endExpr);
+
+// StringConst
+
+BINARYEN_API const char*
+BinaryenStringConstGetString(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringConstSetString(BinaryenExpressionRef expr,
+                                               const char* stringStr);
+
+// StringMeasure
+
+BINARYEN_API BinaryenOp BinaryenStringMeasureGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringMeasureSetOp(BinaryenExpressionRef expr,
+                                             BinaryenOp op);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringMeasureGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringMeasureSetRef(BinaryenExpressionRef expr,
+                                              BinaryenExpressionRef refExpr);
+
+// StringEncode
+
+BINARYEN_API BinaryenOp BinaryenStringEncodeGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringEncodeSetOp(BinaryenExpressionRef expr,
+                                            BinaryenOp op);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEncodeGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringEncodeSetRef(BinaryenExpressionRef expr,
+                                             BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEncodeGetPtr(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringEncodeSetPtr(BinaryenExpressionRef expr,
+                                             BinaryenExpressionRef ptrExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEncodeGetStart(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringEncodeSetStart(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef startExpr);
+
+// StringConcat
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringConcatGetLeft(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringConcatSetLeft(BinaryenExpressionRef expr,
+                                              BinaryenExpressionRef leftExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringConcatGetRight(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringConcatSetRight(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef rightExpr);
+
+// StringEq
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEqGetLeft(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringEqSetLeft(BinaryenExpressionRef expr,
+                                          BinaryenExpressionRef leftExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringEqGetRight(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringEqSetRight(BinaryenExpressionRef expr,
+                                           BinaryenExpressionRef rightExpr);
+
+// StringAs
+
+BINARYEN_API BinaryenOp BinaryenStringAsGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringAsSetOp(BinaryenExpressionRef expr,
+                                        BinaryenOp op);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringAsGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringAsSetRef(BinaryenExpressionRef expr,
+                                         BinaryenExpressionRef refExpr);
+
+// StringWTF8Advance
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF8AdvanceGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenStringWTF8AdvanceSetRef(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF8AdvanceGetPos(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenStringWTF8AdvanceSetPos(BinaryenExpressionRef expr,
+                                BinaryenExpressionRef posExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF8AdvanceGetBytes(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenStringWTF8AdvanceSetBytes(BinaryenExpressionRef expr,
+                                  BinaryenExpressionRef bytesExpr);
+
+// StringWTF16Get
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF16GetGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringWTF16GetSetRef(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringWTF16GetGetPos(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringWTF16GetSetPos(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef posExpr);
+
+// StringIterNext
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringIterNextGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringIterNextSetRef(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef refExpr);
+
+// StringIterMove
+
+BINARYEN_API BinaryenOp BinaryenStringIterMoveGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringIterMoveSetOp(BinaryenExpressionRef expr,
+                                              BinaryenOp op);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringIterMoveGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringIterMoveSetRef(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringIterMoveGetNum(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringIterMoveSetNum(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef numExpr);
+
+// StringSliceWTF
+
+BINARYEN_API BinaryenOp BinaryenStringSliceWTFGetOp(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringSliceWTFSetOp(BinaryenExpressionRef expr,
+                                              BinaryenOp op);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceWTFGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringSliceWTFSetRef(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceWTFGetStart(BinaryenExpressionRef expr);
+BINARYEN_API void
+BinaryenStringSliceWTFSetStart(BinaryenExpressionRef expr,
+                               BinaryenExpressionRef startExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceWTFGetEnd(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringSliceWTFSetEnd(BinaryenExpressionRef expr,
+                                               BinaryenExpressionRef endExpr);
+
+// StringSliceIter
+
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceIterGetRef(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringSliceIterSetRef(BinaryenExpressionRef expr,
+                                                BinaryenExpressionRef refExpr);
+BINARYEN_API BinaryenExpressionRef
+BinaryenStringSliceIterGetNum(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenStringSliceIterSetNum(BinaryenExpressionRef expr,
+                                                BinaryenExpressionRef numExpr);
+
 // Functions
 
 BINARYEN_REF(Function);
@@ -2126,6 +2740,10 @@ BinaryenGetFunctionByIndex(BinaryenModuleRef module, BinaryenIndex index);
 
 // Imports
 
+// These either create a new entity (function/table/memory/etc.) and
+// mark it as an import, or, if an entity already exists with internalName then
+// the existing entity is turned into an import.
+
 BINARYEN_API void BinaryenAddFunctionImport(BinaryenModuleRef module,
                                             const char* internalName,
                                             const char* externalModuleName,
@@ -2154,6 +2772,9 @@ BINARYEN_API void BinaryenAddTagImport(BinaryenModuleRef module,
                                        BinaryenType params,
                                        BinaryenType results);
 
+// Memory
+BINARYEN_REF(Memory);
+
 // Exports
 
 BINARYEN_REF(Export);
@@ -2270,8 +2891,7 @@ BinaryenGetElementSegment(BinaryenModuleRef module, const char* name);
 BINARYEN_API BinaryenElementSegmentRef
 BinaryenGetElementSegmentByIndex(BinaryenModuleRef module, BinaryenIndex index);
 
-// Memory. One per module
-
+// This will create a memory, overwriting any existing memory
 // Each memory has data in segments, a start offset in segmentOffsets, and a
 // size in segmentSizes. exportName can be NULL
 BINARYEN_API void BinaryenSetMemory(BinaryenModuleRef module,
@@ -2283,7 +2903,25 @@ BINARYEN_API void BinaryenSetMemory(BinaryenModuleRef module,
                                     BinaryenExpressionRef* segmentOffsets,
                                     BinaryenIndex* segmentSizes,
                                     BinaryenIndex numSegments,
-                                    bool shared);
+                                    bool shared,
+                                    bool memory64,
+                                    const char* name);
+
+BINARYEN_API bool BinaryenHasMemory(BinaryenModuleRef module);
+BINARYEN_API BinaryenIndex BinaryenMemoryGetInitial(BinaryenModuleRef module,
+                                                    const char* name);
+BINARYEN_API bool BinaryenMemoryHasMax(BinaryenModuleRef module,
+                                       const char* name);
+BINARYEN_API BinaryenIndex BinaryenMemoryGetMax(BinaryenModuleRef module,
+                                                const char* name);
+BINARYEN_API const char* BinaryenMemoryImportGetModule(BinaryenModuleRef module,
+                                                       const char* name);
+BINARYEN_API const char* BinaryenMemoryImportGetBase(BinaryenModuleRef module,
+                                                     const char* name);
+BINARYEN_API bool BinaryenMemoryIsShared(BinaryenModuleRef module,
+                                         const char* name);
+BINARYEN_API bool BinaryenMemoryIs64(BinaryenModuleRef module,
+                                     const char* name);
 
 // Memory segments. Query utilities.
 
@@ -2321,6 +2959,10 @@ BINARYEN_API BinaryenModuleRef BinaryenModuleParse(const char* text);
 // Print a module to stdout in s-expression text format. Useful for debugging.
 BINARYEN_API void BinaryenModulePrint(BinaryenModuleRef module);
 
+// Print a module to stdout in stack IR text format. Useful for debugging.
+BINARYEN_API void BinaryenModulePrintStackIR(BinaryenModuleRef module,
+                                             bool optimize);
+
 // Print a module to stdout in asm.js syntax.
 BINARYEN_API void BinaryenModulePrintAsmjs(BinaryenModuleRef module);
 
@@ -2455,6 +3097,14 @@ BINARYEN_API size_t BinaryenModuleWriteText(BinaryenModuleRef module,
                                             char* output,
                                             size_t outputSize);
 
+// Serialize a module in stack IR text format.
+// @return how many bytes were written. This will be less than or equal to
+//         outputSize
+BINARYEN_API size_t BinaryenModuleWriteStackIR(BinaryenModuleRef module,
+                                               char* output,
+                                               size_t outputSize,
+                                               bool optimize);
+
 typedef struct BinaryenBufferSizes {
   size_t outputBytes;
   size_t sourceMapBytes;
@@ -2495,6 +3145,12 @@ BinaryenModuleAllocateAndWrite(BinaryenModuleRef module,
 // once not needed anymore.
 BINARYEN_API char* BinaryenModuleAllocateAndWriteText(BinaryenModuleRef module);
 
+// Serialize a module in stack IR form. Implicitly allocates the returned
+// char* with malloc(), and expects the user to free() them manually
+// once not needed anymore.
+BINARYEN_API char*
+BinaryenModuleAllocateAndWriteStackIR(BinaryenModuleRef module, bool optimize);
+
 // Deserialize a module from binary form.
 BINARYEN_API BinaryenModuleRef BinaryenModuleRead(char* input,
                                                   size_t inputSize);
@@ -2836,6 +3492,114 @@ BINARYEN_API bool ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner,
 BINARYEN_API BinaryenExpressionRef ExpressionRunnerRunAndDispose(
   ExpressionRunnerRef runner, BinaryenExpressionRef expr);
 
+//
+// ========= TypeBuilder =========
+//
+
+#ifdef __cplusplus
+namespace wasm {
+struct TypeBuilder;
+} // namespace wasm
+typedef struct wasm::TypeBuilder* TypeBuilderRef;
+#else
+typedef struct TypeBuilder* TypeBuilderRef;
+#endif
+
+typedef uint32_t TypeBuilderErrorReason;
+
+// Indicates a cycle in the supertype relation.
+BINARYEN_API TypeBuilderErrorReason TypeBuilderErrorReasonSelfSupertype(void);
+// Indicates that the declared supertype of a type is invalid.
+BINARYEN_API TypeBuilderErrorReason
+TypeBuilderErrorReasonInvalidSupertype(void);
+// Indicates that the declared supertype is an invalid forward reference.
+BINARYEN_API TypeBuilderErrorReason
+TypeBuilderErrorReasonForwardSupertypeReference(void);
+// Indicates that a child of a type is an invalid forward reference.
+BINARYEN_API TypeBuilderErrorReason
+TypeBuilderErrorReasonForwardChildReference(void);
+
+typedef uint32_t BinaryenBasicHeapType;
+
+// Constructs a new type builder that allows for the construction of recursive
+// types. Contains a table of `size` mutable heap types.
+BINARYEN_API TypeBuilderRef TypeBuilderCreate(BinaryenIndex size);
+// Grows the backing table of the type builder by `count` slots.
+BINARYEN_API void TypeBuilderGrow(TypeBuilderRef builder, BinaryenIndex count);
+// Gets the size of the backing table of the type builder.
+BINARYEN_API BinaryenIndex TypeBuilderGetSize(TypeBuilderRef builder);
+// Sets the heap type at index `index` to a basic heap type. Must not be used in
+// nominal mode.
+BINARYEN_API void
+TypeBuilderSetBasicHeapType(TypeBuilderRef builder,
+                            BinaryenIndex index,
+                            BinaryenBasicHeapType basicHeapType);
+// Sets the heap type at index `index` to a concrete signature type. Expects
+// temporary tuple types if multiple parameter and/or result types include
+// temporary types.
+BINARYEN_API void TypeBuilderSetSignatureType(TypeBuilderRef builder,
+                                              BinaryenIndex index,
+                                              BinaryenType paramTypes,
+                                              BinaryenType resultTypes);
+// Sets the heap type at index `index` to a concrete struct type.
+BINARYEN_API void TypeBuilderSetStructType(TypeBuilderRef builder,
+                                           BinaryenIndex index,
+                                           BinaryenType* fieldTypes,
+                                           BinaryenPackedType* fieldPackedTypes,
+                                           bool* fieldMutables,
+                                           int numFields);
+// Sets the heap type at index `index` to a concrete array type.
+BINARYEN_API void TypeBuilderSetArrayType(TypeBuilderRef builder,
+                                          BinaryenIndex index,
+                                          BinaryenType elementType,
+                                          BinaryenPackedType elementPackedType,
+                                          int elementMutable);
+// Tests if the heap type at index `index` is a basic heap type.
+BINARYEN_API bool TypeBuilderIsBasic(TypeBuilderRef builder,
+                                     BinaryenIndex index);
+// Gets the basic heap type at index `index`.
+BINARYEN_API BinaryenBasicHeapType TypeBuilderGetBasic(TypeBuilderRef builder,
+                                                       BinaryenIndex index);
+// Gets the temporary heap type to use at index `index`. Temporary heap types
+// may only be used to construct temporary types using the type builder.
+BINARYEN_API BinaryenHeapType TypeBuilderGetTempHeapType(TypeBuilderRef builder,
+                                                         BinaryenIndex index);
+// Gets a temporary tuple type for use with and owned by the type builder.
+BINARYEN_API BinaryenType TypeBuilderGetTempTupleType(TypeBuilderRef builder,
+                                                      BinaryenType* types,
+                                                      BinaryenIndex numTypes);
+// Gets a temporary reference type for use with and owned by the type builder.
+BINARYEN_API BinaryenType TypeBuilderGetTempRefType(TypeBuilderRef builder,
+                                                    BinaryenHeapType heapType,
+                                                    int nullable);
+// Sets the type at `index` to be a subtype of the given super type.
+BINARYEN_API void TypeBuilderSetSubType(TypeBuilderRef builder,
+                                        BinaryenIndex index,
+                                        BinaryenHeapType superType);
+// Creates a new recursion group in the range `index` inclusive to `index +
+// length` exclusive. Recursion groups must not overlap.
+BINARYEN_API void TypeBuilderCreateRecGroup(TypeBuilderRef builder,
+                                            BinaryenIndex index,
+                                            BinaryenIndex length);
+// Builds the heap type hierarchy and disposes the builder. Returns `false` and
+// populates `errorIndex` and `errorReason` on failure.
+BINARYEN_API bool
+TypeBuilderBuildAndDispose(TypeBuilderRef builder,
+                           BinaryenHeapType* heapTypes,
+                           BinaryenIndex* errorIndex,
+                           TypeBuilderErrorReason* errorReason);
+
+// Sets the textual name of a compound `heapType`. Has no effect if the type
+// already has a canonical name.
+BINARYEN_API void BinaryenModuleSetTypeName(BinaryenModuleRef module,
+                                            BinaryenHeapType heapType,
+                                            const char* name);
+// Sets the field name of a struct `heapType` at index `index`.
+BINARYEN_API void BinaryenModuleSetFieldName(BinaryenModuleRef module,
+                                             BinaryenHeapType heapType,
+                                             BinaryenIndex index,
+                                             const char* name);
+
 //
 // ========= Utilities =========
 //
diff --git a/src/cfg/Relooper.cpp b/src/cfg/Relooper.cpp
index 1cdcbe2..80d1926 100644
--- a/src/cfg/Relooper.cpp
+++ b/src/cfg/Relooper.cpp
@@ -526,7 +526,7 @@ LoopShape* Relooper::AddLoopShape() {
 
 namespace {
 
-typedef std::list<Block*> BlockList;
+using BlockList = std::list<Block*>;
 
 struct RelooperRecursor {
   Relooper* Parent;
@@ -554,7 +554,7 @@ struct Liveness : public RelooperRecursor {
   }
 };
 
-typedef std::pair<Branch*, Block*> BranchBlock;
+using BranchBlock = std::pair<Branch*, Block*>;
 
 struct Optimizer : public RelooperRecursor {
   Block* Entry;
@@ -1251,7 +1251,7 @@ void Relooper::Calculate(Block* Entry) {
     void FindIndependentGroups(BlockSet& Entries,
                                BlockBlockSetMap& IndependentGroups,
                                BlockSet* Ignore = nullptr) {
-      typedef std::map<Block*, Block*> BlockBlockMap;
+      using BlockBlockMap = std::map<Block*, Block*>;
 
       struct HelperClass {
         BlockBlockSetMap& IndependentGroups;
diff --git a/src/cfg/Relooper.h b/src/cfg/Relooper.h
index 37d262e..7fa23b2 100644
--- a/src/cfg/Relooper.h
+++ b/src/cfg/Relooper.h
@@ -124,8 +124,8 @@ struct Branch {
   Render(RelooperBuilder& Builder, Block* Target, bool SetLabel);
 };
 
-typedef wasm::InsertOrderedSet<Block*> BlockSet;
-typedef wasm::InsertOrderedMap<Block*, Branch*> BlockBranchMap;
+using BlockSet = wasm::InsertOrderedSet<Block*>;
+using BlockBranchMap = wasm::InsertOrderedMap<Block*, Branch*>;
 
 // Represents a basic block of code - some instructions that end with a
 // control flow modifier (a branch, return or throw).
@@ -241,7 +241,7 @@ struct SimpleShape : public Shape {
   wasm::Expression* Render(RelooperBuilder& Builder, bool InLoop) override;
 };
 
-typedef std::map<int, Shape*> IdShapeMap;
+using IdShapeMap = std::map<int, Shape*>;
 
 struct MultipleShape : public Shape {
   IdShapeMap InnerMap; // entry block ID -> shape
@@ -313,7 +313,7 @@ struct Relooper {
   void SetMinSize(bool MinSize_) { MinSize = MinSize_; }
 };
 
-typedef wasm::InsertOrderedMap<Block*, BlockSet> BlockBlockSetMap;
+using BlockBlockSetMap = wasm::InsertOrderedMap<Block*, BlockSet>;
 
 #ifdef RELOOPER_DEBUG
 struct Debugging {
diff --git a/src/cfg/cfg-traversal.h b/src/cfg/cfg-traversal.h
index faaa3c8..3a15143 100644
--- a/src/cfg/cfg-traversal.h
+++ b/src/cfg/cfg-traversal.h
@@ -277,6 +277,7 @@ struct CFGWalker : public ControlFlowWalker<SubType, VisitorType> {
             break;
           }
         }
+        WASM_UNUSED(found);
         assert(found);
         continue;
       }
@@ -392,7 +393,8 @@ struct CFGWalker : public ControlFlowWalker<SubType, VisitorType> {
         break;
       }
       case Expression::Id::CallId:
-      case Expression::Id::CallIndirectId: {
+      case Expression::Id::CallIndirectId:
+      case Expression::Id::CallRefId: {
         self->pushTask(SubType::doEndCall, currp);
         break;
       }
diff --git a/src/cfg/liveness-traversal.h b/src/cfg/liveness-traversal.h
index 3193cbd..d9b310d 100644
--- a/src/cfg/liveness-traversal.h
+++ b/src/cfg/liveness-traversal.h
@@ -36,7 +36,7 @@ namespace wasm {
 // may be a great many potential elements but actual sets
 // may be fairly small. Specifically, we use a sorted
 // vector.
-typedef SortedVector SetOfLocals;
+using SetOfLocals = SortedVector;
 
 // A liveness-relevant action. Supports a get, a set, or an
 // "other" which can be used for other purposes, to mark
diff --git a/src/dataflow/graph.h b/src/dataflow/graph.h
index 0380adf..6bd01d8 100644
--- a/src/dataflow/graph.h
+++ b/src/dataflow/graph.h
@@ -84,7 +84,7 @@ struct Graph : public UnifiedExpressionVisitor<Graph, Node*> {
   // When we are in unreachable code (i.e., a path that does not
   // need to be merged in anywhere), we set the length of this
   // vector to 0 to indicate that.
-  typedef std::vector<Node*> Locals;
+  using Locals = std::vector<Node*>;
 
   // The current local state in the control flow path being emitted.
   Locals locals;
diff --git a/src/dataflow/users.h b/src/dataflow/users.h
index faa41f7..d8bee8a 100644
--- a/src/dataflow/users.h
+++ b/src/dataflow/users.h
@@ -34,7 +34,7 @@ namespace wasm::DataFlow {
 // where y, z etc. are nodes that use x, that is, x is in their
 // values vector.
 class Users {
-  typedef std::unordered_set<DataFlow::Node*> UserSet;
+  using UserSet = std::unordered_set<DataFlow::Node*>;
 
   std::unordered_map<DataFlow::Node*, UserSet> users;
 
diff --git a/src/dataflow/utils.h b/src/dataflow/utils.h
index af59c41..4e88ed1 100644
--- a/src/dataflow/utils.h
+++ b/src/dataflow/utils.h
@@ -32,11 +32,7 @@
 namespace wasm::DataFlow {
 
 inline std::ostream& dump(Node* node, std::ostream& o, size_t indent = 0) {
-  auto doIndent = [&]() {
-    for (size_t i = 0; i < indent; i++) {
-      o << ' ';
-    }
-  };
+  auto doIndent = [&]() { o << std::string(indent, ' '); };
   doIndent();
   o << '[' << node << ' ';
   switch (node->type) {
diff --git a/src/emscripten-optimizer/istring.h b/src/emscripten-optimizer/istring.h
deleted file mode 100644
index 69df761..0000000
--- a/src/emscripten-optimizer/istring.h
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright 2015 WebAssembly Community Group participants
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Interned String type, 100% interned on creation. Comparisons are always just
-// a pointer comparison
-
-#ifndef wasm_istring_h
-#define wasm_istring_h
-
-#include <set>
-#include <unordered_map>
-#include <unordered_set>
-
-#include <assert.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "support/threads.h"
-#include "support/utilities.h"
-
-namespace cashew {
-
-struct IString {
-  const char* str = nullptr;
-
-  static size_t
-  hash_c(const char* str) { // see http://www.cse.yorku.ca/~oz/hash.html
-    unsigned int hash = 5381;
-    int c;
-    while ((c = *str++)) {
-      hash = ((hash << 5) + hash) ^ c;
-    }
-    return (size_t)hash;
-  }
-
-  class CStringHash {
-  public:
-    size_t operator()(const char* str) const { return IString::hash_c(str); }
-  };
-  class CStringEqual {
-  public:
-    bool operator()(const char* x, const char* y) const {
-      return strcmp(x, y) == 0;
-    }
-  };
-
-  IString() = default;
-  // if reuse=true, then input is assumed to remain alive; not copied
-  IString(const char* s, bool reuse = true) {
-    assert(s);
-    set(s, reuse);
-  }
-
-  void set(const char* s, bool reuse = true) {
-    typedef std::unordered_set<const char*, CStringHash, CStringEqual>
-      StringSet;
-    // one global store of strings per thread, we must not access this
-    // in parallel
-    thread_local static StringSet strings;
-
-    auto existing = strings.find(s);
-
-    if (existing == strings.end()) {
-      // if the string isn't already known, we must use a single global
-      // storage location, guarded by a mutex, so each string is allocated
-      // exactly once
-      static std::mutex mutex;
-      std::unique_lock<std::mutex> lock(mutex);
-      // a single global set contains the actual strings, so we allocate each
-      // one exactly once.
-      static StringSet globalStrings;
-      auto globalExisting = globalStrings.find(s);
-      if (globalExisting == globalStrings.end()) {
-        if (!reuse) {
-          static std::vector<std::unique_ptr<std::string>> allocated;
-          allocated.emplace_back(wasm::make_unique<std::string>(s));
-          s = allocated.back()->c_str(); // we'll never modify it, so this is ok
-        }
-        // insert into global set
-        globalStrings.insert(s);
-      } else {
-        s = *globalExisting;
-      }
-      // add the string to our thread-local set
-      strings.insert(s);
-    } else {
-      s = *existing;
-    }
-
-    str = s;
-  }
-
-  void set(const IString& s) { str = s.str; }
-
-  void clear() { str = nullptr; }
-
-  bool operator==(const IString& other) const {
-    // assert((str == other.str) == !strcmp(str, other.str));
-    return str == other.str; // fast!
-  }
-  bool operator!=(const IString& other) const {
-    // assert((str == other.str) == !strcmp(str, other.str));
-    return str != other.str; // fast!
-  }
-  bool operator<(const IString& other) const {
-    return strcmp(str ? str : "", other.str ? other.str : "") < 0;
-  }
-
-  char operator[](int x) const { return str[x]; }
-
-  bool operator!() const { // no string, or empty string
-    return !str || str[0] == 0;
-  }
-
-  const char* c_str() const { return str; }
-  bool equals(const char* other) const { return !strcmp(str, other); }
-
-  bool is() const { return str != nullptr; }
-  bool isNull() const { return str == nullptr; }
-
-  const char* stripPrefix(const char* prefix) const {
-    const char* ptr = str;
-    while (true) {
-      if (*prefix == 0) {
-        return ptr;
-      }
-      if (*ptr == 0) {
-        return nullptr;
-      }
-      if (*ptr++ != *prefix++) {
-        return nullptr;
-      }
-    }
-  }
-
-  bool startsWith(const char* prefix) const {
-    return stripPrefix(prefix) != nullptr;
-  }
-  bool startsWith(const IString& prefix) const {
-    return startsWith(prefix.str);
-  }
-
-  size_t size() const { return str ? strlen(str) : 0; }
-};
-
-} // namespace cashew
-
-// Utilities for creating hashmaps/sets over IStrings
-
-namespace std {
-
-template<> struct hash<cashew::IString> {
-  size_t operator()(const cashew::IString& str) const {
-    return std::hash<size_t>{}(size_t(str.str));
-  }
-};
-
-template<> struct equal_to<cashew::IString> {
-  bool operator()(const cashew::IString& x, const cashew::IString& y) const {
-    return x == y;
-  }
-};
-
-} // namespace std
-
-namespace cashew {
-
-// IStringSet
-
-class IStringSet : public std::unordered_set<IString> {
-  std::vector<char> data;
-
-public:
-  IStringSet() = default;
-  IStringSet(const char* init) { // comma-delimited list
-    int size = strlen(init) + 1;
-    data.resize(size);
-    char* curr = &data[0];
-    strncpy(curr, init, size);
-    while (1) {
-      char* end = strchr(curr, ' ');
-      if (end) {
-        *end = 0;
-      }
-      insert(curr);
-      if (!end) {
-        break;
-      }
-      curr = end + 1;
-    }
-  }
-
-  bool has(const IString& str) { return count(str) > 0; }
-};
-
-class IOrderedStringSet : public std::set<IString> {
-public:
-  bool has(const IString& str) { return count(str) > 0; }
-};
-
-} // namespace cashew
-
-#endif // wasm_istring_h
diff --git a/src/emscripten-optimizer/optimizer.h b/src/emscripten-optimizer/optimizer.h
index 132ff6f..a27fe25 100644
--- a/src/emscripten-optimizer/optimizer.h
+++ b/src/emscripten-optimizer/optimizer.h
@@ -19,7 +19,7 @@
 
 #include "simple_ast.h"
 
-using namespace cashew;
+using IString = wasm::IString;
 
 extern IString JS_FLOAT_ZERO;
 
@@ -50,8 +50,8 @@ enum JsSign {
   JS_NONSIGNED,
 };
 
-Ref makeJsCoercedZero(JsType type);
-Ref makeJsCoercion(Ref node, JsType type);
-Ref makeSigning(Ref node, JsSign sign);
+cashew::Ref makeJsCoercedZero(JsType type);
+cashew::Ref makeJsCoercion(cashew::Ref node, JsType type);
+cashew::Ref makeSigning(cashew::Ref node, JsSign sign);
 
 #endif // wasm_optimizer_h
diff --git a/src/emscripten-optimizer/parser.cpp b/src/emscripten-optimizer/parser.cpp
index 0169662..5330432 100644
--- a/src/emscripten-optimizer/parser.cpp
+++ b/src/emscripten-optimizer/parser.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <unordered_map>
+
 #include "parser.h"
 
 namespace cashew {
diff --git a/src/emscripten-optimizer/parser.h b/src/emscripten-optimizer/parser.h
index c4d5960..f1d4728 100644
--- a/src/emscripten-optimizer/parser.h
+++ b/src/emscripten-optimizer/parser.h
@@ -30,11 +30,46 @@
 #include <limits>
 #include <vector>
 
-#include "istring.h"
+#include "support/istring.h"
 #include "support/safe_integer.h"
 
 namespace cashew {
 
+using IString = wasm::IString;
+
+// IStringSet
+
+class IStringSet : public std::unordered_set<IString> {
+  std::vector<char> data;
+
+public:
+  IStringSet() = default;
+  IStringSet(const char* init) { // comma-delimited list
+    int size = strlen(init) + 1;
+    data.resize(size);
+    char* curr = &data[0];
+    strncpy(curr, init, size);
+    while (1) {
+      char* end = strchr(curr, ' ');
+      if (end) {
+        *end = 0;
+      }
+      insert(curr);
+      if (!end) {
+        break;
+      }
+      curr = end + 1;
+    }
+  }
+
+  bool has(const IString& str) { return count(str) > 0; }
+};
+
+class IOrderedStringSet : public std::set<IString> {
+public:
+  bool has(const IString& str) { return count(str) > 0; }
+};
+
 // common strings
 
 extern IString TOPLEVEL;
@@ -233,11 +268,11 @@ template<class NodeRef, class Builder> class Parser {
           src++;
         }
         if (*src == 0) {
-          str.set(start);
+          str = IString(start);
         } else {
           char temp = *src;
           *src = 0;
-          str.set(start, false);
+          str = IString(start, false);
           *src = temp;
         }
         type = keywords.has(str) ? KEYWORD : IDENT;
@@ -333,11 +368,11 @@ template<class NodeRef, class Builder> class Parser {
           default:
             abort();
         }
-        size = strlen(str.str);
+        size = str.size();
 #ifndef NDEBUG
         char temp = start[size];
         start[size] = 0;
-        assert(strcmp(str.str, start) == 0);
+        assert(str.str == start);
         start[size] = temp;
 #endif
         type = OPERATOR;
@@ -346,13 +381,13 @@ template<class NodeRef, class Builder> class Parser {
         type = SEPARATOR;
         char temp = src[1];
         src[1] = 0;
-        str.set(src, false);
+        str = IString(src, false);
         src[1] = temp;
         src++;
       } else if (*src == '"' || *src == '\'') {
         char* end = strchr(src + 1, *src);
         *end = 0;
-        str.set(src + 1);
+        str = IString(src + 1);
         src = end + 1;
         type = STRING;
       } else {
@@ -391,7 +426,7 @@ template<class NodeRef, class Builder> class Parser {
   // This is a list of the current stack of node-operator-node-operator-etc.
   // this works by each parseExpression call appending to the vector; then
   // recursing out, and the toplevel sorts it all
-  typedef std::vector<ExpressionElement> ExpressionParts;
+  using ExpressionParts = std::vector<ExpressionElement>;
   std::vector<ExpressionParts> expressionPartsStack;
 
   // Parses an element in a list of such elements, e.g. list of statements in a
diff --git a/src/emscripten-optimizer/simple_ast.cpp b/src/emscripten-optimizer/simple_ast.cpp
index 6afa0a6..086d504 100644
--- a/src/emscripten-optimizer/simple_ast.cpp
+++ b/src/emscripten-optimizer/simple_ast.cpp
@@ -24,13 +24,11 @@ Ref& Ref::operator[](unsigned x) { return (*get())[x]; }
 
 Ref& Ref::operator[](IString x) { return (*get())[x]; }
 
-bool Ref::operator==(const char* str) {
-  return get()->isString() && !strcmp(get()->str.str, str);
+bool Ref::operator==(std::string_view str) {
+  return get()->isString() && get()->str == str;
 }
 
-bool Ref::operator!=(const char* str) {
-  return get()->isString() ? !!strcmp(get()->str.str, str) : true;
-}
+bool Ref::operator!=(std::string_view str) { return !(*this == str); }
 
 bool Ref::operator==(const IString& str) {
   return get()->isString() && get()->str == str;
@@ -81,8 +79,8 @@ void Value::stringify(std::ostream& os, bool pretty) {
   }
   switch (type) {
     case String: {
-      if (str.str) {
-        os << '"' << str.str << '"';
+      if (str) {
+        os << '"' << str << '"';
       } else {
         os << "\"(null)\"";
       }
@@ -147,7 +145,7 @@ void Value::stringify(std::ostream& os, bool pretty) {
           }
         }
         indentify();
-        os << '"' << i.first.c_str() << "\": ";
+        os << '"' << i.first << "\": ";
         i.second->stringify(os, pretty);
       }
       if (pretty) {
diff --git a/src/emscripten-optimizer/simple_ast.h b/src/emscripten-optimizer/simple_ast.h
index 64db04d..eb43209 100644
--- a/src/emscripten-optimizer/simple_ast.h
+++ b/src/emscripten-optimizer/simple_ast.h
@@ -63,8 +63,9 @@ struct Ref {
   Ref& operator[](IString x);
 
   // special conveniences
-  bool operator==(const char* str); // comparison to string, which is by value
-  bool operator!=(const char* str);
+  bool
+  operator==(std::string_view str); // comparison to string, which is by value
+  bool operator!=(std::string_view str);
   bool operator==(const IString& str);
   bool operator!=(const IString& str);
   // prevent Ref == number, which is potentially ambiguous; use ->getNumber() ==
@@ -119,7 +120,7 @@ struct Value {
 
   Type type = Null;
 
-  typedef std::unordered_map<IString, Ref> ObjectStorage;
+  using ObjectStorage = std::unordered_map<IString, Ref>;
 
   // MSVC does not allow unrestricted unions:
   // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
@@ -163,13 +164,13 @@ struct Value {
   Value& setString(const char* s) {
     free();
     type = String;
-    str.set(s);
+    str = IString(s);
     return *this;
   }
   Value& setString(const IString& s) {
     free();
     type = String;
-    str.set(s);
+    str = s;
     return *this;
   }
   Value& setNumber(double n) {
@@ -233,7 +234,7 @@ struct Value {
 
   const char* getCString() {
     assert(isString());
-    return str.str;
+    return str.str.data();
   }
   IString& getIString() {
     assert(isString());
@@ -817,7 +818,7 @@ struct JSPrinter {
         break;
       }
       default: {
-        errv("cannot yet print %s\n", type.str);
+        errv("cannot yet print %s\n", type.str.data());
         abort();
       }
     }
@@ -908,7 +909,7 @@ struct JSPrinter {
 
   void printAssignName(Ref node) {
     auto* assign = node->asAssignName();
-    emit(assign->target().c_str());
+    emit(assign->target().str.data());
     space();
     emit('=');
     space();
@@ -1472,10 +1473,10 @@ struct JSPrinter {
           needQuote = true;
           str = args[i][0][1]->getCString();
         } else if (args[i][0][0] == GETTER) {
-          getterSetter = GETTER.c_str();
+          getterSetter = GETTER.str.data();
           str = args[i][0][1]->getCString();
         } else if (args[i][0][0] == SETTER) {
-          getterSetter = SETTER.c_str();
+          getterSetter = SETTER.str.data();
           str = args[i][0][1]->getCString();
           setterParam = args[i][0][2]->getCString();
         } else {
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index 4111969..929634c 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -4,28 +4,31 @@
 
 #ifdef INSTRUCTION_PARSER
 #undef INSTRUCTION_PARSER
-char op[33] = {'\0'};
-strncpy(op, s[0]->c_str(), 32);
+char buf[33] = {};
+using namespace std::string_view_literals;
+auto str = s[0]->str().str;
+memcpy(buf, str.data(), str.size());
+std::string_view op = {buf, str.size()};
 switch (op[0]) {
   case 'a': {
     switch (op[1]) {
       case 'r': {
         switch (op[6]) {
           case 'c':
-            if (strcmp(op, "array.copy") == 0) { return makeArrayCopy(s); }
+            if (op == "array.copy"sv) { return makeArrayCopy(s); }
             goto parse_error;
           case 'g': {
             switch (op[9]) {
               case '\0':
-                if (strcmp(op, "array.get") == 0) { return makeArrayGet(s); }
+                if (op == "array.get"sv) { return makeArrayGet(s); }
                 goto parse_error;
               case '_': {
                 switch (op[10]) {
                   case 's':
-                    if (strcmp(op, "array.get_s") == 0) { return makeArrayGet(s, true); }
+                    if (op == "array.get_s"sv) { return makeArrayGet(s, true); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "array.get_u") == 0) { return makeArrayGet(s, false); }
+                    if (op == "array.get_u"sv) { return makeArrayGet(s, false); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -33,40 +36,32 @@ switch (op[0]) {
               default: goto parse_error;
             }
           }
-          case 'i': {
-            switch (op[10]) {
-              case '\0':
-                if (strcmp(op, "array.init") == 0) { return makeArrayInit(s); }
-                goto parse_error;
-              case '_':
-                if (strcmp(op, "array.init_static") == 0) { return makeArrayInitStatic(s); }
-                goto parse_error;
-              default: goto parse_error;
-            }
-          }
+          case 'i':
+            if (op == "array.init_static"sv) { return makeArrayInitStatic(s); }
+            goto parse_error;
           case 'l':
-            if (strcmp(op, "array.len") == 0) { return makeArrayLen(s); }
+            if (op == "array.len"sv) { return makeArrayLen(s); }
             goto parse_error;
           case 'n': {
             switch (op[9]) {
               case '\0':
-                if (strcmp(op, "array.new") == 0) { return makeArrayNewStatic(s, false); }
+                if (op == "array.new"sv) { return makeArrayNewStatic(s, false); }
                 goto parse_error;
               case '_': {
                 switch (op[10]) {
                   case 'd': {
-                    switch (op[17]) {
-                      case '\0':
-                        if (strcmp(op, "array.new_default") == 0) { return makeArrayNewStatic(s, true); }
+                    switch (op[11]) {
+                      case 'a':
+                        if (op == "array.new_data"sv) { return makeArrayNewSeg(s, NewData); }
                         goto parse_error;
-                      case '_':
-                        if (strcmp(op, "array.new_default_with_rtt") == 0) { return makeArrayNew(s, true); }
+                      case 'e':
+                        if (op == "array.new_default"sv) { return makeArrayNewStatic(s, true); }
                         goto parse_error;
                       default: goto parse_error;
                     }
                   }
-                  case 'w':
-                    if (strcmp(op, "array.new_with_rtt") == 0) { return makeArrayNew(s, false); }
+                  case 'e':
+                    if (op == "array.new_elem"sv) { return makeArrayNewSeg(s, NewElem); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -75,13 +70,13 @@ switch (op[0]) {
             }
           }
           case 's':
-            if (strcmp(op, "array.set") == 0) { return makeArraySet(s); }
+            if (op == "array.set"sv) { return makeArraySet(s); }
             goto parse_error;
           default: goto parse_error;
         }
       }
       case 't':
-        if (strcmp(op, "atomic.fence") == 0) { return makeAtomicFence(s); }
+        if (op == "atomic.fence"sv) { return makeAtomicFence(s); }
         goto parse_error;
       default: goto parse_error;
     }
@@ -89,37 +84,37 @@ switch (op[0]) {
   case 'b': {
     switch (op[1]) {
       case 'l':
-        if (strcmp(op, "block") == 0) { return makeBlock(s); }
+        if (op == "block"sv) { return makeBlock(s); }
         goto parse_error;
       case 'r': {
         switch (op[2]) {
           case '\0':
-            if (strcmp(op, "br") == 0) { return makeBreak(s); }
+            if (op == "br"sv) { return makeBreak(s); }
             goto parse_error;
           case '_': {
             switch (op[3]) {
               case 'i':
-                if (strcmp(op, "br_if") == 0) { return makeBreak(s); }
+                if (op == "br_if"sv) { return makeBreak(s); }
                 goto parse_error;
               case 'o': {
                 switch (op[6]) {
                   case 'c': {
                     switch (op[10]) {
                       case '\0':
-                        if (strcmp(op, "br_on_cast") == 0) { return makeBrOn(s, BrOnCast); }
+                        if (op == "br_on_cast"sv) { return makeBrOn(s, BrOnCast); }
                         goto parse_error;
                       case '_': {
                         switch (op[11]) {
                           case 'f':
-                            if (strcmp(op, "br_on_cast_fail") == 0) { return makeBrOn(s, BrOnCastFail); }
+                            if (op == "br_on_cast_fail"sv) { return makeBrOn(s, BrOnCastFail); }
                             goto parse_error;
                           case 's': {
                             switch (op[17]) {
                               case '\0':
-                                if (strcmp(op, "br_on_cast_static") == 0) { return makeBrOnStatic(s, BrOnCast); }
+                                if (op == "br_on_cast_static"sv) { return makeBrOnStatic(s, BrOnCast); }
                                 goto parse_error;
                               case '_':
-                                if (strcmp(op, "br_on_cast_static_fail") == 0) { return makeBrOnStatic(s, BrOnCastFail); }
+                                if (op == "br_on_cast_static_fail"sv) { return makeBrOnStatic(s, BrOnCastFail); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -131,35 +126,35 @@ switch (op[0]) {
                     }
                   }
                   case 'd':
-                    if (strcmp(op, "br_on_data") == 0) { return makeBrOn(s, BrOnData); }
+                    if (op == "br_on_data"sv) { return makeBrOn(s, BrOnData); }
                     goto parse_error;
                   case 'f':
-                    if (strcmp(op, "br_on_func") == 0) { return makeBrOn(s, BrOnFunc); }
+                    if (op == "br_on_func"sv) { return makeBrOn(s, BrOnFunc); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "br_on_i31") == 0) { return makeBrOn(s, BrOnI31); }
+                    if (op == "br_on_i31"sv) { return makeBrOn(s, BrOnI31); }
                     goto parse_error;
                   case 'n': {
                     switch (op[7]) {
                       case 'o': {
                         switch (op[10]) {
                           case 'd':
-                            if (strcmp(op, "br_on_non_data") == 0) { return makeBrOn(s, BrOnNonData); }
+                            if (op == "br_on_non_data"sv) { return makeBrOn(s, BrOnNonData); }
                             goto parse_error;
                           case 'f':
-                            if (strcmp(op, "br_on_non_func") == 0) { return makeBrOn(s, BrOnNonFunc); }
+                            if (op == "br_on_non_func"sv) { return makeBrOn(s, BrOnNonFunc); }
                             goto parse_error;
                           case 'i':
-                            if (strcmp(op, "br_on_non_i31") == 0) { return makeBrOn(s, BrOnNonI31); }
+                            if (op == "br_on_non_i31"sv) { return makeBrOn(s, BrOnNonI31); }
                             goto parse_error;
                           case 'n':
-                            if (strcmp(op, "br_on_non_null") == 0) { return makeBrOn(s, BrOnNonNull); }
+                            if (op == "br_on_non_null"sv) { return makeBrOn(s, BrOnNonNull); }
                             goto parse_error;
                           default: goto parse_error;
                         }
                       }
                       case 'u':
-                        if (strcmp(op, "br_on_null") == 0) { return makeBrOn(s, BrOnNull); }
+                        if (op == "br_on_null"sv) { return makeBrOn(s, BrOnNull); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -168,7 +163,7 @@ switch (op[0]) {
                 }
               }
               case 't':
-                if (strcmp(op, "br_table") == 0) { return makeBreakTable(s); }
+                if (op == "br_table"sv) { return makeBreakTable(s); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -182,15 +177,15 @@ switch (op[0]) {
   case 'c': {
     switch (op[4]) {
       case '\0':
-        if (strcmp(op, "call") == 0) { return makeCall(s, /*isReturn=*/false); }
+        if (op == "call"sv) { return makeCall(s, /*isReturn=*/false); }
         goto parse_error;
       case '_': {
         switch (op[5]) {
           case 'i':
-            if (strcmp(op, "call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/false); }
+            if (op == "call_indirect"sv) { return makeCallIndirect(s, /*isReturn=*/false); }
             goto parse_error;
           case 'r':
-            if (strcmp(op, "call_ref") == 0) { return makeCallRef(s, /*isReturn=*/false); }
+            if (op == "call_ref"sv) { return makeCallRef(s, /*isReturn=*/false); }
             goto parse_error;
           default: goto parse_error;
         }
@@ -201,17 +196,33 @@ switch (op[0]) {
   case 'd': {
     switch (op[1]) {
       case 'a':
-        if (strcmp(op, "data.drop") == 0) { return makeDataDrop(s); }
+        if (op == "data.drop"sv) { return makeDataDrop(s); }
         goto parse_error;
       case 'r':
-        if (strcmp(op, "drop") == 0) { return makeDrop(s); }
+        if (op == "drop"sv) { return makeDrop(s); }
         goto parse_error;
       default: goto parse_error;
     }
   }
-  case 'e':
-    if (strcmp(op, "else") == 0) { return makeThenOrElse(s); }
-    goto parse_error;
+  case 'e': {
+    switch (op[1]) {
+      case 'l':
+        if (op == "else"sv) { return makeThenOrElse(s); }
+        goto parse_error;
+      case 'x': {
+        switch (op[7]) {
+          case 'e':
+            if (op == "extern.externalize"sv) { return makeRefAs(s, ExternExternalize); }
+            goto parse_error;
+          case 'i':
+            if (op == "extern.internalize"sv) { return makeRefAs(s, ExternInternalize); }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
   case 'f': {
     switch (op[1]) {
       case '3': {
@@ -221,10 +232,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[5]) {
                   case 'b':
-                    if (strcmp(op, "f32.abs") == 0) { return makeUnary(s, UnaryOp::AbsFloat32); }
+                    if (op == "f32.abs"sv) { return makeUnary(s, UnaryOp::AbsFloat32); }
                     goto parse_error;
                   case 'd':
-                    if (strcmp(op, "f32.add") == 0) { return makeBinary(s, BinaryOp::AddFloat32); }
+                    if (op == "f32.add"sv) { return makeBinary(s, BinaryOp::AddFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -232,24 +243,24 @@ switch (op[0]) {
               case 'c': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f32.ceil") == 0) { return makeUnary(s, UnaryOp::CeilFloat32); }
+                    if (op == "f32.ceil"sv) { return makeUnary(s, UnaryOp::CeilFloat32); }
                     goto parse_error;
                   case 'o': {
                     switch (op[6]) {
                       case 'n': {
                         switch (op[7]) {
                           case 's':
-                            if (strcmp(op, "f32.const") == 0) { return makeConst(s, Type::f32); }
+                            if (op == "f32.const"sv) { return makeConst(s, Type::f32); }
                             goto parse_error;
                           case 'v': {
                             switch (op[13]) {
                               case '3': {
                                 switch (op[16]) {
                                   case 's':
-                                    if (strcmp(op, "f32.convert_i32_s") == 0) { return makeUnary(s, UnaryOp::ConvertSInt32ToFloat32); }
+                                    if (op == "f32.convert_i32_s"sv) { return makeUnary(s, UnaryOp::ConvertSInt32ToFloat32); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "f32.convert_i32_u") == 0) { return makeUnary(s, UnaryOp::ConvertUInt32ToFloat32); }
+                                    if (op == "f32.convert_i32_u"sv) { return makeUnary(s, UnaryOp::ConvertUInt32ToFloat32); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -257,10 +268,10 @@ switch (op[0]) {
                               case '6': {
                                 switch (op[16]) {
                                   case 's':
-                                    if (strcmp(op, "f32.convert_i64_s") == 0) { return makeUnary(s, UnaryOp::ConvertSInt64ToFloat32); }
+                                    if (op == "f32.convert_i64_s"sv) { return makeUnary(s, UnaryOp::ConvertSInt64ToFloat32); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "f32.convert_i64_u") == 0) { return makeUnary(s, UnaryOp::ConvertUInt64ToFloat32); }
+                                    if (op == "f32.convert_i64_u"sv) { return makeUnary(s, UnaryOp::ConvertUInt64ToFloat32); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -272,7 +283,7 @@ switch (op[0]) {
                         }
                       }
                       case 'p':
-                        if (strcmp(op, "f32.copysign") == 0) { return makeBinary(s, BinaryOp::CopySignFloat32); }
+                        if (op == "f32.copysign"sv) { return makeBinary(s, BinaryOp::CopySignFloat32); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -283,27 +294,27 @@ switch (op[0]) {
               case 'd': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f32.demote_f64") == 0) { return makeUnary(s, UnaryOp::DemoteFloat64); }
+                    if (op == "f32.demote_f64"sv) { return makeUnary(s, UnaryOp::DemoteFloat64); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f32.div") == 0) { return makeBinary(s, BinaryOp::DivFloat32); }
+                    if (op == "f32.div"sv) { return makeBinary(s, BinaryOp::DivFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'e':
-                if (strcmp(op, "f32.eq") == 0) { return makeBinary(s, BinaryOp::EqFloat32); }
+                if (op == "f32.eq"sv) { return makeBinary(s, BinaryOp::EqFloat32); }
                 goto parse_error;
               case 'f':
-                if (strcmp(op, "f32.floor") == 0) { return makeUnary(s, UnaryOp::FloorFloat32); }
+                if (op == "f32.floor"sv) { return makeUnary(s, UnaryOp::FloorFloat32); }
                 goto parse_error;
               case 'g': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f32.ge") == 0) { return makeBinary(s, BinaryOp::GeFloat32); }
+                    if (op == "f32.ge"sv) { return makeBinary(s, BinaryOp::GeFloat32); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f32.gt") == 0) { return makeBinary(s, BinaryOp::GtFloat32); }
+                    if (op == "f32.gt"sv) { return makeBinary(s, BinaryOp::GtFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -311,13 +322,13 @@ switch (op[0]) {
               case 'l': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f32.le") == 0) { return makeBinary(s, BinaryOp::LeFloat32); }
+                    if (op == "f32.le"sv) { return makeBinary(s, BinaryOp::LeFloat32); }
                     goto parse_error;
                   case 'o':
-                    if (strcmp(op, "f32.load") == 0) { return makeLoad(s, Type::f32, /*isAtomic=*/false); }
+                    if (op == "f32.load"sv) { return makeLoad(s, Type::f32, /*signed=*/false, 4, /*isAtomic=*/false); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f32.lt") == 0) { return makeBinary(s, BinaryOp::LtFloat32); }
+                    if (op == "f32.lt"sv) { return makeBinary(s, BinaryOp::LtFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -325,13 +336,13 @@ switch (op[0]) {
               case 'm': {
                 switch (op[5]) {
                   case 'a':
-                    if (strcmp(op, "f32.max") == 0) { return makeBinary(s, BinaryOp::MaxFloat32); }
+                    if (op == "f32.max"sv) { return makeBinary(s, BinaryOp::MaxFloat32); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f32.min") == 0) { return makeBinary(s, BinaryOp::MinFloat32); }
+                    if (op == "f32.min"sv) { return makeBinary(s, BinaryOp::MinFloat32); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f32.mul") == 0) { return makeBinary(s, BinaryOp::MulFloat32); }
+                    if (op == "f32.mul"sv) { return makeBinary(s, BinaryOp::MulFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -339,36 +350,36 @@ switch (op[0]) {
               case 'n': {
                 switch (op[6]) {
                   case '\0':
-                    if (strcmp(op, "f32.ne") == 0) { return makeBinary(s, BinaryOp::NeFloat32); }
+                    if (op == "f32.ne"sv) { return makeBinary(s, BinaryOp::NeFloat32); }
                     goto parse_error;
                   case 'a':
-                    if (strcmp(op, "f32.nearest") == 0) { return makeUnary(s, UnaryOp::NearestFloat32); }
+                    if (op == "f32.nearest"sv) { return makeUnary(s, UnaryOp::NearestFloat32); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "f32.neg") == 0) { return makeUnary(s, UnaryOp::NegFloat32); }
+                    if (op == "f32.neg"sv) { return makeUnary(s, UnaryOp::NegFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'r':
-                if (strcmp(op, "f32.reinterpret_i32") == 0) { return makeUnary(s, UnaryOp::ReinterpretInt32); }
+                if (op == "f32.reinterpret_i32"sv) { return makeUnary(s, UnaryOp::ReinterpretInt32); }
                 goto parse_error;
               case 's': {
                 switch (op[5]) {
                   case 'q':
-                    if (strcmp(op, "f32.sqrt") == 0) { return makeUnary(s, UnaryOp::SqrtFloat32); }
+                    if (op == "f32.sqrt"sv) { return makeUnary(s, UnaryOp::SqrtFloat32); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f32.store") == 0) { return makeStore(s, Type::f32, /*isAtomic=*/false); }
+                    if (op == "f32.store"sv) { return makeStore(s, Type::f32, 4, /*isAtomic=*/false); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f32.sub") == 0) { return makeBinary(s, BinaryOp::SubFloat32); }
+                    if (op == "f32.sub"sv) { return makeBinary(s, BinaryOp::SubFloat32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 't':
-                if (strcmp(op, "f32.trunc") == 0) { return makeUnary(s, UnaryOp::TruncFloat32); }
+                if (op == "f32.trunc"sv) { return makeUnary(s, UnaryOp::TruncFloat32); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -378,10 +389,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[7]) {
                   case 'b':
-                    if (strcmp(op, "f32x4.abs") == 0) { return makeUnary(s, UnaryOp::AbsVecF32x4); }
+                    if (op == "f32x4.abs"sv) { return makeUnary(s, UnaryOp::AbsVecF32x4); }
                     goto parse_error;
                   case 'd':
-                    if (strcmp(op, "f32x4.add") == 0) { return makeBinary(s, BinaryOp::AddVecF32x4); }
+                    if (op == "f32x4.add"sv) { return makeBinary(s, BinaryOp::AddVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -389,15 +400,15 @@ switch (op[0]) {
               case 'c': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f32x4.ceil") == 0) { return makeUnary(s, UnaryOp::CeilVecF32x4); }
+                    if (op == "f32x4.ceil"sv) { return makeUnary(s, UnaryOp::CeilVecF32x4); }
                     goto parse_error;
                   case 'o': {
                     switch (op[20]) {
                       case 's':
-                        if (strcmp(op, "f32x4.convert_i32x4_s") == 0) { return makeUnary(s, UnaryOp::ConvertSVecI32x4ToVecF32x4); }
+                        if (op == "f32x4.convert_i32x4_s"sv) { return makeUnary(s, UnaryOp::ConvertSVecI32x4ToVecF32x4); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "f32x4.convert_i32x4_u") == 0) { return makeUnary(s, UnaryOp::ConvertUVecI32x4ToVecF32x4); }
+                        if (op == "f32x4.convert_i32x4_u"sv) { return makeUnary(s, UnaryOp::ConvertUVecI32x4ToVecF32x4); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -408,10 +419,10 @@ switch (op[0]) {
               case 'd': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f32x4.demote_f64x2_zero") == 0) { return makeUnary(s, UnaryOp::DemoteZeroVecF64x2ToVecF32x4); }
+                    if (op == "f32x4.demote_f64x2_zero"sv) { return makeUnary(s, UnaryOp::DemoteZeroVecF64x2ToVecF32x4); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f32x4.div") == 0) { return makeBinary(s, BinaryOp::DivVecF32x4); }
+                    if (op == "f32x4.div"sv) { return makeBinary(s, BinaryOp::DivVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -419,24 +430,24 @@ switch (op[0]) {
               case 'e': {
                 switch (op[7]) {
                   case 'q':
-                    if (strcmp(op, "f32x4.eq") == 0) { return makeBinary(s, BinaryOp::EqVecF32x4); }
+                    if (op == "f32x4.eq"sv) { return makeBinary(s, BinaryOp::EqVecF32x4); }
                     goto parse_error;
                   case 'x':
-                    if (strcmp(op, "f32x4.extract_lane") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecF32x4, 4); }
+                    if (op == "f32x4.extract_lane"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecF32x4, 4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'f':
-                if (strcmp(op, "f32x4.floor") == 0) { return makeUnary(s, UnaryOp::FloorVecF32x4); }
+                if (op == "f32x4.floor"sv) { return makeUnary(s, UnaryOp::FloorVecF32x4); }
                 goto parse_error;
               case 'g': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f32x4.ge") == 0) { return makeBinary(s, BinaryOp::GeVecF32x4); }
+                    if (op == "f32x4.ge"sv) { return makeBinary(s, BinaryOp::GeVecF32x4); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f32x4.gt") == 0) { return makeBinary(s, BinaryOp::GtVecF32x4); }
+                    if (op == "f32x4.gt"sv) { return makeBinary(s, BinaryOp::GtVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -444,10 +455,10 @@ switch (op[0]) {
               case 'l': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f32x4.le") == 0) { return makeBinary(s, BinaryOp::LeVecF32x4); }
+                    if (op == "f32x4.le"sv) { return makeBinary(s, BinaryOp::LeVecF32x4); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f32x4.lt") == 0) { return makeBinary(s, BinaryOp::LtVecF32x4); }
+                    if (op == "f32x4.lt"sv) { return makeBinary(s, BinaryOp::LtVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -455,13 +466,13 @@ switch (op[0]) {
               case 'm': {
                 switch (op[7]) {
                   case 'a':
-                    if (strcmp(op, "f32x4.max") == 0) { return makeBinary(s, BinaryOp::MaxVecF32x4); }
+                    if (op == "f32x4.max"sv) { return makeBinary(s, BinaryOp::MaxVecF32x4); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f32x4.min") == 0) { return makeBinary(s, BinaryOp::MinVecF32x4); }
+                    if (op == "f32x4.min"sv) { return makeBinary(s, BinaryOp::MinVecF32x4); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f32x4.mul") == 0) { return makeBinary(s, BinaryOp::MulVecF32x4); }
+                    if (op == "f32x4.mul"sv) { return makeBinary(s, BinaryOp::MulVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -469,13 +480,13 @@ switch (op[0]) {
               case 'n': {
                 switch (op[8]) {
                   case '\0':
-                    if (strcmp(op, "f32x4.ne") == 0) { return makeBinary(s, BinaryOp::NeVecF32x4); }
+                    if (op == "f32x4.ne"sv) { return makeBinary(s, BinaryOp::NeVecF32x4); }
                     goto parse_error;
                   case 'a':
-                    if (strcmp(op, "f32x4.nearest") == 0) { return makeUnary(s, UnaryOp::NearestVecF32x4); }
+                    if (op == "f32x4.nearest"sv) { return makeUnary(s, UnaryOp::NearestVecF32x4); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "f32x4.neg") == 0) { return makeUnary(s, UnaryOp::NegVecF32x4); }
+                    if (op == "f32x4.neg"sv) { return makeUnary(s, UnaryOp::NegVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -483,10 +494,10 @@ switch (op[0]) {
               case 'p': {
                 switch (op[8]) {
                   case 'a':
-                    if (strcmp(op, "f32x4.pmax") == 0) { return makeBinary(s, BinaryOp::PMaxVecF32x4); }
+                    if (op == "f32x4.pmax"sv) { return makeBinary(s, BinaryOp::PMaxVecF32x4); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f32x4.pmin") == 0) { return makeBinary(s, BinaryOp::PMinVecF32x4); }
+                    if (op == "f32x4.pmin"sv) { return makeBinary(s, BinaryOp::PMinVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -498,10 +509,10 @@ switch (op[0]) {
                       case 'f': {
                         switch (op[16]) {
                           case 'a':
-                            if (strcmp(op, "f32x4.relaxed_fma") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmaVecF32x4); }
+                            if (op == "f32x4.relaxed_fma"sv) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmaVecF32x4); }
                             goto parse_error;
                           case 's':
-                            if (strcmp(op, "f32x4.relaxed_fms") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmsVecF32x4); }
+                            if (op == "f32x4.relaxed_fms"sv) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmsVecF32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -509,10 +520,10 @@ switch (op[0]) {
                       case 'm': {
                         switch (op[15]) {
                           case 'a':
-                            if (strcmp(op, "f32x4.relaxed_max") == 0) { return makeBinary(s, BinaryOp::RelaxedMaxVecF32x4); }
+                            if (op == "f32x4.relaxed_max"sv) { return makeBinary(s, BinaryOp::RelaxedMaxVecF32x4); }
                             goto parse_error;
                           case 'i':
-                            if (strcmp(op, "f32x4.relaxed_min") == 0) { return makeBinary(s, BinaryOp::RelaxedMinVecF32x4); }
+                            if (op == "f32x4.relaxed_min"sv) { return makeBinary(s, BinaryOp::RelaxedMinVecF32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -521,7 +532,7 @@ switch (op[0]) {
                     }
                   }
                   case 'p':
-                    if (strcmp(op, "f32x4.replace_lane") == 0) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecF32x4, 4); }
+                    if (op == "f32x4.replace_lane"sv) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecF32x4, 4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -529,19 +540,19 @@ switch (op[0]) {
               case 's': {
                 switch (op[7]) {
                   case 'p':
-                    if (strcmp(op, "f32x4.splat") == 0) { return makeUnary(s, UnaryOp::SplatVecF32x4); }
+                    if (op == "f32x4.splat"sv) { return makeUnary(s, UnaryOp::SplatVecF32x4); }
                     goto parse_error;
                   case 'q':
-                    if (strcmp(op, "f32x4.sqrt") == 0) { return makeUnary(s, UnaryOp::SqrtVecF32x4); }
+                    if (op == "f32x4.sqrt"sv) { return makeUnary(s, UnaryOp::SqrtVecF32x4); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f32x4.sub") == 0) { return makeBinary(s, BinaryOp::SubVecF32x4); }
+                    if (op == "f32x4.sub"sv) { return makeBinary(s, BinaryOp::SubVecF32x4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 't':
-                if (strcmp(op, "f32x4.trunc") == 0) { return makeUnary(s, UnaryOp::TruncVecF32x4); }
+                if (op == "f32x4.trunc"sv) { return makeUnary(s, UnaryOp::TruncVecF32x4); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -556,10 +567,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[5]) {
                   case 'b':
-                    if (strcmp(op, "f64.abs") == 0) { return makeUnary(s, UnaryOp::AbsFloat64); }
+                    if (op == "f64.abs"sv) { return makeUnary(s, UnaryOp::AbsFloat64); }
                     goto parse_error;
                   case 'd':
-                    if (strcmp(op, "f64.add") == 0) { return makeBinary(s, BinaryOp::AddFloat64); }
+                    if (op == "f64.add"sv) { return makeBinary(s, BinaryOp::AddFloat64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -567,24 +578,24 @@ switch (op[0]) {
               case 'c': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f64.ceil") == 0) { return makeUnary(s, UnaryOp::CeilFloat64); }
+                    if (op == "f64.ceil"sv) { return makeUnary(s, UnaryOp::CeilFloat64); }
                     goto parse_error;
                   case 'o': {
                     switch (op[6]) {
                       case 'n': {
                         switch (op[7]) {
                           case 's':
-                            if (strcmp(op, "f64.const") == 0) { return makeConst(s, Type::f64); }
+                            if (op == "f64.const"sv) { return makeConst(s, Type::f64); }
                             goto parse_error;
                           case 'v': {
                             switch (op[13]) {
                               case '3': {
                                 switch (op[16]) {
                                   case 's':
-                                    if (strcmp(op, "f64.convert_i32_s") == 0) { return makeUnary(s, UnaryOp::ConvertSInt32ToFloat64); }
+                                    if (op == "f64.convert_i32_s"sv) { return makeUnary(s, UnaryOp::ConvertSInt32ToFloat64); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "f64.convert_i32_u") == 0) { return makeUnary(s, UnaryOp::ConvertUInt32ToFloat64); }
+                                    if (op == "f64.convert_i32_u"sv) { return makeUnary(s, UnaryOp::ConvertUInt32ToFloat64); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -592,10 +603,10 @@ switch (op[0]) {
                               case '6': {
                                 switch (op[16]) {
                                   case 's':
-                                    if (strcmp(op, "f64.convert_i64_s") == 0) { return makeUnary(s, UnaryOp::ConvertSInt64ToFloat64); }
+                                    if (op == "f64.convert_i64_s"sv) { return makeUnary(s, UnaryOp::ConvertSInt64ToFloat64); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "f64.convert_i64_u") == 0) { return makeUnary(s, UnaryOp::ConvertUInt64ToFloat64); }
+                                    if (op == "f64.convert_i64_u"sv) { return makeUnary(s, UnaryOp::ConvertUInt64ToFloat64); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -607,7 +618,7 @@ switch (op[0]) {
                         }
                       }
                       case 'p':
-                        if (strcmp(op, "f64.copysign") == 0) { return makeBinary(s, BinaryOp::CopySignFloat64); }
+                        if (op == "f64.copysign"sv) { return makeBinary(s, BinaryOp::CopySignFloat64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -616,21 +627,21 @@ switch (op[0]) {
                 }
               }
               case 'd':
-                if (strcmp(op, "f64.div") == 0) { return makeBinary(s, BinaryOp::DivFloat64); }
+                if (op == "f64.div"sv) { return makeBinary(s, BinaryOp::DivFloat64); }
                 goto parse_error;
               case 'e':
-                if (strcmp(op, "f64.eq") == 0) { return makeBinary(s, BinaryOp::EqFloat64); }
+                if (op == "f64.eq"sv) { return makeBinary(s, BinaryOp::EqFloat64); }
                 goto parse_error;
               case 'f':
-                if (strcmp(op, "f64.floor") == 0) { return makeUnary(s, UnaryOp::FloorFloat64); }
+                if (op == "f64.floor"sv) { return makeUnary(s, UnaryOp::FloorFloat64); }
                 goto parse_error;
               case 'g': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f64.ge") == 0) { return makeBinary(s, BinaryOp::GeFloat64); }
+                    if (op == "f64.ge"sv) { return makeBinary(s, BinaryOp::GeFloat64); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f64.gt") == 0) { return makeBinary(s, BinaryOp::GtFloat64); }
+                    if (op == "f64.gt"sv) { return makeBinary(s, BinaryOp::GtFloat64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -638,13 +649,13 @@ switch (op[0]) {
               case 'l': {
                 switch (op[5]) {
                   case 'e':
-                    if (strcmp(op, "f64.le") == 0) { return makeBinary(s, BinaryOp::LeFloat64); }
+                    if (op == "f64.le"sv) { return makeBinary(s, BinaryOp::LeFloat64); }
                     goto parse_error;
                   case 'o':
-                    if (strcmp(op, "f64.load") == 0) { return makeLoad(s, Type::f64, /*isAtomic=*/false); }
+                    if (op == "f64.load"sv) { return makeLoad(s, Type::f64, /*signed=*/false, 8, /*isAtomic=*/false); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f64.lt") == 0) { return makeBinary(s, BinaryOp::LtFloat64); }
+                    if (op == "f64.lt"sv) { return makeBinary(s, BinaryOp::LtFloat64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -652,13 +663,13 @@ switch (op[0]) {
               case 'm': {
                 switch (op[5]) {
                   case 'a':
-                    if (strcmp(op, "f64.max") == 0) { return makeBinary(s, BinaryOp::MaxFloat64); }
+                    if (op == "f64.max"sv) { return makeBinary(s, BinaryOp::MaxFloat64); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f64.min") == 0) { return makeBinary(s, BinaryOp::MinFloat64); }
+                    if (op == "f64.min"sv) { return makeBinary(s, BinaryOp::MinFloat64); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f64.mul") == 0) { return makeBinary(s, BinaryOp::MulFloat64); }
+                    if (op == "f64.mul"sv) { return makeBinary(s, BinaryOp::MulFloat64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -666,39 +677,39 @@ switch (op[0]) {
               case 'n': {
                 switch (op[6]) {
                   case '\0':
-                    if (strcmp(op, "f64.ne") == 0) { return makeBinary(s, BinaryOp::NeFloat64); }
+                    if (op == "f64.ne"sv) { return makeBinary(s, BinaryOp::NeFloat64); }
                     goto parse_error;
                   case 'a':
-                    if (strcmp(op, "f64.nearest") == 0) { return makeUnary(s, UnaryOp::NearestFloat64); }
+                    if (op == "f64.nearest"sv) { return makeUnary(s, UnaryOp::NearestFloat64); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "f64.neg") == 0) { return makeUnary(s, UnaryOp::NegFloat64); }
+                    if (op == "f64.neg"sv) { return makeUnary(s, UnaryOp::NegFloat64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'p':
-                if (strcmp(op, "f64.promote_f32") == 0) { return makeUnary(s, UnaryOp::PromoteFloat32); }
+                if (op == "f64.promote_f32"sv) { return makeUnary(s, UnaryOp::PromoteFloat32); }
                 goto parse_error;
               case 'r':
-                if (strcmp(op, "f64.reinterpret_i64") == 0) { return makeUnary(s, UnaryOp::ReinterpretInt64); }
+                if (op == "f64.reinterpret_i64"sv) { return makeUnary(s, UnaryOp::ReinterpretInt64); }
                 goto parse_error;
               case 's': {
                 switch (op[5]) {
                   case 'q':
-                    if (strcmp(op, "f64.sqrt") == 0) { return makeUnary(s, UnaryOp::SqrtFloat64); }
+                    if (op == "f64.sqrt"sv) { return makeUnary(s, UnaryOp::SqrtFloat64); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f64.store") == 0) { return makeStore(s, Type::f64, /*isAtomic=*/false); }
+                    if (op == "f64.store"sv) { return makeStore(s, Type::f64, 8, /*isAtomic=*/false); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f64.sub") == 0) { return makeBinary(s, BinaryOp::SubFloat64); }
+                    if (op == "f64.sub"sv) { return makeBinary(s, BinaryOp::SubFloat64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 't':
-                if (strcmp(op, "f64.trunc") == 0) { return makeUnary(s, UnaryOp::TruncFloat64); }
+                if (op == "f64.trunc"sv) { return makeUnary(s, UnaryOp::TruncFloat64); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -708,10 +719,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[7]) {
                   case 'b':
-                    if (strcmp(op, "f64x2.abs") == 0) { return makeUnary(s, UnaryOp::AbsVecF64x2); }
+                    if (op == "f64x2.abs"sv) { return makeUnary(s, UnaryOp::AbsVecF64x2); }
                     goto parse_error;
                   case 'd':
-                    if (strcmp(op, "f64x2.add") == 0) { return makeBinary(s, BinaryOp::AddVecF64x2); }
+                    if (op == "f64x2.add"sv) { return makeBinary(s, BinaryOp::AddVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -719,15 +730,15 @@ switch (op[0]) {
               case 'c': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f64x2.ceil") == 0) { return makeUnary(s, UnaryOp::CeilVecF64x2); }
+                    if (op == "f64x2.ceil"sv) { return makeUnary(s, UnaryOp::CeilVecF64x2); }
                     goto parse_error;
                   case 'o': {
                     switch (op[24]) {
                       case 's':
-                        if (strcmp(op, "f64x2.convert_low_i32x4_s") == 0) { return makeUnary(s, UnaryOp::ConvertLowSVecI32x4ToVecF64x2); }
+                        if (op == "f64x2.convert_low_i32x4_s"sv) { return makeUnary(s, UnaryOp::ConvertLowSVecI32x4ToVecF64x2); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "f64x2.convert_low_i32x4_u") == 0) { return makeUnary(s, UnaryOp::ConvertLowUVecI32x4ToVecF64x2); }
+                        if (op == "f64x2.convert_low_i32x4_u"sv) { return makeUnary(s, UnaryOp::ConvertLowUVecI32x4ToVecF64x2); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -736,29 +747,29 @@ switch (op[0]) {
                 }
               }
               case 'd':
-                if (strcmp(op, "f64x2.div") == 0) { return makeBinary(s, BinaryOp::DivVecF64x2); }
+                if (op == "f64x2.div"sv) { return makeBinary(s, BinaryOp::DivVecF64x2); }
                 goto parse_error;
               case 'e': {
                 switch (op[7]) {
                   case 'q':
-                    if (strcmp(op, "f64x2.eq") == 0) { return makeBinary(s, BinaryOp::EqVecF64x2); }
+                    if (op == "f64x2.eq"sv) { return makeBinary(s, BinaryOp::EqVecF64x2); }
                     goto parse_error;
                   case 'x':
-                    if (strcmp(op, "f64x2.extract_lane") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecF64x2, 2); }
+                    if (op == "f64x2.extract_lane"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecF64x2, 2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'f':
-                if (strcmp(op, "f64x2.floor") == 0) { return makeUnary(s, UnaryOp::FloorVecF64x2); }
+                if (op == "f64x2.floor"sv) { return makeUnary(s, UnaryOp::FloorVecF64x2); }
                 goto parse_error;
               case 'g': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f64x2.ge") == 0) { return makeBinary(s, BinaryOp::GeVecF64x2); }
+                    if (op == "f64x2.ge"sv) { return makeBinary(s, BinaryOp::GeVecF64x2); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f64x2.gt") == 0) { return makeBinary(s, BinaryOp::GtVecF64x2); }
+                    if (op == "f64x2.gt"sv) { return makeBinary(s, BinaryOp::GtVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -766,10 +777,10 @@ switch (op[0]) {
               case 'l': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "f64x2.le") == 0) { return makeBinary(s, BinaryOp::LeVecF64x2); }
+                    if (op == "f64x2.le"sv) { return makeBinary(s, BinaryOp::LeVecF64x2); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "f64x2.lt") == 0) { return makeBinary(s, BinaryOp::LtVecF64x2); }
+                    if (op == "f64x2.lt"sv) { return makeBinary(s, BinaryOp::LtVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -777,13 +788,13 @@ switch (op[0]) {
               case 'm': {
                 switch (op[7]) {
                   case 'a':
-                    if (strcmp(op, "f64x2.max") == 0) { return makeBinary(s, BinaryOp::MaxVecF64x2); }
+                    if (op == "f64x2.max"sv) { return makeBinary(s, BinaryOp::MaxVecF64x2); }
                     goto parse_error;
                   case 'i':
-                    if (strcmp(op, "f64x2.min") == 0) { return makeBinary(s, BinaryOp::MinVecF64x2); }
+                    if (op == "f64x2.min"sv) { return makeBinary(s, BinaryOp::MinVecF64x2); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f64x2.mul") == 0) { return makeBinary(s, BinaryOp::MulVecF64x2); }
+                    if (op == "f64x2.mul"sv) { return makeBinary(s, BinaryOp::MulVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -791,13 +802,13 @@ switch (op[0]) {
               case 'n': {
                 switch (op[8]) {
                   case '\0':
-                    if (strcmp(op, "f64x2.ne") == 0) { return makeBinary(s, BinaryOp::NeVecF64x2); }
+                    if (op == "f64x2.ne"sv) { return makeBinary(s, BinaryOp::NeVecF64x2); }
                     goto parse_error;
                   case 'a':
-                    if (strcmp(op, "f64x2.nearest") == 0) { return makeUnary(s, UnaryOp::NearestVecF64x2); }
+                    if (op == "f64x2.nearest"sv) { return makeUnary(s, UnaryOp::NearestVecF64x2); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "f64x2.neg") == 0) { return makeUnary(s, UnaryOp::NegVecF64x2); }
+                    if (op == "f64x2.neg"sv) { return makeUnary(s, UnaryOp::NegVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -807,16 +818,16 @@ switch (op[0]) {
                   case 'm': {
                     switch (op[8]) {
                       case 'a':
-                        if (strcmp(op, "f64x2.pmax") == 0) { return makeBinary(s, BinaryOp::PMaxVecF64x2); }
+                        if (op == "f64x2.pmax"sv) { return makeBinary(s, BinaryOp::PMaxVecF64x2); }
                         goto parse_error;
                       case 'i':
-                        if (strcmp(op, "f64x2.pmin") == 0) { return makeBinary(s, BinaryOp::PMinVecF64x2); }
+                        if (op == "f64x2.pmin"sv) { return makeBinary(s, BinaryOp::PMinVecF64x2); }
                         goto parse_error;
                       default: goto parse_error;
                     }
                   }
                   case 'r':
-                    if (strcmp(op, "f64x2.promote_low_f32x4") == 0) { return makeUnary(s, UnaryOp::PromoteLowVecF32x4ToVecF64x2); }
+                    if (op == "f64x2.promote_low_f32x4"sv) { return makeUnary(s, UnaryOp::PromoteLowVecF32x4ToVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -828,10 +839,10 @@ switch (op[0]) {
                       case 'f': {
                         switch (op[16]) {
                           case 'a':
-                            if (strcmp(op, "f64x2.relaxed_fma") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmaVecF64x2); }
+                            if (op == "f64x2.relaxed_fma"sv) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmaVecF64x2); }
                             goto parse_error;
                           case 's':
-                            if (strcmp(op, "f64x2.relaxed_fms") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmsVecF64x2); }
+                            if (op == "f64x2.relaxed_fms"sv) { return makeSIMDTernary(s, SIMDTernaryOp::RelaxedFmsVecF64x2); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -839,10 +850,10 @@ switch (op[0]) {
                       case 'm': {
                         switch (op[15]) {
                           case 'a':
-                            if (strcmp(op, "f64x2.relaxed_max") == 0) { return makeBinary(s, BinaryOp::RelaxedMaxVecF64x2); }
+                            if (op == "f64x2.relaxed_max"sv) { return makeBinary(s, BinaryOp::RelaxedMaxVecF64x2); }
                             goto parse_error;
                           case 'i':
-                            if (strcmp(op, "f64x2.relaxed_min") == 0) { return makeBinary(s, BinaryOp::RelaxedMinVecF64x2); }
+                            if (op == "f64x2.relaxed_min"sv) { return makeBinary(s, BinaryOp::RelaxedMinVecF64x2); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -851,7 +862,7 @@ switch (op[0]) {
                     }
                   }
                   case 'p':
-                    if (strcmp(op, "f64x2.replace_lane") == 0) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecF64x2, 2); }
+                    if (op == "f64x2.replace_lane"sv) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecF64x2, 2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -859,19 +870,19 @@ switch (op[0]) {
               case 's': {
                 switch (op[7]) {
                   case 'p':
-                    if (strcmp(op, "f64x2.splat") == 0) { return makeUnary(s, UnaryOp::SplatVecF64x2); }
+                    if (op == "f64x2.splat"sv) { return makeUnary(s, UnaryOp::SplatVecF64x2); }
                     goto parse_error;
                   case 'q':
-                    if (strcmp(op, "f64x2.sqrt") == 0) { return makeUnary(s, UnaryOp::SqrtVecF64x2); }
+                    if (op == "f64x2.sqrt"sv) { return makeUnary(s, UnaryOp::SqrtVecF64x2); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "f64x2.sub") == 0) { return makeBinary(s, BinaryOp::SubVecF64x2); }
+                    if (op == "f64x2.sub"sv) { return makeBinary(s, BinaryOp::SubVecF64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 't':
-                if (strcmp(op, "f64x2.trunc") == 0) { return makeUnary(s, UnaryOp::TruncVecF64x2); }
+                if (op == "f64x2.trunc"sv) { return makeUnary(s, UnaryOp::TruncVecF64x2); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -885,10 +896,10 @@ switch (op[0]) {
   case 'g': {
     switch (op[7]) {
       case 'g':
-        if (strcmp(op, "global.get") == 0) { return makeGlobalGet(s); }
+        if (op == "global.get"sv) { return makeGlobalGet(s); }
         goto parse_error;
       case 's':
-        if (strcmp(op, "global.set") == 0) { return makeGlobalSet(s); }
+        if (op == "global.set"sv) { return makeGlobalSet(s); }
         goto parse_error;
       default: goto parse_error;
     }
@@ -900,20 +911,20 @@ switch (op[0]) {
           case 'a': {
             switch (op[7]) {
               case 'b':
-                if (strcmp(op, "i16x8.abs") == 0) { return makeUnary(s, UnaryOp::AbsVecI16x8); }
+                if (op == "i16x8.abs"sv) { return makeUnary(s, UnaryOp::AbsVecI16x8); }
                 goto parse_error;
               case 'd': {
                 switch (op[9]) {
                   case '\0':
-                    if (strcmp(op, "i16x8.add") == 0) { return makeBinary(s, BinaryOp::AddVecI16x8); }
+                    if (op == "i16x8.add"sv) { return makeBinary(s, BinaryOp::AddVecI16x8); }
                     goto parse_error;
                   case '_': {
                     switch (op[14]) {
                       case 's':
-                        if (strcmp(op, "i16x8.add_sat_s") == 0) { return makeBinary(s, BinaryOp::AddSatSVecI16x8); }
+                        if (op == "i16x8.add_sat_s"sv) { return makeBinary(s, BinaryOp::AddSatSVecI16x8); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i16x8.add_sat_u") == 0) { return makeBinary(s, BinaryOp::AddSatUVecI16x8); }
+                        if (op == "i16x8.add_sat_u"sv) { return makeBinary(s, BinaryOp::AddSatUVecI16x8); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -922,42 +933,34 @@ switch (op[0]) {
                 }
               }
               case 'l':
-                if (strcmp(op, "i16x8.all_true") == 0) { return makeUnary(s, UnaryOp::AllTrueVecI16x8); }
+                if (op == "i16x8.all_true"sv) { return makeUnary(s, UnaryOp::AllTrueVecI16x8); }
                 goto parse_error;
               case 'v':
-                if (strcmp(op, "i16x8.avgr_u") == 0) { return makeBinary(s, BinaryOp::AvgrUVecI16x8); }
+                if (op == "i16x8.avgr_u"sv) { return makeBinary(s, BinaryOp::AvgrUVecI16x8); }
                 goto parse_error;
               default: goto parse_error;
             }
           }
           case 'b':
-            if (strcmp(op, "i16x8.bitmask") == 0) { return makeUnary(s, UnaryOp::BitmaskVecI16x8); }
+            if (op == "i16x8.bitmask"sv) { return makeUnary(s, UnaryOp::BitmaskVecI16x8); }
+            goto parse_error;
+          case 'd':
+            if (op == "i16x8.dot_i8x16_i7x16_s"sv) { return makeBinary(s, BinaryOp::DotI8x16I7x16SToVecI16x8); }
             goto parse_error;
-          case 'd': {
-            switch (op[22]) {
-              case 's':
-                if (strcmp(op, "i16x8.dot_i8x16_i7x16_s") == 0) { return makeBinary(s, BinaryOp::DotI8x16I7x16SToVecI16x8); }
-                goto parse_error;
-              case 'u':
-                if (strcmp(op, "i16x8.dot_i8x16_i7x16_u") == 0) { return makeBinary(s, BinaryOp::DotI8x16I7x16UToVecI16x8); }
-                goto parse_error;
-              default: goto parse_error;
-            }
-          }
           case 'e': {
             switch (op[7]) {
               case 'q':
-                if (strcmp(op, "i16x8.eq") == 0) { return makeBinary(s, BinaryOp::EqVecI16x8); }
+                if (op == "i16x8.eq"sv) { return makeBinary(s, BinaryOp::EqVecI16x8); }
                 goto parse_error;
               case 'x': {
                 switch (op[9]) {
                   case 'a': {
                     switch (op[28]) {
                       case 's':
-                        if (strcmp(op, "i16x8.extadd_pairwise_i8x16_s") == 0) { return makeUnary(s, UnaryOp::ExtAddPairwiseSVecI8x16ToI16x8); }
+                        if (op == "i16x8.extadd_pairwise_i8x16_s"sv) { return makeUnary(s, UnaryOp::ExtAddPairwiseSVecI8x16ToI16x8); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i16x8.extadd_pairwise_i8x16_u") == 0) { return makeUnary(s, UnaryOp::ExtAddPairwiseUVecI8x16ToI16x8); }
+                        if (op == "i16x8.extadd_pairwise_i8x16_u"sv) { return makeUnary(s, UnaryOp::ExtAddPairwiseUVecI8x16ToI16x8); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -967,10 +970,10 @@ switch (op[0]) {
                       case 'h': {
                         switch (op[24]) {
                           case 's':
-                            if (strcmp(op, "i16x8.extend_high_i8x16_s") == 0) { return makeUnary(s, UnaryOp::ExtendHighSVecI8x16ToVecI16x8); }
+                            if (op == "i16x8.extend_high_i8x16_s"sv) { return makeUnary(s, UnaryOp::ExtendHighSVecI8x16ToVecI16x8); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i16x8.extend_high_i8x16_u") == 0) { return makeUnary(s, UnaryOp::ExtendHighUVecI8x16ToVecI16x8); }
+                            if (op == "i16x8.extend_high_i8x16_u"sv) { return makeUnary(s, UnaryOp::ExtendHighUVecI8x16ToVecI16x8); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -978,10 +981,10 @@ switch (op[0]) {
                       case 'l': {
                         switch (op[23]) {
                           case 's':
-                            if (strcmp(op, "i16x8.extend_low_i8x16_s") == 0) { return makeUnary(s, UnaryOp::ExtendLowSVecI8x16ToVecI16x8); }
+                            if (op == "i16x8.extend_low_i8x16_s"sv) { return makeUnary(s, UnaryOp::ExtendLowSVecI8x16ToVecI16x8); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i16x8.extend_low_i8x16_u") == 0) { return makeUnary(s, UnaryOp::ExtendLowUVecI8x16ToVecI16x8); }
+                            if (op == "i16x8.extend_low_i8x16_u"sv) { return makeUnary(s, UnaryOp::ExtendLowUVecI8x16ToVecI16x8); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -994,10 +997,10 @@ switch (op[0]) {
                       case 'h': {
                         switch (op[24]) {
                           case 's':
-                            if (strcmp(op, "i16x8.extmul_high_i8x16_s") == 0) { return makeBinary(s, BinaryOp::ExtMulHighSVecI16x8); }
+                            if (op == "i16x8.extmul_high_i8x16_s"sv) { return makeBinary(s, BinaryOp::ExtMulHighSVecI16x8); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i16x8.extmul_high_i8x16_u") == 0) { return makeBinary(s, BinaryOp::ExtMulHighUVecI16x8); }
+                            if (op == "i16x8.extmul_high_i8x16_u"sv) { return makeBinary(s, BinaryOp::ExtMulHighUVecI16x8); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1005,10 +1008,10 @@ switch (op[0]) {
                       case 'l': {
                         switch (op[23]) {
                           case 's':
-                            if (strcmp(op, "i16x8.extmul_low_i8x16_s") == 0) { return makeBinary(s, BinaryOp::ExtMulLowSVecI16x8); }
+                            if (op == "i16x8.extmul_low_i8x16_s"sv) { return makeBinary(s, BinaryOp::ExtMulLowSVecI16x8); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i16x8.extmul_low_i8x16_u") == 0) { return makeBinary(s, BinaryOp::ExtMulLowUVecI16x8); }
+                            if (op == "i16x8.extmul_low_i8x16_u"sv) { return makeBinary(s, BinaryOp::ExtMulLowUVecI16x8); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1019,10 +1022,10 @@ switch (op[0]) {
                   case 'r': {
                     switch (op[19]) {
                       case 's':
-                        if (strcmp(op, "i16x8.extract_lane_s") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneSVecI16x8, 8); }
+                        if (op == "i16x8.extract_lane_s"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneSVecI16x8, 8); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i16x8.extract_lane_u") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneUVecI16x8, 8); }
+                        if (op == "i16x8.extract_lane_u"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneUVecI16x8, 8); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1038,10 +1041,10 @@ switch (op[0]) {
               case 'e': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i16x8.ge_s") == 0) { return makeBinary(s, BinaryOp::GeSVecI16x8); }
+                    if (op == "i16x8.ge_s"sv) { return makeBinary(s, BinaryOp::GeSVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.ge_u") == 0) { return makeBinary(s, BinaryOp::GeUVecI16x8); }
+                    if (op == "i16x8.ge_u"sv) { return makeBinary(s, BinaryOp::GeUVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1049,10 +1052,10 @@ switch (op[0]) {
               case 't': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i16x8.gt_s") == 0) { return makeBinary(s, BinaryOp::GtSVecI16x8); }
+                    if (op == "i16x8.gt_s"sv) { return makeBinary(s, BinaryOp::GtSVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.gt_u") == 0) { return makeBinary(s, BinaryOp::GtUVecI16x8); }
+                    if (op == "i16x8.gt_u"sv) { return makeBinary(s, BinaryOp::GtUVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1063,15 +1066,15 @@ switch (op[0]) {
           case 'l': {
             switch (op[7]) {
               case 'a':
-                if (strcmp(op, "i16x8.laneselect") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI16x8); }
+                if (op == "i16x8.laneselect"sv) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI16x8); }
                 goto parse_error;
               case 'e': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i16x8.le_s") == 0) { return makeBinary(s, BinaryOp::LeSVecI16x8); }
+                    if (op == "i16x8.le_s"sv) { return makeBinary(s, BinaryOp::LeSVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.le_u") == 0) { return makeBinary(s, BinaryOp::LeUVecI16x8); }
+                    if (op == "i16x8.le_u"sv) { return makeBinary(s, BinaryOp::LeUVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1079,10 +1082,10 @@ switch (op[0]) {
               case 't': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i16x8.lt_s") == 0) { return makeBinary(s, BinaryOp::LtSVecI16x8); }
+                    if (op == "i16x8.lt_s"sv) { return makeBinary(s, BinaryOp::LtSVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.lt_u") == 0) { return makeBinary(s, BinaryOp::LtUVecI16x8); }
+                    if (op == "i16x8.lt_u"sv) { return makeBinary(s, BinaryOp::LtUVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1095,10 +1098,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[10]) {
                   case 's':
-                    if (strcmp(op, "i16x8.max_s") == 0) { return makeBinary(s, BinaryOp::MaxSVecI16x8); }
+                    if (op == "i16x8.max_s"sv) { return makeBinary(s, BinaryOp::MaxSVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.max_u") == 0) { return makeBinary(s, BinaryOp::MaxUVecI16x8); }
+                    if (op == "i16x8.max_u"sv) { return makeBinary(s, BinaryOp::MaxUVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1106,16 +1109,16 @@ switch (op[0]) {
               case 'i': {
                 switch (op[10]) {
                   case 's':
-                    if (strcmp(op, "i16x8.min_s") == 0) { return makeBinary(s, BinaryOp::MinSVecI16x8); }
+                    if (op == "i16x8.min_s"sv) { return makeBinary(s, BinaryOp::MinSVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.min_u") == 0) { return makeBinary(s, BinaryOp::MinUVecI16x8); }
+                    if (op == "i16x8.min_u"sv) { return makeBinary(s, BinaryOp::MinUVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'u':
-                if (strcmp(op, "i16x8.mul") == 0) { return makeBinary(s, BinaryOp::MulVecI16x8); }
+                if (op == "i16x8.mul"sv) { return makeBinary(s, BinaryOp::MulVecI16x8); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -1125,10 +1128,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[19]) {
                   case 's':
-                    if (strcmp(op, "i16x8.narrow_i32x4_s") == 0) { return makeBinary(s, BinaryOp::NarrowSVecI32x4ToVecI16x8); }
+                    if (op == "i16x8.narrow_i32x4_s"sv) { return makeBinary(s, BinaryOp::NarrowSVecI32x4ToVecI16x8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i16x8.narrow_i32x4_u") == 0) { return makeBinary(s, BinaryOp::NarrowUVecI32x4ToVecI16x8); }
+                    if (op == "i16x8.narrow_i32x4_u"sv) { return makeBinary(s, BinaryOp::NarrowUVecI32x4ToVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1136,10 +1139,10 @@ switch (op[0]) {
               case 'e': {
                 switch (op[8]) {
                   case '\0':
-                    if (strcmp(op, "i16x8.ne") == 0) { return makeBinary(s, BinaryOp::NeVecI16x8); }
+                    if (op == "i16x8.ne"sv) { return makeBinary(s, BinaryOp::NeVecI16x8); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "i16x8.neg") == 0) { return makeUnary(s, UnaryOp::NegVecI16x8); }
+                    if (op == "i16x8.neg"sv) { return makeUnary(s, UnaryOp::NegVecI16x8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1148,15 +1151,15 @@ switch (op[0]) {
             }
           }
           case 'q':
-            if (strcmp(op, "i16x8.q15mulr_sat_s") == 0) { return makeBinary(s, BinaryOp::Q15MulrSatSVecI16x8); }
+            if (op == "i16x8.q15mulr_sat_s"sv) { return makeBinary(s, BinaryOp::Q15MulrSatSVecI16x8); }
             goto parse_error;
           case 'r': {
             switch (op[8]) {
               case 'l':
-                if (strcmp(op, "i16x8.relaxed_q15mulr_s") == 0) { return makeBinary(s, BinaryOp::RelaxedQ15MulrSVecI16x8); }
+                if (op == "i16x8.relaxed_q15mulr_s"sv) { return makeBinary(s, BinaryOp::RelaxedQ15MulrSVecI16x8); }
                 goto parse_error;
               case 'p':
-                if (strcmp(op, "i16x8.replace_lane") == 0) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI16x8, 8); }
+                if (op == "i16x8.replace_lane"sv) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI16x8, 8); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -1166,15 +1169,15 @@ switch (op[0]) {
               case 'h': {
                 switch (op[8]) {
                   case 'l':
-                    if (strcmp(op, "i16x8.shl") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI16x8); }
+                    if (op == "i16x8.shl"sv) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI16x8); }
                     goto parse_error;
                   case 'r': {
                     switch (op[10]) {
                       case 's':
-                        if (strcmp(op, "i16x8.shr_s") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI16x8); }
+                        if (op == "i16x8.shr_s"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI16x8); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i16x8.shr_u") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI16x8); }
+                        if (op == "i16x8.shr_u"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI16x8); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1183,20 +1186,20 @@ switch (op[0]) {
                 }
               }
               case 'p':
-                if (strcmp(op, "i16x8.splat") == 0) { return makeUnary(s, UnaryOp::SplatVecI16x8); }
+                if (op == "i16x8.splat"sv) { return makeUnary(s, UnaryOp::SplatVecI16x8); }
                 goto parse_error;
               case 'u': {
                 switch (op[9]) {
                   case '\0':
-                    if (strcmp(op, "i16x8.sub") == 0) { return makeBinary(s, BinaryOp::SubVecI16x8); }
+                    if (op == "i16x8.sub"sv) { return makeBinary(s, BinaryOp::SubVecI16x8); }
                     goto parse_error;
                   case '_': {
                     switch (op[14]) {
                       case 's':
-                        if (strcmp(op, "i16x8.sub_sat_s") == 0) { return makeBinary(s, BinaryOp::SubSatSVecI16x8); }
+                        if (op == "i16x8.sub_sat_s"sv) { return makeBinary(s, BinaryOp::SubSatSVecI16x8); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i16x8.sub_sat_u") == 0) { return makeBinary(s, BinaryOp::SubSatUVecI16x8); }
+                        if (op == "i16x8.sub_sat_u"sv) { return makeBinary(s, BinaryOp::SubSatUVecI16x8); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1217,16 +1220,16 @@ switch (op[0]) {
               case 'g': {
                 switch (op[8]) {
                   case 's':
-                    if (strcmp(op, "i31.get_s") == 0) { return makeI31Get(s, true); }
+                    if (op == "i31.get_s"sv) { return makeI31Get(s, true); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i31.get_u") == 0) { return makeI31Get(s, false); }
+                    if (op == "i31.get_u"sv) { return makeI31Get(s, false); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'n':
-                if (strcmp(op, "i31.new") == 0) { return makeI31New(s); }
+                if (op == "i31.new"sv) { return makeI31New(s); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -1238,23 +1241,23 @@ switch (op[0]) {
                   case 'a': {
                     switch (op[5]) {
                       case 'd':
-                        if (strcmp(op, "i32.add") == 0) { return makeBinary(s, BinaryOp::AddInt32); }
+                        if (op == "i32.add"sv) { return makeBinary(s, BinaryOp::AddInt32); }
                         goto parse_error;
                       case 'n':
-                        if (strcmp(op, "i32.and") == 0) { return makeBinary(s, BinaryOp::AndInt32); }
+                        if (op == "i32.and"sv) { return makeBinary(s, BinaryOp::AndInt32); }
                         goto parse_error;
                       case 't': {
                         switch (op[11]) {
                           case 'l': {
                             switch (op[15]) {
                               case '\0':
-                                if (strcmp(op, "i32.atomic.load") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/true); }
+                                if (op == "i32.atomic.load"sv) { return makeLoad(s, Type::i32, /*signed=*/false, 4, /*isAtomic=*/true); }
                                 goto parse_error;
                               case '1':
-                                if (strcmp(op, "i32.atomic.load16_u") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/true); }
+                                if (op == "i32.atomic.load16_u"sv) { return makeLoad(s, Type::i32, /*signed=*/false, 2, /*isAtomic=*/true); }
                                 goto parse_error;
                               case '8':
-                                if (strcmp(op, "i32.atomic.load8_u") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/true); }
+                                if (op == "i32.atomic.load8_u"sv) { return makeLoad(s, Type::i32, /*signed=*/false, 1, /*isAtomic=*/true); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1266,30 +1269,30 @@ switch (op[0]) {
                                   case 'a': {
                                     switch (op[16]) {
                                       case 'd':
-                                        if (strcmp(op, "i32.atomic.rmw.add") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw.add"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i32, 4); }
                                         goto parse_error;
                                       case 'n':
-                                        if (strcmp(op, "i32.atomic.rmw.and") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw.and"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i32, 4); }
                                         goto parse_error;
                                       default: goto parse_error;
                                     }
                                   }
                                   case 'c':
-                                    if (strcmp(op, "i32.atomic.rmw.cmpxchg") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw.cmpxchg"sv) { return makeAtomicCmpxchg(s, Type::i32, 4); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i32.atomic.rmw.or") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw.or"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i32, 4); }
                                     goto parse_error;
                                   case 's':
-                                    if (strcmp(op, "i32.atomic.rmw.sub") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw.sub"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i32, 4); }
                                     goto parse_error;
                                   case 'x': {
                                     switch (op[16]) {
                                       case 'c':
-                                        if (strcmp(op, "i32.atomic.rmw.xchg") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw.xchg"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i32, 4); }
                                         goto parse_error;
                                       case 'o':
-                                        if (strcmp(op, "i32.atomic.rmw.xor") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw.xor"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i32, 4); }
                                         goto parse_error;
                                       default: goto parse_error;
                                     }
@@ -1302,30 +1305,30 @@ switch (op[0]) {
                                   case 'a': {
                                     switch (op[18]) {
                                       case 'd':
-                                        if (strcmp(op, "i32.atomic.rmw16.add_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw16.add_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i32, 2); }
                                         goto parse_error;
                                       case 'n':
-                                        if (strcmp(op, "i32.atomic.rmw16.and_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw16.and_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i32, 2); }
                                         goto parse_error;
                                       default: goto parse_error;
                                     }
                                   }
                                   case 'c':
-                                    if (strcmp(op, "i32.atomic.rmw16.cmpxchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw16.cmpxchg_u"sv) { return makeAtomicCmpxchg(s, Type::i32, 2); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i32.atomic.rmw16.or_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw16.or_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i32, 2); }
                                     goto parse_error;
                                   case 's':
-                                    if (strcmp(op, "i32.atomic.rmw16.sub_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw16.sub_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i32, 2); }
                                     goto parse_error;
                                   case 'x': {
                                     switch (op[18]) {
                                       case 'c':
-                                        if (strcmp(op, "i32.atomic.rmw16.xchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw16.xchg_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i32, 2); }
                                         goto parse_error;
                                       case 'o':
-                                        if (strcmp(op, "i32.atomic.rmw16.xor_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw16.xor_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i32, 2); }
                                         goto parse_error;
                                       default: goto parse_error;
                                     }
@@ -1338,30 +1341,30 @@ switch (op[0]) {
                                   case 'a': {
                                     switch (op[17]) {
                                       case 'd':
-                                        if (strcmp(op, "i32.atomic.rmw8.add_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw8.add_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i32, 1); }
                                         goto parse_error;
                                       case 'n':
-                                        if (strcmp(op, "i32.atomic.rmw8.and_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw8.and_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i32, 1); }
                                         goto parse_error;
                                       default: goto parse_error;
                                     }
                                   }
                                   case 'c':
-                                    if (strcmp(op, "i32.atomic.rmw8.cmpxchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw8.cmpxchg_u"sv) { return makeAtomicCmpxchg(s, Type::i32, 1); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i32.atomic.rmw8.or_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw8.or_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i32, 1); }
                                     goto parse_error;
                                   case 's':
-                                    if (strcmp(op, "i32.atomic.rmw8.sub_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                    if (op == "i32.atomic.rmw8.sub_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i32, 1); }
                                     goto parse_error;
                                   case 'x': {
                                     switch (op[17]) {
                                       case 'c':
-                                        if (strcmp(op, "i32.atomic.rmw8.xchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw8.xchg_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i32, 1); }
                                         goto parse_error;
                                       case 'o':
-                                        if (strcmp(op, "i32.atomic.rmw8.xor_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i32); }
+                                        if (op == "i32.atomic.rmw8.xor_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i32, 1); }
                                         goto parse_error;
                                       default: goto parse_error;
                                     }
@@ -1375,13 +1378,13 @@ switch (op[0]) {
                           case 's': {
                             switch (op[16]) {
                               case '\0':
-                                if (strcmp(op, "i32.atomic.store") == 0) { return makeStore(s, Type::i32, /*isAtomic=*/true); }
+                                if (op == "i32.atomic.store"sv) { return makeStore(s, Type::i32, 4, /*isAtomic=*/true); }
                                 goto parse_error;
                               case '1':
-                                if (strcmp(op, "i32.atomic.store16") == 0) { return makeStore(s, Type::i32, /*isAtomic=*/true); }
+                                if (op == "i32.atomic.store16"sv) { return makeStore(s, Type::i32, 2, /*isAtomic=*/true); }
                                 goto parse_error;
                               case '8':
-                                if (strcmp(op, "i32.atomic.store8") == 0) { return makeStore(s, Type::i32, /*isAtomic=*/true); }
+                                if (op == "i32.atomic.store8"sv) { return makeStore(s, Type::i32, 1, /*isAtomic=*/true); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1395,13 +1398,13 @@ switch (op[0]) {
                   case 'c': {
                     switch (op[5]) {
                       case 'l':
-                        if (strcmp(op, "i32.clz") == 0) { return makeUnary(s, UnaryOp::ClzInt32); }
+                        if (op == "i32.clz"sv) { return makeUnary(s, UnaryOp::ClzInt32); }
                         goto parse_error;
                       case 'o':
-                        if (strcmp(op, "i32.const") == 0) { return makeConst(s, Type::i32); }
+                        if (op == "i32.const"sv) { return makeConst(s, Type::i32); }
                         goto parse_error;
                       case 't':
-                        if (strcmp(op, "i32.ctz") == 0) { return makeUnary(s, UnaryOp::CtzInt32); }
+                        if (op == "i32.ctz"sv) { return makeUnary(s, UnaryOp::CtzInt32); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1409,10 +1412,10 @@ switch (op[0]) {
                   case 'd': {
                     switch (op[8]) {
                       case 's':
-                        if (strcmp(op, "i32.div_s") == 0) { return makeBinary(s, BinaryOp::DivSInt32); }
+                        if (op == "i32.div_s"sv) { return makeBinary(s, BinaryOp::DivSInt32); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i32.div_u") == 0) { return makeBinary(s, BinaryOp::DivUInt32); }
+                        if (op == "i32.div_u"sv) { return makeBinary(s, BinaryOp::DivUInt32); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1422,10 +1425,10 @@ switch (op[0]) {
                       case 'q': {
                         switch (op[6]) {
                           case '\0':
-                            if (strcmp(op, "i32.eq") == 0) { return makeBinary(s, BinaryOp::EqInt32); }
+                            if (op == "i32.eq"sv) { return makeBinary(s, BinaryOp::EqInt32); }
                             goto parse_error;
                           case 'z':
-                            if (strcmp(op, "i32.eqz") == 0) { return makeUnary(s, UnaryOp::EqZInt32); }
+                            if (op == "i32.eqz"sv) { return makeUnary(s, UnaryOp::EqZInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1433,10 +1436,10 @@ switch (op[0]) {
                       case 'x': {
                         switch (op[10]) {
                           case '1':
-                            if (strcmp(op, "i32.extend16_s") == 0) { return makeUnary(s, UnaryOp::ExtendS16Int32); }
+                            if (op == "i32.extend16_s"sv) { return makeUnary(s, UnaryOp::ExtendS16Int32); }
                             goto parse_error;
                           case '8':
-                            if (strcmp(op, "i32.extend8_s") == 0) { return makeUnary(s, UnaryOp::ExtendS8Int32); }
+                            if (op == "i32.extend8_s"sv) { return makeUnary(s, UnaryOp::ExtendS8Int32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1449,10 +1452,10 @@ switch (op[0]) {
                       case 'e': {
                         switch (op[7]) {
                           case 's':
-                            if (strcmp(op, "i32.ge_s") == 0) { return makeBinary(s, BinaryOp::GeSInt32); }
+                            if (op == "i32.ge_s"sv) { return makeBinary(s, BinaryOp::GeSInt32); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32.ge_u") == 0) { return makeBinary(s, BinaryOp::GeUInt32); }
+                            if (op == "i32.ge_u"sv) { return makeBinary(s, BinaryOp::GeUInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1460,10 +1463,10 @@ switch (op[0]) {
                       case 't': {
                         switch (op[7]) {
                           case 's':
-                            if (strcmp(op, "i32.gt_s") == 0) { return makeBinary(s, BinaryOp::GtSInt32); }
+                            if (op == "i32.gt_s"sv) { return makeBinary(s, BinaryOp::GtSInt32); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32.gt_u") == 0) { return makeBinary(s, BinaryOp::GtUInt32); }
+                            if (op == "i32.gt_u"sv) { return makeBinary(s, BinaryOp::GtUInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1476,10 +1479,10 @@ switch (op[0]) {
                       case 'e': {
                         switch (op[7]) {
                           case 's':
-                            if (strcmp(op, "i32.le_s") == 0) { return makeBinary(s, BinaryOp::LeSInt32); }
+                            if (op == "i32.le_s"sv) { return makeBinary(s, BinaryOp::LeSInt32); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32.le_u") == 0) { return makeBinary(s, BinaryOp::LeUInt32); }
+                            if (op == "i32.le_u"sv) { return makeBinary(s, BinaryOp::LeUInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1487,15 +1490,15 @@ switch (op[0]) {
                       case 'o': {
                         switch (op[8]) {
                           case '\0':
-                            if (strcmp(op, "i32.load") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/false); }
+                            if (op == "i32.load"sv) { return makeLoad(s, Type::i32, /*signed=*/false, 4, /*isAtomic=*/false); }
                             goto parse_error;
                           case '1': {
                             switch (op[11]) {
                               case 's':
-                                if (strcmp(op, "i32.load16_s") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/false); }
+                                if (op == "i32.load16_s"sv) { return makeLoad(s, Type::i32, /*signed=*/true, 2, /*isAtomic=*/false); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.load16_u") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/false); }
+                                if (op == "i32.load16_u"sv) { return makeLoad(s, Type::i32, /*signed=*/false, 2, /*isAtomic=*/false); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1503,10 +1506,10 @@ switch (op[0]) {
                           case '8': {
                             switch (op[10]) {
                               case 's':
-                                if (strcmp(op, "i32.load8_s") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/false); }
+                                if (op == "i32.load8_s"sv) { return makeLoad(s, Type::i32, /*signed=*/true, 1, /*isAtomic=*/false); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.load8_u") == 0) { return makeLoad(s, Type::i32, /*isAtomic=*/false); }
+                                if (op == "i32.load8_u"sv) { return makeLoad(s, Type::i32, /*signed=*/false, 1, /*isAtomic=*/false); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1517,10 +1520,10 @@ switch (op[0]) {
                       case 't': {
                         switch (op[7]) {
                           case 's':
-                            if (strcmp(op, "i32.lt_s") == 0) { return makeBinary(s, BinaryOp::LtSInt32); }
+                            if (op == "i32.lt_s"sv) { return makeBinary(s, BinaryOp::LtSInt32); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32.lt_u") == 0) { return makeBinary(s, BinaryOp::LtUInt32); }
+                            if (op == "i32.lt_u"sv) { return makeBinary(s, BinaryOp::LtUInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1529,31 +1532,31 @@ switch (op[0]) {
                     }
                   }
                   case 'm':
-                    if (strcmp(op, "i32.mul") == 0) { return makeBinary(s, BinaryOp::MulInt32); }
+                    if (op == "i32.mul"sv) { return makeBinary(s, BinaryOp::MulInt32); }
                     goto parse_error;
                   case 'n':
-                    if (strcmp(op, "i32.ne") == 0) { return makeBinary(s, BinaryOp::NeInt32); }
+                    if (op == "i32.ne"sv) { return makeBinary(s, BinaryOp::NeInt32); }
                     goto parse_error;
                   case 'o':
-                    if (strcmp(op, "i32.or") == 0) { return makeBinary(s, BinaryOp::OrInt32); }
+                    if (op == "i32.or"sv) { return makeBinary(s, BinaryOp::OrInt32); }
                     goto parse_error;
                   case 'p':
-                    if (strcmp(op, "i32.popcnt") == 0) { return makeUnary(s, UnaryOp::PopcntInt32); }
+                    if (op == "i32.popcnt"sv) { return makeUnary(s, UnaryOp::PopcntInt32); }
                     goto parse_error;
                   case 'r': {
                     switch (op[5]) {
                       case 'e': {
                         switch (op[6]) {
                           case 'i':
-                            if (strcmp(op, "i32.reinterpret_f32") == 0) { return makeUnary(s, UnaryOp::ReinterpretFloat32); }
+                            if (op == "i32.reinterpret_f32"sv) { return makeUnary(s, UnaryOp::ReinterpretFloat32); }
                             goto parse_error;
                           case 'm': {
                             switch (op[8]) {
                               case 's':
-                                if (strcmp(op, "i32.rem_s") == 0) { return makeBinary(s, BinaryOp::RemSInt32); }
+                                if (op == "i32.rem_s"sv) { return makeBinary(s, BinaryOp::RemSInt32); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.rem_u") == 0) { return makeBinary(s, BinaryOp::RemUInt32); }
+                                if (op == "i32.rem_u"sv) { return makeBinary(s, BinaryOp::RemUInt32); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1564,10 +1567,10 @@ switch (op[0]) {
                       case 'o': {
                         switch (op[7]) {
                           case 'l':
-                            if (strcmp(op, "i32.rotl") == 0) { return makeBinary(s, BinaryOp::RotLInt32); }
+                            if (op == "i32.rotl"sv) { return makeBinary(s, BinaryOp::RotLInt32); }
                             goto parse_error;
                           case 'r':
-                            if (strcmp(op, "i32.rotr") == 0) { return makeBinary(s, BinaryOp::RotRInt32); }
+                            if (op == "i32.rotr"sv) { return makeBinary(s, BinaryOp::RotRInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1580,15 +1583,15 @@ switch (op[0]) {
                       case 'h': {
                         switch (op[6]) {
                           case 'l':
-                            if (strcmp(op, "i32.shl") == 0) { return makeBinary(s, BinaryOp::ShlInt32); }
+                            if (op == "i32.shl"sv) { return makeBinary(s, BinaryOp::ShlInt32); }
                             goto parse_error;
                           case 'r': {
                             switch (op[8]) {
                               case 's':
-                                if (strcmp(op, "i32.shr_s") == 0) { return makeBinary(s, BinaryOp::ShrSInt32); }
+                                if (op == "i32.shr_s"sv) { return makeBinary(s, BinaryOp::ShrSInt32); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.shr_u") == 0) { return makeBinary(s, BinaryOp::ShrUInt32); }
+                                if (op == "i32.shr_u"sv) { return makeBinary(s, BinaryOp::ShrUInt32); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1599,19 +1602,19 @@ switch (op[0]) {
                       case 't': {
                         switch (op[9]) {
                           case '\0':
-                            if (strcmp(op, "i32.store") == 0) { return makeStore(s, Type::i32, /*isAtomic=*/false); }
+                            if (op == "i32.store"sv) { return makeStore(s, Type::i32, 4, /*isAtomic=*/false); }
                             goto parse_error;
                           case '1':
-                            if (strcmp(op, "i32.store16") == 0) { return makeStore(s, Type::i32, /*isAtomic=*/false); }
+                            if (op == "i32.store16"sv) { return makeStore(s, Type::i32, 2, /*isAtomic=*/false); }
                             goto parse_error;
                           case '8':
-                            if (strcmp(op, "i32.store8") == 0) { return makeStore(s, Type::i32, /*isAtomic=*/false); }
+                            if (op == "i32.store8"sv) { return makeStore(s, Type::i32, 1, /*isAtomic=*/false); }
                             goto parse_error;
                           default: goto parse_error;
                         }
                       }
                       case 'u':
-                        if (strcmp(op, "i32.sub") == 0) { return makeBinary(s, BinaryOp::SubInt32); }
+                        if (op == "i32.sub"sv) { return makeBinary(s, BinaryOp::SubInt32); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1623,10 +1626,10 @@ switch (op[0]) {
                           case '3': {
                             switch (op[14]) {
                               case 's':
-                                if (strcmp(op, "i32.trunc_f32_s") == 0) { return makeUnary(s, UnaryOp::TruncSFloat32ToInt32); }
+                                if (op == "i32.trunc_f32_s"sv) { return makeUnary(s, UnaryOp::TruncSFloat32ToInt32); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.trunc_f32_u") == 0) { return makeUnary(s, UnaryOp::TruncUFloat32ToInt32); }
+                                if (op == "i32.trunc_f32_u"sv) { return makeUnary(s, UnaryOp::TruncUFloat32ToInt32); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1634,10 +1637,10 @@ switch (op[0]) {
                           case '6': {
                             switch (op[14]) {
                               case 's':
-                                if (strcmp(op, "i32.trunc_f64_s") == 0) { return makeUnary(s, UnaryOp::TruncSFloat64ToInt32); }
+                                if (op == "i32.trunc_f64_s"sv) { return makeUnary(s, UnaryOp::TruncSFloat64ToInt32); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.trunc_f64_u") == 0) { return makeUnary(s, UnaryOp::TruncUFloat64ToInt32); }
+                                if (op == "i32.trunc_f64_u"sv) { return makeUnary(s, UnaryOp::TruncUFloat64ToInt32); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1650,10 +1653,10 @@ switch (op[0]) {
                           case '3': {
                             switch (op[18]) {
                               case 's':
-                                if (strcmp(op, "i32.trunc_sat_f32_s") == 0) { return makeUnary(s, UnaryOp::TruncSatSFloat32ToInt32); }
+                                if (op == "i32.trunc_sat_f32_s"sv) { return makeUnary(s, UnaryOp::TruncSatSFloat32ToInt32); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.trunc_sat_f32_u") == 0) { return makeUnary(s, UnaryOp::TruncSatUFloat32ToInt32); }
+                                if (op == "i32.trunc_sat_f32_u"sv) { return makeUnary(s, UnaryOp::TruncSatUFloat32ToInt32); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1661,10 +1664,10 @@ switch (op[0]) {
                           case '6': {
                             switch (op[18]) {
                               case 's':
-                                if (strcmp(op, "i32.trunc_sat_f64_s") == 0) { return makeUnary(s, UnaryOp::TruncSatSFloat64ToInt32); }
+                                if (op == "i32.trunc_sat_f64_s"sv) { return makeUnary(s, UnaryOp::TruncSatSFloat64ToInt32); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32.trunc_sat_f64_u") == 0) { return makeUnary(s, UnaryOp::TruncSatUFloat64ToInt32); }
+                                if (op == "i32.trunc_sat_f64_u"sv) { return makeUnary(s, UnaryOp::TruncSatUFloat64ToInt32); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1676,10 +1679,10 @@ switch (op[0]) {
                     }
                   }
                   case 'w':
-                    if (strcmp(op, "i32.wrap_i64") == 0) { return makeUnary(s, UnaryOp::WrapInt64); }
+                    if (op == "i32.wrap_i64"sv) { return makeUnary(s, UnaryOp::WrapInt64); }
                     goto parse_error;
                   case 'x':
-                    if (strcmp(op, "i32.xor") == 0) { return makeBinary(s, BinaryOp::XorInt32); }
+                    if (op == "i32.xor"sv) { return makeBinary(s, BinaryOp::XorInt32); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -1689,53 +1692,45 @@ switch (op[0]) {
                   case 'a': {
                     switch (op[7]) {
                       case 'b':
-                        if (strcmp(op, "i32x4.abs") == 0) { return makeUnary(s, UnaryOp::AbsVecI32x4); }
+                        if (op == "i32x4.abs"sv) { return makeUnary(s, UnaryOp::AbsVecI32x4); }
                         goto parse_error;
                       case 'd':
-                        if (strcmp(op, "i32x4.add") == 0) { return makeBinary(s, BinaryOp::AddVecI32x4); }
+                        if (op == "i32x4.add"sv) { return makeBinary(s, BinaryOp::AddVecI32x4); }
                         goto parse_error;
                       case 'l':
-                        if (strcmp(op, "i32x4.all_true") == 0) { return makeUnary(s, UnaryOp::AllTrueVecI32x4); }
+                        if (op == "i32x4.all_true"sv) { return makeUnary(s, UnaryOp::AllTrueVecI32x4); }
                         goto parse_error;
                       default: goto parse_error;
                     }
                   }
                   case 'b':
-                    if (strcmp(op, "i32x4.bitmask") == 0) { return makeUnary(s, UnaryOp::BitmaskVecI32x4); }
+                    if (op == "i32x4.bitmask"sv) { return makeUnary(s, UnaryOp::BitmaskVecI32x4); }
                     goto parse_error;
                   case 'd': {
                     switch (op[11]) {
                       case '1':
-                        if (strcmp(op, "i32x4.dot_i16x8_s") == 0) { return makeBinary(s, BinaryOp::DotSVecI16x8ToVecI32x4); }
+                        if (op == "i32x4.dot_i16x8_s"sv) { return makeBinary(s, BinaryOp::DotSVecI16x8ToVecI32x4); }
+                        goto parse_error;
+                      case '8':
+                        if (op == "i32x4.dot_i8x16_i7x16_add_s"sv) { return makeSIMDTernary(s, SIMDTernaryOp::DotI8x16I7x16AddSToVecI32x4); }
                         goto parse_error;
-                      case '8': {
-                        switch (op[26]) {
-                          case 's':
-                            if (strcmp(op, "i32x4.dot_i8x16_i7x16_add_s") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::DotI8x16I7x16AddSToVecI32x4); }
-                            goto parse_error;
-                          case 'u':
-                            if (strcmp(op, "i32x4.dot_i8x16_i7x16_add_u") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::DotI8x16I7x16AddUToVecI32x4); }
-                            goto parse_error;
-                          default: goto parse_error;
-                        }
-                      }
                       default: goto parse_error;
                     }
                   }
                   case 'e': {
                     switch (op[7]) {
                       case 'q':
-                        if (strcmp(op, "i32x4.eq") == 0) { return makeBinary(s, BinaryOp::EqVecI32x4); }
+                        if (op == "i32x4.eq"sv) { return makeBinary(s, BinaryOp::EqVecI32x4); }
                         goto parse_error;
                       case 'x': {
                         switch (op[9]) {
                           case 'a': {
                             switch (op[28]) {
                               case 's':
-                                if (strcmp(op, "i32x4.extadd_pairwise_i16x8_s") == 0) { return makeUnary(s, UnaryOp::ExtAddPairwiseSVecI16x8ToI32x4); }
+                                if (op == "i32x4.extadd_pairwise_i16x8_s"sv) { return makeUnary(s, UnaryOp::ExtAddPairwiseSVecI16x8ToI32x4); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32x4.extadd_pairwise_i16x8_u") == 0) { return makeUnary(s, UnaryOp::ExtAddPairwiseUVecI16x8ToI32x4); }
+                                if (op == "i32x4.extadd_pairwise_i16x8_u"sv) { return makeUnary(s, UnaryOp::ExtAddPairwiseUVecI16x8ToI32x4); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1745,10 +1740,10 @@ switch (op[0]) {
                               case 'h': {
                                 switch (op[24]) {
                                   case 's':
-                                    if (strcmp(op, "i32x4.extend_high_i16x8_s") == 0) { return makeUnary(s, UnaryOp::ExtendHighSVecI16x8ToVecI32x4); }
+                                    if (op == "i32x4.extend_high_i16x8_s"sv) { return makeUnary(s, UnaryOp::ExtendHighSVecI16x8ToVecI32x4); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "i32x4.extend_high_i16x8_u") == 0) { return makeUnary(s, UnaryOp::ExtendHighUVecI16x8ToVecI32x4); }
+                                    if (op == "i32x4.extend_high_i16x8_u"sv) { return makeUnary(s, UnaryOp::ExtendHighUVecI16x8ToVecI32x4); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -1756,10 +1751,10 @@ switch (op[0]) {
                               case 'l': {
                                 switch (op[23]) {
                                   case 's':
-                                    if (strcmp(op, "i32x4.extend_low_i16x8_s") == 0) { return makeUnary(s, UnaryOp::ExtendLowSVecI16x8ToVecI32x4); }
+                                    if (op == "i32x4.extend_low_i16x8_s"sv) { return makeUnary(s, UnaryOp::ExtendLowSVecI16x8ToVecI32x4); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "i32x4.extend_low_i16x8_u") == 0) { return makeUnary(s, UnaryOp::ExtendLowUVecI16x8ToVecI32x4); }
+                                    if (op == "i32x4.extend_low_i16x8_u"sv) { return makeUnary(s, UnaryOp::ExtendLowUVecI16x8ToVecI32x4); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -1772,10 +1767,10 @@ switch (op[0]) {
                               case 'h': {
                                 switch (op[24]) {
                                   case 's':
-                                    if (strcmp(op, "i32x4.extmul_high_i16x8_s") == 0) { return makeBinary(s, BinaryOp::ExtMulHighSVecI32x4); }
+                                    if (op == "i32x4.extmul_high_i16x8_s"sv) { return makeBinary(s, BinaryOp::ExtMulHighSVecI32x4); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "i32x4.extmul_high_i16x8_u") == 0) { return makeBinary(s, BinaryOp::ExtMulHighUVecI32x4); }
+                                    if (op == "i32x4.extmul_high_i16x8_u"sv) { return makeBinary(s, BinaryOp::ExtMulHighUVecI32x4); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -1783,10 +1778,10 @@ switch (op[0]) {
                               case 'l': {
                                 switch (op[23]) {
                                   case 's':
-                                    if (strcmp(op, "i32x4.extmul_low_i16x8_s") == 0) { return makeBinary(s, BinaryOp::ExtMulLowSVecI32x4); }
+                                    if (op == "i32x4.extmul_low_i16x8_s"sv) { return makeBinary(s, BinaryOp::ExtMulLowSVecI32x4); }
                                     goto parse_error;
                                   case 'u':
-                                    if (strcmp(op, "i32x4.extmul_low_i16x8_u") == 0) { return makeBinary(s, BinaryOp::ExtMulLowUVecI32x4); }
+                                    if (op == "i32x4.extmul_low_i16x8_u"sv) { return makeBinary(s, BinaryOp::ExtMulLowUVecI32x4); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -1795,7 +1790,7 @@ switch (op[0]) {
                             }
                           }
                           case 'r':
-                            if (strcmp(op, "i32x4.extract_lane") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecI32x4, 4); }
+                            if (op == "i32x4.extract_lane"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecI32x4, 4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1808,10 +1803,10 @@ switch (op[0]) {
                       case 'e': {
                         switch (op[9]) {
                           case 's':
-                            if (strcmp(op, "i32x4.ge_s") == 0) { return makeBinary(s, BinaryOp::GeSVecI32x4); }
+                            if (op == "i32x4.ge_s"sv) { return makeBinary(s, BinaryOp::GeSVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.ge_u") == 0) { return makeBinary(s, BinaryOp::GeUVecI32x4); }
+                            if (op == "i32x4.ge_u"sv) { return makeBinary(s, BinaryOp::GeUVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1819,10 +1814,10 @@ switch (op[0]) {
                       case 't': {
                         switch (op[9]) {
                           case 's':
-                            if (strcmp(op, "i32x4.gt_s") == 0) { return makeBinary(s, BinaryOp::GtSVecI32x4); }
+                            if (op == "i32x4.gt_s"sv) { return makeBinary(s, BinaryOp::GtSVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.gt_u") == 0) { return makeBinary(s, BinaryOp::GtUVecI32x4); }
+                            if (op == "i32x4.gt_u"sv) { return makeBinary(s, BinaryOp::GtUVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1833,15 +1828,15 @@ switch (op[0]) {
                   case 'l': {
                     switch (op[7]) {
                       case 'a':
-                        if (strcmp(op, "i32x4.laneselect") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI32x4); }
+                        if (op == "i32x4.laneselect"sv) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI32x4); }
                         goto parse_error;
                       case 'e': {
                         switch (op[9]) {
                           case 's':
-                            if (strcmp(op, "i32x4.le_s") == 0) { return makeBinary(s, BinaryOp::LeSVecI32x4); }
+                            if (op == "i32x4.le_s"sv) { return makeBinary(s, BinaryOp::LeSVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.le_u") == 0) { return makeBinary(s, BinaryOp::LeUVecI32x4); }
+                            if (op == "i32x4.le_u"sv) { return makeBinary(s, BinaryOp::LeUVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1849,10 +1844,10 @@ switch (op[0]) {
                       case 't': {
                         switch (op[9]) {
                           case 's':
-                            if (strcmp(op, "i32x4.lt_s") == 0) { return makeBinary(s, BinaryOp::LtSVecI32x4); }
+                            if (op == "i32x4.lt_s"sv) { return makeBinary(s, BinaryOp::LtSVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.lt_u") == 0) { return makeBinary(s, BinaryOp::LtUVecI32x4); }
+                            if (op == "i32x4.lt_u"sv) { return makeBinary(s, BinaryOp::LtUVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1865,10 +1860,10 @@ switch (op[0]) {
                       case 'a': {
                         switch (op[10]) {
                           case 's':
-                            if (strcmp(op, "i32x4.max_s") == 0) { return makeBinary(s, BinaryOp::MaxSVecI32x4); }
+                            if (op == "i32x4.max_s"sv) { return makeBinary(s, BinaryOp::MaxSVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.max_u") == 0) { return makeBinary(s, BinaryOp::MaxUVecI32x4); }
+                            if (op == "i32x4.max_u"sv) { return makeBinary(s, BinaryOp::MaxUVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1876,16 +1871,16 @@ switch (op[0]) {
                       case 'i': {
                         switch (op[10]) {
                           case 's':
-                            if (strcmp(op, "i32x4.min_s") == 0) { return makeBinary(s, BinaryOp::MinSVecI32x4); }
+                            if (op == "i32x4.min_s"sv) { return makeBinary(s, BinaryOp::MinSVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.min_u") == 0) { return makeBinary(s, BinaryOp::MinUVecI32x4); }
+                            if (op == "i32x4.min_u"sv) { return makeBinary(s, BinaryOp::MinUVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
                       }
                       case 'u':
-                        if (strcmp(op, "i32x4.mul") == 0) { return makeBinary(s, BinaryOp::MulVecI32x4); }
+                        if (op == "i32x4.mul"sv) { return makeBinary(s, BinaryOp::MulVecI32x4); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1893,10 +1888,10 @@ switch (op[0]) {
                   case 'n': {
                     switch (op[8]) {
                       case '\0':
-                        if (strcmp(op, "i32x4.ne") == 0) { return makeBinary(s, BinaryOp::NeVecI32x4); }
+                        if (op == "i32x4.ne"sv) { return makeBinary(s, BinaryOp::NeVecI32x4); }
                         goto parse_error;
                       case 'g':
-                        if (strcmp(op, "i32x4.neg") == 0) { return makeUnary(s, UnaryOp::NegVecI32x4); }
+                        if (op == "i32x4.neg"sv) { return makeUnary(s, UnaryOp::NegVecI32x4); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1908,10 +1903,10 @@ switch (op[0]) {
                           case '3': {
                             switch (op[26]) {
                               case 's':
-                                if (strcmp(op, "i32x4.relaxed_trunc_f32x4_s") == 0) { return makeUnary(s, UnaryOp::RelaxedTruncSVecF32x4ToVecI32x4); }
+                                if (op == "i32x4.relaxed_trunc_f32x4_s"sv) { return makeUnary(s, UnaryOp::RelaxedTruncSVecF32x4ToVecI32x4); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32x4.relaxed_trunc_f32x4_u") == 0) { return makeUnary(s, UnaryOp::RelaxedTruncUVecF32x4ToVecI32x4); }
+                                if (op == "i32x4.relaxed_trunc_f32x4_u"sv) { return makeUnary(s, UnaryOp::RelaxedTruncUVecF32x4ToVecI32x4); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1919,10 +1914,10 @@ switch (op[0]) {
                           case '6': {
                             switch (op[26]) {
                               case 's':
-                                if (strcmp(op, "i32x4.relaxed_trunc_f64x2_s_zero") == 0) { return makeUnary(s, UnaryOp::RelaxedTruncZeroSVecF64x2ToVecI32x4); }
+                                if (op == "i32x4.relaxed_trunc_f64x2_s_zero"sv) { return makeUnary(s, UnaryOp::RelaxedTruncZeroSVecF64x2ToVecI32x4); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32x4.relaxed_trunc_f64x2_u_zero") == 0) { return makeUnary(s, UnaryOp::RelaxedTruncZeroUVecF64x2ToVecI32x4); }
+                                if (op == "i32x4.relaxed_trunc_f64x2_u_zero"sv) { return makeUnary(s, UnaryOp::RelaxedTruncZeroUVecF64x2ToVecI32x4); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1931,7 +1926,7 @@ switch (op[0]) {
                         }
                       }
                       case 'p':
-                        if (strcmp(op, "i32x4.replace_lane") == 0) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI32x4, 4); }
+                        if (op == "i32x4.replace_lane"sv) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI32x4, 4); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1941,15 +1936,15 @@ switch (op[0]) {
                       case 'h': {
                         switch (op[8]) {
                           case 'l':
-                            if (strcmp(op, "i32x4.shl") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI32x4); }
+                            if (op == "i32x4.shl"sv) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI32x4); }
                             goto parse_error;
                           case 'r': {
                             switch (op[10]) {
                               case 's':
-                                if (strcmp(op, "i32x4.shr_s") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI32x4); }
+                                if (op == "i32x4.shr_s"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI32x4); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i32x4.shr_u") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI32x4); }
+                                if (op == "i32x4.shr_u"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI32x4); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -1958,10 +1953,10 @@ switch (op[0]) {
                         }
                       }
                       case 'p':
-                        if (strcmp(op, "i32x4.splat") == 0) { return makeUnary(s, UnaryOp::SplatVecI32x4); }
+                        if (op == "i32x4.splat"sv) { return makeUnary(s, UnaryOp::SplatVecI32x4); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i32x4.sub") == 0) { return makeBinary(s, BinaryOp::SubVecI32x4); }
+                        if (op == "i32x4.sub"sv) { return makeBinary(s, BinaryOp::SubVecI32x4); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -1971,10 +1966,10 @@ switch (op[0]) {
                       case '3': {
                         switch (op[22]) {
                           case 's':
-                            if (strcmp(op, "i32x4.trunc_sat_f32x4_s") == 0) { return makeUnary(s, UnaryOp::TruncSatSVecF32x4ToVecI32x4); }
+                            if (op == "i32x4.trunc_sat_f32x4_s"sv) { return makeUnary(s, UnaryOp::TruncSatSVecF32x4ToVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.trunc_sat_f32x4_u") == 0) { return makeUnary(s, UnaryOp::TruncSatUVecF32x4ToVecI32x4); }
+                            if (op == "i32x4.trunc_sat_f32x4_u"sv) { return makeUnary(s, UnaryOp::TruncSatUVecF32x4ToVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -1982,10 +1977,10 @@ switch (op[0]) {
                       case '6': {
                         switch (op[22]) {
                           case 's':
-                            if (strcmp(op, "i32x4.trunc_sat_f64x2_s_zero") == 0) { return makeUnary(s, UnaryOp::TruncSatZeroSVecF64x2ToVecI32x4); }
+                            if (op == "i32x4.trunc_sat_f64x2_s_zero"sv) { return makeUnary(s, UnaryOp::TruncSatZeroSVecF64x2ToVecI32x4); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i32x4.trunc_sat_f64x2_u_zero") == 0) { return makeUnary(s, UnaryOp::TruncSatZeroUVecF64x2ToVecI32x4); }
+                            if (op == "i32x4.trunc_sat_f64x2_u_zero"sv) { return makeUnary(s, UnaryOp::TruncSatZeroUVecF64x2ToVecI32x4); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2009,26 +2004,26 @@ switch (op[0]) {
               case 'a': {
                 switch (op[5]) {
                   case 'd':
-                    if (strcmp(op, "i64.add") == 0) { return makeBinary(s, BinaryOp::AddInt64); }
+                    if (op == "i64.add"sv) { return makeBinary(s, BinaryOp::AddInt64); }
                     goto parse_error;
                   case 'n':
-                    if (strcmp(op, "i64.and") == 0) { return makeBinary(s, BinaryOp::AndInt64); }
+                    if (op == "i64.and"sv) { return makeBinary(s, BinaryOp::AndInt64); }
                     goto parse_error;
                   case 't': {
                     switch (op[11]) {
                       case 'l': {
                         switch (op[15]) {
                           case '\0':
-                            if (strcmp(op, "i64.atomic.load") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.load"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 8, /*isAtomic=*/true); }
                             goto parse_error;
                           case '1':
-                            if (strcmp(op, "i64.atomic.load16_u") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.load16_u"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 2, /*isAtomic=*/true); }
                             goto parse_error;
                           case '3':
-                            if (strcmp(op, "i64.atomic.load32_u") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.load32_u"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 4, /*isAtomic=*/true); }
                             goto parse_error;
                           case '8':
-                            if (strcmp(op, "i64.atomic.load8_u") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.load8_u"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 1, /*isAtomic=*/true); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2040,30 +2035,30 @@ switch (op[0]) {
                               case 'a': {
                                 switch (op[16]) {
                                   case 'd':
-                                    if (strcmp(op, "i64.atomic.rmw.add") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw.add"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 8); }
                                     goto parse_error;
                                   case 'n':
-                                    if (strcmp(op, "i64.atomic.rmw.and") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw.and"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 8); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
                               }
                               case 'c':
-                                if (strcmp(op, "i64.atomic.rmw.cmpxchg") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw.cmpxchg"sv) { return makeAtomicCmpxchg(s, Type::i64, 8); }
                                 goto parse_error;
                               case 'o':
-                                if (strcmp(op, "i64.atomic.rmw.or") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw.or"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 8); }
                                 goto parse_error;
                               case 's':
-                                if (strcmp(op, "i64.atomic.rmw.sub") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw.sub"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 8); }
                                 goto parse_error;
                               case 'x': {
                                 switch (op[16]) {
                                   case 'c':
-                                    if (strcmp(op, "i64.atomic.rmw.xchg") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw.xchg"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 8); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i64.atomic.rmw.xor") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw.xor"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 8); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -2076,30 +2071,30 @@ switch (op[0]) {
                               case 'a': {
                                 switch (op[18]) {
                                   case 'd':
-                                    if (strcmp(op, "i64.atomic.rmw16.add_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw16.add_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 2); }
                                     goto parse_error;
                                   case 'n':
-                                    if (strcmp(op, "i64.atomic.rmw16.and_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw16.and_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 2); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
                               }
                               case 'c':
-                                if (strcmp(op, "i64.atomic.rmw16.cmpxchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw16.cmpxchg_u"sv) { return makeAtomicCmpxchg(s, Type::i64, 2); }
                                 goto parse_error;
                               case 'o':
-                                if (strcmp(op, "i64.atomic.rmw16.or_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw16.or_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 2); }
                                 goto parse_error;
                               case 's':
-                                if (strcmp(op, "i64.atomic.rmw16.sub_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw16.sub_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 2); }
                                 goto parse_error;
                               case 'x': {
                                 switch (op[18]) {
                                   case 'c':
-                                    if (strcmp(op, "i64.atomic.rmw16.xchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw16.xchg_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 2); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i64.atomic.rmw16.xor_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw16.xor_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 2); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -2112,30 +2107,30 @@ switch (op[0]) {
                               case 'a': {
                                 switch (op[18]) {
                                   case 'd':
-                                    if (strcmp(op, "i64.atomic.rmw32.add_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw32.add_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 4); }
                                     goto parse_error;
                                   case 'n':
-                                    if (strcmp(op, "i64.atomic.rmw32.and_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw32.and_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 4); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
                               }
                               case 'c':
-                                if (strcmp(op, "i64.atomic.rmw32.cmpxchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw32.cmpxchg_u"sv) { return makeAtomicCmpxchg(s, Type::i64, 4); }
                                 goto parse_error;
                               case 'o':
-                                if (strcmp(op, "i64.atomic.rmw32.or_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw32.or_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 4); }
                                 goto parse_error;
                               case 's':
-                                if (strcmp(op, "i64.atomic.rmw32.sub_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw32.sub_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 4); }
                                 goto parse_error;
                               case 'x': {
                                 switch (op[18]) {
                                   case 'c':
-                                    if (strcmp(op, "i64.atomic.rmw32.xchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw32.xchg_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 4); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i64.atomic.rmw32.xor_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw32.xor_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 4); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -2148,30 +2143,30 @@ switch (op[0]) {
                               case 'a': {
                                 switch (op[17]) {
                                   case 'd':
-                                    if (strcmp(op, "i64.atomic.rmw8.add_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw8.add_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAdd, Type::i64, 1); }
                                     goto parse_error;
                                   case 'n':
-                                    if (strcmp(op, "i64.atomic.rmw8.and_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw8.and_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWAnd, Type::i64, 1); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
                               }
                               case 'c':
-                                if (strcmp(op, "i64.atomic.rmw8.cmpxchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw8.cmpxchg_u"sv) { return makeAtomicCmpxchg(s, Type::i64, 1); }
                                 goto parse_error;
                               case 'o':
-                                if (strcmp(op, "i64.atomic.rmw8.or_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw8.or_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWOr, Type::i64, 1); }
                                 goto parse_error;
                               case 's':
-                                if (strcmp(op, "i64.atomic.rmw8.sub_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                if (op == "i64.atomic.rmw8.sub_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWSub, Type::i64, 1); }
                                 goto parse_error;
                               case 'x': {
                                 switch (op[17]) {
                                   case 'c':
-                                    if (strcmp(op, "i64.atomic.rmw8.xchg_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw8.xchg_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXchg, Type::i64, 1); }
                                     goto parse_error;
                                   case 'o':
-                                    if (strcmp(op, "i64.atomic.rmw8.xor_u") == 0) { return makeAtomicRMWOrCmpxchg(s, Type::i64); }
+                                    if (op == "i64.atomic.rmw8.xor_u"sv) { return makeAtomicRMW(s, AtomicRMWOp::RMWXor, Type::i64, 1); }
                                     goto parse_error;
                                   default: goto parse_error;
                                 }
@@ -2185,16 +2180,16 @@ switch (op[0]) {
                       case 's': {
                         switch (op[16]) {
                           case '\0':
-                            if (strcmp(op, "i64.atomic.store") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.store"sv) { return makeStore(s, Type::i64, 8, /*isAtomic=*/true); }
                             goto parse_error;
                           case '1':
-                            if (strcmp(op, "i64.atomic.store16") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.store16"sv) { return makeStore(s, Type::i64, 2, /*isAtomic=*/true); }
                             goto parse_error;
                           case '3':
-                            if (strcmp(op, "i64.atomic.store32") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.store32"sv) { return makeStore(s, Type::i64, 4, /*isAtomic=*/true); }
                             goto parse_error;
                           case '8':
-                            if (strcmp(op, "i64.atomic.store8") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/true); }
+                            if (op == "i64.atomic.store8"sv) { return makeStore(s, Type::i64, 1, /*isAtomic=*/true); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2208,13 +2203,13 @@ switch (op[0]) {
               case 'c': {
                 switch (op[5]) {
                   case 'l':
-                    if (strcmp(op, "i64.clz") == 0) { return makeUnary(s, UnaryOp::ClzInt64); }
+                    if (op == "i64.clz"sv) { return makeUnary(s, UnaryOp::ClzInt64); }
                     goto parse_error;
                   case 'o':
-                    if (strcmp(op, "i64.const") == 0) { return makeConst(s, Type::i64); }
+                    if (op == "i64.const"sv) { return makeConst(s, Type::i64); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "i64.ctz") == 0) { return makeUnary(s, UnaryOp::CtzInt64); }
+                    if (op == "i64.ctz"sv) { return makeUnary(s, UnaryOp::CtzInt64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2222,10 +2217,10 @@ switch (op[0]) {
               case 'd': {
                 switch (op[8]) {
                   case 's':
-                    if (strcmp(op, "i64.div_s") == 0) { return makeBinary(s, BinaryOp::DivSInt64); }
+                    if (op == "i64.div_s"sv) { return makeBinary(s, BinaryOp::DivSInt64); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i64.div_u") == 0) { return makeBinary(s, BinaryOp::DivUInt64); }
+                    if (op == "i64.div_u"sv) { return makeBinary(s, BinaryOp::DivUInt64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2235,10 +2230,10 @@ switch (op[0]) {
                   case 'q': {
                     switch (op[6]) {
                       case '\0':
-                        if (strcmp(op, "i64.eq") == 0) { return makeBinary(s, BinaryOp::EqInt64); }
+                        if (op == "i64.eq"sv) { return makeBinary(s, BinaryOp::EqInt64); }
                         goto parse_error;
                       case 'z':
-                        if (strcmp(op, "i64.eqz") == 0) { return makeUnary(s, UnaryOp::EqZInt64); }
+                        if (op == "i64.eqz"sv) { return makeUnary(s, UnaryOp::EqZInt64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2246,21 +2241,21 @@ switch (op[0]) {
                   case 'x': {
                     switch (op[10]) {
                       case '1':
-                        if (strcmp(op, "i64.extend16_s") == 0) { return makeUnary(s, UnaryOp::ExtendS16Int64); }
+                        if (op == "i64.extend16_s"sv) { return makeUnary(s, UnaryOp::ExtendS16Int64); }
                         goto parse_error;
                       case '3':
-                        if (strcmp(op, "i64.extend32_s") == 0) { return makeUnary(s, UnaryOp::ExtendS32Int64); }
+                        if (op == "i64.extend32_s"sv) { return makeUnary(s, UnaryOp::ExtendS32Int64); }
                         goto parse_error;
                       case '8':
-                        if (strcmp(op, "i64.extend8_s") == 0) { return makeUnary(s, UnaryOp::ExtendS8Int64); }
+                        if (op == "i64.extend8_s"sv) { return makeUnary(s, UnaryOp::ExtendS8Int64); }
                         goto parse_error;
                       case '_': {
                         switch (op[15]) {
                           case 's':
-                            if (strcmp(op, "i64.extend_i32_s") == 0) { return makeUnary(s, UnaryOp::ExtendSInt32); }
+                            if (op == "i64.extend_i32_s"sv) { return makeUnary(s, UnaryOp::ExtendSInt32); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.extend_i32_u") == 0) { return makeUnary(s, UnaryOp::ExtendUInt32); }
+                            if (op == "i64.extend_i32_u"sv) { return makeUnary(s, UnaryOp::ExtendUInt32); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2276,10 +2271,10 @@ switch (op[0]) {
                   case 'e': {
                     switch (op[7]) {
                       case 's':
-                        if (strcmp(op, "i64.ge_s") == 0) { return makeBinary(s, BinaryOp::GeSInt64); }
+                        if (op == "i64.ge_s"sv) { return makeBinary(s, BinaryOp::GeSInt64); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i64.ge_u") == 0) { return makeBinary(s, BinaryOp::GeUInt64); }
+                        if (op == "i64.ge_u"sv) { return makeBinary(s, BinaryOp::GeUInt64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2287,10 +2282,10 @@ switch (op[0]) {
                   case 't': {
                     switch (op[7]) {
                       case 's':
-                        if (strcmp(op, "i64.gt_s") == 0) { return makeBinary(s, BinaryOp::GtSInt64); }
+                        if (op == "i64.gt_s"sv) { return makeBinary(s, BinaryOp::GtSInt64); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i64.gt_u") == 0) { return makeBinary(s, BinaryOp::GtUInt64); }
+                        if (op == "i64.gt_u"sv) { return makeBinary(s, BinaryOp::GtUInt64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2303,10 +2298,10 @@ switch (op[0]) {
                   case 'e': {
                     switch (op[7]) {
                       case 's':
-                        if (strcmp(op, "i64.le_s") == 0) { return makeBinary(s, BinaryOp::LeSInt64); }
+                        if (op == "i64.le_s"sv) { return makeBinary(s, BinaryOp::LeSInt64); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i64.le_u") == 0) { return makeBinary(s, BinaryOp::LeUInt64); }
+                        if (op == "i64.le_u"sv) { return makeBinary(s, BinaryOp::LeUInt64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2314,15 +2309,15 @@ switch (op[0]) {
                   case 'o': {
                     switch (op[8]) {
                       case '\0':
-                        if (strcmp(op, "i64.load") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                        if (op == "i64.load"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 8, /*isAtomic=*/false); }
                         goto parse_error;
                       case '1': {
                         switch (op[11]) {
                           case 's':
-                            if (strcmp(op, "i64.load16_s") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                            if (op == "i64.load16_s"sv) { return makeLoad(s, Type::i64, /*signed=*/true, 2, /*isAtomic=*/false); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.load16_u") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                            if (op == "i64.load16_u"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 2, /*isAtomic=*/false); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2330,10 +2325,10 @@ switch (op[0]) {
                       case '3': {
                         switch (op[11]) {
                           case 's':
-                            if (strcmp(op, "i64.load32_s") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                            if (op == "i64.load32_s"sv) { return makeLoad(s, Type::i64, /*signed=*/true, 4, /*isAtomic=*/false); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.load32_u") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                            if (op == "i64.load32_u"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 4, /*isAtomic=*/false); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2341,10 +2336,10 @@ switch (op[0]) {
                       case '8': {
                         switch (op[10]) {
                           case 's':
-                            if (strcmp(op, "i64.load8_s") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                            if (op == "i64.load8_s"sv) { return makeLoad(s, Type::i64, /*signed=*/true, 1, /*isAtomic=*/false); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.load8_u") == 0) { return makeLoad(s, Type::i64, /*isAtomic=*/false); }
+                            if (op == "i64.load8_u"sv) { return makeLoad(s, Type::i64, /*signed=*/false, 1, /*isAtomic=*/false); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2355,10 +2350,10 @@ switch (op[0]) {
                   case 't': {
                     switch (op[7]) {
                       case 's':
-                        if (strcmp(op, "i64.lt_s") == 0) { return makeBinary(s, BinaryOp::LtSInt64); }
+                        if (op == "i64.lt_s"sv) { return makeBinary(s, BinaryOp::LtSInt64); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i64.lt_u") == 0) { return makeBinary(s, BinaryOp::LtUInt64); }
+                        if (op == "i64.lt_u"sv) { return makeBinary(s, BinaryOp::LtUInt64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2367,31 +2362,31 @@ switch (op[0]) {
                 }
               }
               case 'm':
-                if (strcmp(op, "i64.mul") == 0) { return makeBinary(s, BinaryOp::MulInt64); }
+                if (op == "i64.mul"sv) { return makeBinary(s, BinaryOp::MulInt64); }
                 goto parse_error;
               case 'n':
-                if (strcmp(op, "i64.ne") == 0) { return makeBinary(s, BinaryOp::NeInt64); }
+                if (op == "i64.ne"sv) { return makeBinary(s, BinaryOp::NeInt64); }
                 goto parse_error;
               case 'o':
-                if (strcmp(op, "i64.or") == 0) { return makeBinary(s, BinaryOp::OrInt64); }
+                if (op == "i64.or"sv) { return makeBinary(s, BinaryOp::OrInt64); }
                 goto parse_error;
               case 'p':
-                if (strcmp(op, "i64.popcnt") == 0) { return makeUnary(s, UnaryOp::PopcntInt64); }
+                if (op == "i64.popcnt"sv) { return makeUnary(s, UnaryOp::PopcntInt64); }
                 goto parse_error;
               case 'r': {
                 switch (op[5]) {
                   case 'e': {
                     switch (op[6]) {
                       case 'i':
-                        if (strcmp(op, "i64.reinterpret_f64") == 0) { return makeUnary(s, UnaryOp::ReinterpretFloat64); }
+                        if (op == "i64.reinterpret_f64"sv) { return makeUnary(s, UnaryOp::ReinterpretFloat64); }
                         goto parse_error;
                       case 'm': {
                         switch (op[8]) {
                           case 's':
-                            if (strcmp(op, "i64.rem_s") == 0) { return makeBinary(s, BinaryOp::RemSInt64); }
+                            if (op == "i64.rem_s"sv) { return makeBinary(s, BinaryOp::RemSInt64); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.rem_u") == 0) { return makeBinary(s, BinaryOp::RemUInt64); }
+                            if (op == "i64.rem_u"sv) { return makeBinary(s, BinaryOp::RemUInt64); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2402,10 +2397,10 @@ switch (op[0]) {
                   case 'o': {
                     switch (op[7]) {
                       case 'l':
-                        if (strcmp(op, "i64.rotl") == 0) { return makeBinary(s, BinaryOp::RotLInt64); }
+                        if (op == "i64.rotl"sv) { return makeBinary(s, BinaryOp::RotLInt64); }
                         goto parse_error;
                       case 'r':
-                        if (strcmp(op, "i64.rotr") == 0) { return makeBinary(s, BinaryOp::RotRInt64); }
+                        if (op == "i64.rotr"sv) { return makeBinary(s, BinaryOp::RotRInt64); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2418,15 +2413,15 @@ switch (op[0]) {
                   case 'h': {
                     switch (op[6]) {
                       case 'l':
-                        if (strcmp(op, "i64.shl") == 0) { return makeBinary(s, BinaryOp::ShlInt64); }
+                        if (op == "i64.shl"sv) { return makeBinary(s, BinaryOp::ShlInt64); }
                         goto parse_error;
                       case 'r': {
                         switch (op[8]) {
                           case 's':
-                            if (strcmp(op, "i64.shr_s") == 0) { return makeBinary(s, BinaryOp::ShrSInt64); }
+                            if (op == "i64.shr_s"sv) { return makeBinary(s, BinaryOp::ShrSInt64); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.shr_u") == 0) { return makeBinary(s, BinaryOp::ShrUInt64); }
+                            if (op == "i64.shr_u"sv) { return makeBinary(s, BinaryOp::ShrUInt64); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2437,22 +2432,22 @@ switch (op[0]) {
                   case 't': {
                     switch (op[9]) {
                       case '\0':
-                        if (strcmp(op, "i64.store") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/false); }
+                        if (op == "i64.store"sv) { return makeStore(s, Type::i64, 8, /*isAtomic=*/false); }
                         goto parse_error;
                       case '1':
-                        if (strcmp(op, "i64.store16") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/false); }
+                        if (op == "i64.store16"sv) { return makeStore(s, Type::i64, 2, /*isAtomic=*/false); }
                         goto parse_error;
                       case '3':
-                        if (strcmp(op, "i64.store32") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/false); }
+                        if (op == "i64.store32"sv) { return makeStore(s, Type::i64, 4, /*isAtomic=*/false); }
                         goto parse_error;
                       case '8':
-                        if (strcmp(op, "i64.store8") == 0) { return makeStore(s, Type::i64, /*isAtomic=*/false); }
+                        if (op == "i64.store8"sv) { return makeStore(s, Type::i64, 1, /*isAtomic=*/false); }
                         goto parse_error;
                       default: goto parse_error;
                     }
                   }
                   case 'u':
-                    if (strcmp(op, "i64.sub") == 0) { return makeBinary(s, BinaryOp::SubInt64); }
+                    if (op == "i64.sub"sv) { return makeBinary(s, BinaryOp::SubInt64); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2464,10 +2459,10 @@ switch (op[0]) {
                       case '3': {
                         switch (op[14]) {
                           case 's':
-                            if (strcmp(op, "i64.trunc_f32_s") == 0) { return makeUnary(s, UnaryOp::TruncSFloat32ToInt64); }
+                            if (op == "i64.trunc_f32_s"sv) { return makeUnary(s, UnaryOp::TruncSFloat32ToInt64); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.trunc_f32_u") == 0) { return makeUnary(s, UnaryOp::TruncUFloat32ToInt64); }
+                            if (op == "i64.trunc_f32_u"sv) { return makeUnary(s, UnaryOp::TruncUFloat32ToInt64); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2475,10 +2470,10 @@ switch (op[0]) {
                       case '6': {
                         switch (op[14]) {
                           case 's':
-                            if (strcmp(op, "i64.trunc_f64_s") == 0) { return makeUnary(s, UnaryOp::TruncSFloat64ToInt64); }
+                            if (op == "i64.trunc_f64_s"sv) { return makeUnary(s, UnaryOp::TruncSFloat64ToInt64); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.trunc_f64_u") == 0) { return makeUnary(s, UnaryOp::TruncUFloat64ToInt64); }
+                            if (op == "i64.trunc_f64_u"sv) { return makeUnary(s, UnaryOp::TruncUFloat64ToInt64); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2491,10 +2486,10 @@ switch (op[0]) {
                       case '3': {
                         switch (op[18]) {
                           case 's':
-                            if (strcmp(op, "i64.trunc_sat_f32_s") == 0) { return makeUnary(s, UnaryOp::TruncSatSFloat32ToInt64); }
+                            if (op == "i64.trunc_sat_f32_s"sv) { return makeUnary(s, UnaryOp::TruncSatSFloat32ToInt64); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.trunc_sat_f32_u") == 0) { return makeUnary(s, UnaryOp::TruncSatUFloat32ToInt64); }
+                            if (op == "i64.trunc_sat_f32_u"sv) { return makeUnary(s, UnaryOp::TruncSatUFloat32ToInt64); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2502,10 +2497,10 @@ switch (op[0]) {
                       case '6': {
                         switch (op[18]) {
                           case 's':
-                            if (strcmp(op, "i64.trunc_sat_f64_s") == 0) { return makeUnary(s, UnaryOp::TruncSatSFloat64ToInt64); }
+                            if (op == "i64.trunc_sat_f64_s"sv) { return makeUnary(s, UnaryOp::TruncSatSFloat64ToInt64); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64.trunc_sat_f64_u") == 0) { return makeUnary(s, UnaryOp::TruncSatUFloat64ToInt64); }
+                            if (op == "i64.trunc_sat_f64_u"sv) { return makeUnary(s, UnaryOp::TruncSatUFloat64ToInt64); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2517,7 +2512,7 @@ switch (op[0]) {
                 }
               }
               case 'x':
-                if (strcmp(op, "i64.xor") == 0) { return makeBinary(s, BinaryOp::XorInt64); }
+                if (op == "i64.xor"sv) { return makeBinary(s, BinaryOp::XorInt64); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -2527,24 +2522,24 @@ switch (op[0]) {
               case 'a': {
                 switch (op[7]) {
                   case 'b':
-                    if (strcmp(op, "i64x2.abs") == 0) { return makeUnary(s, UnaryOp::AbsVecI64x2); }
+                    if (op == "i64x2.abs"sv) { return makeUnary(s, UnaryOp::AbsVecI64x2); }
                     goto parse_error;
                   case 'd':
-                    if (strcmp(op, "i64x2.add") == 0) { return makeBinary(s, BinaryOp::AddVecI64x2); }
+                    if (op == "i64x2.add"sv) { return makeBinary(s, BinaryOp::AddVecI64x2); }
                     goto parse_error;
                   case 'l':
-                    if (strcmp(op, "i64x2.all_true") == 0) { return makeUnary(s, UnaryOp::AllTrueVecI64x2); }
+                    if (op == "i64x2.all_true"sv) { return makeUnary(s, UnaryOp::AllTrueVecI64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'b':
-                if (strcmp(op, "i64x2.bitmask") == 0) { return makeUnary(s, UnaryOp::BitmaskVecI64x2); }
+                if (op == "i64x2.bitmask"sv) { return makeUnary(s, UnaryOp::BitmaskVecI64x2); }
                 goto parse_error;
               case 'e': {
                 switch (op[7]) {
                   case 'q':
-                    if (strcmp(op, "i64x2.eq") == 0) { return makeBinary(s, BinaryOp::EqVecI64x2); }
+                    if (op == "i64x2.eq"sv) { return makeBinary(s, BinaryOp::EqVecI64x2); }
                     goto parse_error;
                   case 'x': {
                     switch (op[9]) {
@@ -2553,10 +2548,10 @@ switch (op[0]) {
                           case 'h': {
                             switch (op[24]) {
                               case 's':
-                                if (strcmp(op, "i64x2.extend_high_i32x4_s") == 0) { return makeUnary(s, UnaryOp::ExtendHighSVecI32x4ToVecI64x2); }
+                                if (op == "i64x2.extend_high_i32x4_s"sv) { return makeUnary(s, UnaryOp::ExtendHighSVecI32x4ToVecI64x2); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i64x2.extend_high_i32x4_u") == 0) { return makeUnary(s, UnaryOp::ExtendHighUVecI32x4ToVecI64x2); }
+                                if (op == "i64x2.extend_high_i32x4_u"sv) { return makeUnary(s, UnaryOp::ExtendHighUVecI32x4ToVecI64x2); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -2564,10 +2559,10 @@ switch (op[0]) {
                           case 'l': {
                             switch (op[23]) {
                               case 's':
-                                if (strcmp(op, "i64x2.extend_low_i32x4_s") == 0) { return makeUnary(s, UnaryOp::ExtendLowSVecI32x4ToVecI64x2); }
+                                if (op == "i64x2.extend_low_i32x4_s"sv) { return makeUnary(s, UnaryOp::ExtendLowSVecI32x4ToVecI64x2); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i64x2.extend_low_i32x4_u") == 0) { return makeUnary(s, UnaryOp::ExtendLowUVecI32x4ToVecI64x2); }
+                                if (op == "i64x2.extend_low_i32x4_u"sv) { return makeUnary(s, UnaryOp::ExtendLowUVecI32x4ToVecI64x2); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -2580,10 +2575,10 @@ switch (op[0]) {
                           case 'h': {
                             switch (op[24]) {
                               case 's':
-                                if (strcmp(op, "i64x2.extmul_high_i32x4_s") == 0) { return makeBinary(s, BinaryOp::ExtMulHighSVecI64x2); }
+                                if (op == "i64x2.extmul_high_i32x4_s"sv) { return makeBinary(s, BinaryOp::ExtMulHighSVecI64x2); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i64x2.extmul_high_i32x4_u") == 0) { return makeBinary(s, BinaryOp::ExtMulHighUVecI64x2); }
+                                if (op == "i64x2.extmul_high_i32x4_u"sv) { return makeBinary(s, BinaryOp::ExtMulHighUVecI64x2); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -2591,10 +2586,10 @@ switch (op[0]) {
                           case 'l': {
                             switch (op[23]) {
                               case 's':
-                                if (strcmp(op, "i64x2.extmul_low_i32x4_s") == 0) { return makeBinary(s, BinaryOp::ExtMulLowSVecI64x2); }
+                                if (op == "i64x2.extmul_low_i32x4_s"sv) { return makeBinary(s, BinaryOp::ExtMulLowSVecI64x2); }
                                 goto parse_error;
                               case 'u':
-                                if (strcmp(op, "i64x2.extmul_low_i32x4_u") == 0) { return makeBinary(s, BinaryOp::ExtMulLowUVecI64x2); }
+                                if (op == "i64x2.extmul_low_i32x4_u"sv) { return makeBinary(s, BinaryOp::ExtMulLowUVecI64x2); }
                                 goto parse_error;
                               default: goto parse_error;
                             }
@@ -2603,7 +2598,7 @@ switch (op[0]) {
                         }
                       }
                       case 'r':
-                        if (strcmp(op, "i64x2.extract_lane") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecI64x2, 2); }
+                        if (op == "i64x2.extract_lane"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneVecI64x2, 2); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2614,10 +2609,10 @@ switch (op[0]) {
               case 'g': {
                 switch (op[7]) {
                   case 'e':
-                    if (strcmp(op, "i64x2.ge_s") == 0) { return makeBinary(s, BinaryOp::GeSVecI64x2); }
+                    if (op == "i64x2.ge_s"sv) { return makeBinary(s, BinaryOp::GeSVecI64x2); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "i64x2.gt_s") == 0) { return makeBinary(s, BinaryOp::GtSVecI64x2); }
+                    if (op == "i64x2.gt_s"sv) { return makeBinary(s, BinaryOp::GtSVecI64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2625,48 +2620,48 @@ switch (op[0]) {
               case 'l': {
                 switch (op[7]) {
                   case 'a':
-                    if (strcmp(op, "i64x2.laneselect") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI64x2); }
+                    if (op == "i64x2.laneselect"sv) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI64x2); }
                     goto parse_error;
                   case 'e':
-                    if (strcmp(op, "i64x2.le_s") == 0) { return makeBinary(s, BinaryOp::LeSVecI64x2); }
+                    if (op == "i64x2.le_s"sv) { return makeBinary(s, BinaryOp::LeSVecI64x2); }
                     goto parse_error;
                   case 't':
-                    if (strcmp(op, "i64x2.lt_s") == 0) { return makeBinary(s, BinaryOp::LtSVecI64x2); }
+                    if (op == "i64x2.lt_s"sv) { return makeBinary(s, BinaryOp::LtSVecI64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'm':
-                if (strcmp(op, "i64x2.mul") == 0) { return makeBinary(s, BinaryOp::MulVecI64x2); }
+                if (op == "i64x2.mul"sv) { return makeBinary(s, BinaryOp::MulVecI64x2); }
                 goto parse_error;
               case 'n': {
                 switch (op[8]) {
                   case '\0':
-                    if (strcmp(op, "i64x2.ne") == 0) { return makeBinary(s, BinaryOp::NeVecI64x2); }
+                    if (op == "i64x2.ne"sv) { return makeBinary(s, BinaryOp::NeVecI64x2); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "i64x2.neg") == 0) { return makeUnary(s, UnaryOp::NegVecI64x2); }
+                    if (op == "i64x2.neg"sv) { return makeUnary(s, UnaryOp::NegVecI64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'r':
-                if (strcmp(op, "i64x2.replace_lane") == 0) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI64x2, 2); }
+                if (op == "i64x2.replace_lane"sv) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI64x2, 2); }
                 goto parse_error;
               case 's': {
                 switch (op[7]) {
                   case 'h': {
                     switch (op[8]) {
                       case 'l':
-                        if (strcmp(op, "i64x2.shl") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI64x2); }
+                        if (op == "i64x2.shl"sv) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI64x2); }
                         goto parse_error;
                       case 'r': {
                         switch (op[10]) {
                           case 's':
-                            if (strcmp(op, "i64x2.shr_s") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI64x2); }
+                            if (op == "i64x2.shr_s"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI64x2); }
                             goto parse_error;
                           case 'u':
-                            if (strcmp(op, "i64x2.shr_u") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI64x2); }
+                            if (op == "i64x2.shr_u"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI64x2); }
                             goto parse_error;
                           default: goto parse_error;
                         }
@@ -2675,10 +2670,10 @@ switch (op[0]) {
                     }
                   }
                   case 'p':
-                    if (strcmp(op, "i64x2.splat") == 0) { return makeUnary(s, UnaryOp::SplatVecI64x2); }
+                    if (op == "i64x2.splat"sv) { return makeUnary(s, UnaryOp::SplatVecI64x2); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i64x2.sub") == 0) { return makeBinary(s, BinaryOp::SubVecI64x2); }
+                    if (op == "i64x2.sub"sv) { return makeBinary(s, BinaryOp::SubVecI64x2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2694,20 +2689,20 @@ switch (op[0]) {
           case 'a': {
             switch (op[7]) {
               case 'b':
-                if (strcmp(op, "i8x16.abs") == 0) { return makeUnary(s, UnaryOp::AbsVecI8x16); }
+                if (op == "i8x16.abs"sv) { return makeUnary(s, UnaryOp::AbsVecI8x16); }
                 goto parse_error;
               case 'd': {
                 switch (op[9]) {
                   case '\0':
-                    if (strcmp(op, "i8x16.add") == 0) { return makeBinary(s, BinaryOp::AddVecI8x16); }
+                    if (op == "i8x16.add"sv) { return makeBinary(s, BinaryOp::AddVecI8x16); }
                     goto parse_error;
                   case '_': {
                     switch (op[14]) {
                       case 's':
-                        if (strcmp(op, "i8x16.add_sat_s") == 0) { return makeBinary(s, BinaryOp::AddSatSVecI8x16); }
+                        if (op == "i8x16.add_sat_s"sv) { return makeBinary(s, BinaryOp::AddSatSVecI8x16); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i8x16.add_sat_u") == 0) { return makeBinary(s, BinaryOp::AddSatUVecI8x16); }
+                        if (op == "i8x16.add_sat_u"sv) { return makeBinary(s, BinaryOp::AddSatUVecI8x16); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2716,29 +2711,29 @@ switch (op[0]) {
                 }
               }
               case 'l':
-                if (strcmp(op, "i8x16.all_true") == 0) { return makeUnary(s, UnaryOp::AllTrueVecI8x16); }
+                if (op == "i8x16.all_true"sv) { return makeUnary(s, UnaryOp::AllTrueVecI8x16); }
                 goto parse_error;
               case 'v':
-                if (strcmp(op, "i8x16.avgr_u") == 0) { return makeBinary(s, BinaryOp::AvgrUVecI8x16); }
+                if (op == "i8x16.avgr_u"sv) { return makeBinary(s, BinaryOp::AvgrUVecI8x16); }
                 goto parse_error;
               default: goto parse_error;
             }
           }
           case 'b':
-            if (strcmp(op, "i8x16.bitmask") == 0) { return makeUnary(s, UnaryOp::BitmaskVecI8x16); }
+            if (op == "i8x16.bitmask"sv) { return makeUnary(s, UnaryOp::BitmaskVecI8x16); }
             goto parse_error;
           case 'e': {
             switch (op[7]) {
               case 'q':
-                if (strcmp(op, "i8x16.eq") == 0) { return makeBinary(s, BinaryOp::EqVecI8x16); }
+                if (op == "i8x16.eq"sv) { return makeBinary(s, BinaryOp::EqVecI8x16); }
                 goto parse_error;
               case 'x': {
                 switch (op[19]) {
                   case 's':
-                    if (strcmp(op, "i8x16.extract_lane_s") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneSVecI8x16, 16); }
+                    if (op == "i8x16.extract_lane_s"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneSVecI8x16, 16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.extract_lane_u") == 0) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneUVecI8x16, 16); }
+                    if (op == "i8x16.extract_lane_u"sv) { return makeSIMDExtract(s, SIMDExtractOp::ExtractLaneUVecI8x16, 16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2751,10 +2746,10 @@ switch (op[0]) {
               case 'e': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i8x16.ge_s") == 0) { return makeBinary(s, BinaryOp::GeSVecI8x16); }
+                    if (op == "i8x16.ge_s"sv) { return makeBinary(s, BinaryOp::GeSVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.ge_u") == 0) { return makeBinary(s, BinaryOp::GeUVecI8x16); }
+                    if (op == "i8x16.ge_u"sv) { return makeBinary(s, BinaryOp::GeUVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2762,10 +2757,10 @@ switch (op[0]) {
               case 't': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i8x16.gt_s") == 0) { return makeBinary(s, BinaryOp::GtSVecI8x16); }
+                    if (op == "i8x16.gt_s"sv) { return makeBinary(s, BinaryOp::GtSVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.gt_u") == 0) { return makeBinary(s, BinaryOp::GtUVecI8x16); }
+                    if (op == "i8x16.gt_u"sv) { return makeBinary(s, BinaryOp::GtUVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2776,15 +2771,15 @@ switch (op[0]) {
           case 'l': {
             switch (op[7]) {
               case 'a':
-                if (strcmp(op, "i8x16.laneselect") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI8x16); }
+                if (op == "i8x16.laneselect"sv) { return makeSIMDTernary(s, SIMDTernaryOp::LaneselectI8x16); }
                 goto parse_error;
               case 'e': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i8x16.le_s") == 0) { return makeBinary(s, BinaryOp::LeSVecI8x16); }
+                    if (op == "i8x16.le_s"sv) { return makeBinary(s, BinaryOp::LeSVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.le_u") == 0) { return makeBinary(s, BinaryOp::LeUVecI8x16); }
+                    if (op == "i8x16.le_u"sv) { return makeBinary(s, BinaryOp::LeUVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2792,10 +2787,10 @@ switch (op[0]) {
               case 't': {
                 switch (op[9]) {
                   case 's':
-                    if (strcmp(op, "i8x16.lt_s") == 0) { return makeBinary(s, BinaryOp::LtSVecI8x16); }
+                    if (op == "i8x16.lt_s"sv) { return makeBinary(s, BinaryOp::LtSVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.lt_u") == 0) { return makeBinary(s, BinaryOp::LtUVecI8x16); }
+                    if (op == "i8x16.lt_u"sv) { return makeBinary(s, BinaryOp::LtUVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2808,10 +2803,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[10]) {
                   case 's':
-                    if (strcmp(op, "i8x16.max_s") == 0) { return makeBinary(s, BinaryOp::MaxSVecI8x16); }
+                    if (op == "i8x16.max_s"sv) { return makeBinary(s, BinaryOp::MaxSVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.max_u") == 0) { return makeBinary(s, BinaryOp::MaxUVecI8x16); }
+                    if (op == "i8x16.max_u"sv) { return makeBinary(s, BinaryOp::MaxUVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2819,10 +2814,10 @@ switch (op[0]) {
               case 'i': {
                 switch (op[10]) {
                   case 's':
-                    if (strcmp(op, "i8x16.min_s") == 0) { return makeBinary(s, BinaryOp::MinSVecI8x16); }
+                    if (op == "i8x16.min_s"sv) { return makeBinary(s, BinaryOp::MinSVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.min_u") == 0) { return makeBinary(s, BinaryOp::MinUVecI8x16); }
+                    if (op == "i8x16.min_u"sv) { return makeBinary(s, BinaryOp::MinUVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2835,10 +2830,10 @@ switch (op[0]) {
               case 'a': {
                 switch (op[19]) {
                   case 's':
-                    if (strcmp(op, "i8x16.narrow_i16x8_s") == 0) { return makeBinary(s, BinaryOp::NarrowSVecI16x8ToVecI8x16); }
+                    if (op == "i8x16.narrow_i16x8_s"sv) { return makeBinary(s, BinaryOp::NarrowSVecI16x8ToVecI8x16); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "i8x16.narrow_i16x8_u") == 0) { return makeBinary(s, BinaryOp::NarrowUVecI16x8ToVecI8x16); }
+                    if (op == "i8x16.narrow_i16x8_u"sv) { return makeBinary(s, BinaryOp::NarrowUVecI16x8ToVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2846,10 +2841,10 @@ switch (op[0]) {
               case 'e': {
                 switch (op[8]) {
                   case '\0':
-                    if (strcmp(op, "i8x16.ne") == 0) { return makeBinary(s, BinaryOp::NeVecI8x16); }
+                    if (op == "i8x16.ne"sv) { return makeBinary(s, BinaryOp::NeVecI8x16); }
                     goto parse_error;
                   case 'g':
-                    if (strcmp(op, "i8x16.neg") == 0) { return makeUnary(s, UnaryOp::NegVecI8x16); }
+                    if (op == "i8x16.neg"sv) { return makeUnary(s, UnaryOp::NegVecI8x16); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -2858,15 +2853,15 @@ switch (op[0]) {
             }
           }
           case 'p':
-            if (strcmp(op, "i8x16.popcnt") == 0) { return makeUnary(s, UnaryOp::PopcntVecI8x16); }
+            if (op == "i8x16.popcnt"sv) { return makeUnary(s, UnaryOp::PopcntVecI8x16); }
             goto parse_error;
           case 'r': {
             switch (op[8]) {
               case 'l':
-                if (strcmp(op, "i8x16.relaxed_swizzle") == 0) { return makeBinary(s, BinaryOp::RelaxedSwizzleVecI8x16); }
+                if (op == "i8x16.relaxed_swizzle"sv) { return makeBinary(s, BinaryOp::RelaxedSwizzleVecI8x16); }
                 goto parse_error;
               case 'p':
-                if (strcmp(op, "i8x16.replace_lane") == 0) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI8x16, 16); }
+                if (op == "i8x16.replace_lane"sv) { return makeSIMDReplace(s, SIMDReplaceOp::ReplaceLaneVecI8x16, 16); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -2876,40 +2871,40 @@ switch (op[0]) {
               case 'h': {
                 switch (op[8]) {
                   case 'l':
-                    if (strcmp(op, "i8x16.shl") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI8x16); }
+                    if (op == "i8x16.shl"sv) { return makeSIMDShift(s, SIMDShiftOp::ShlVecI8x16); }
                     goto parse_error;
                   case 'r': {
                     switch (op[10]) {
                       case 's':
-                        if (strcmp(op, "i8x16.shr_s") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI8x16); }
+                        if (op == "i8x16.shr_s"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrSVecI8x16); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i8x16.shr_u") == 0) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI8x16); }
+                        if (op == "i8x16.shr_u"sv) { return makeSIMDShift(s, SIMDShiftOp::ShrUVecI8x16); }
                         goto parse_error;
                       default: goto parse_error;
                     }
                   }
                   case 'u':
-                    if (strcmp(op, "i8x16.shuffle") == 0) { return makeSIMDShuffle(s); }
+                    if (op == "i8x16.shuffle"sv) { return makeSIMDShuffle(s); }
                     goto parse_error;
                   default: goto parse_error;
                 }
               }
               case 'p':
-                if (strcmp(op, "i8x16.splat") == 0) { return makeUnary(s, UnaryOp::SplatVecI8x16); }
+                if (op == "i8x16.splat"sv) { return makeUnary(s, UnaryOp::SplatVecI8x16); }
                 goto parse_error;
               case 'u': {
                 switch (op[9]) {
                   case '\0':
-                    if (strcmp(op, "i8x16.sub") == 0) { return makeBinary(s, BinaryOp::SubVecI8x16); }
+                    if (op == "i8x16.sub"sv) { return makeBinary(s, BinaryOp::SubVecI8x16); }
                     goto parse_error;
                   case '_': {
                     switch (op[14]) {
                       case 's':
-                        if (strcmp(op, "i8x16.sub_sat_s") == 0) { return makeBinary(s, BinaryOp::SubSatSVecI8x16); }
+                        if (op == "i8x16.sub_sat_s"sv) { return makeBinary(s, BinaryOp::SubSatSVecI8x16); }
                         goto parse_error;
                       case 'u':
-                        if (strcmp(op, "i8x16.sub_sat_u") == 0) { return makeBinary(s, BinaryOp::SubSatUVecI8x16); }
+                        if (op == "i8x16.sub_sat_u"sv) { return makeBinary(s, BinaryOp::SubSatUVecI8x16); }
                         goto parse_error;
                       default: goto parse_error;
                     }
@@ -2918,7 +2913,7 @@ switch (op[0]) {
                 }
               }
               case 'w':
-                if (strcmp(op, "i8x16.swizzle") == 0) { return makeBinary(s, BinaryOp::SwizzleVecI8x16); }
+                if (op == "i8x16.swizzle"sv) { return makeBinary(s, BinaryOp::SwizzleVecI8x16); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -2927,7 +2922,7 @@ switch (op[0]) {
         }
       }
       case 'f':
-        if (strcmp(op, "if") == 0) { return makeIf(s); }
+        if (op == "if"sv) { return makeIf(s); }
         goto parse_error;
       default: goto parse_error;
     }
@@ -2937,19 +2932,19 @@ switch (op[0]) {
       case 'c': {
         switch (op[6]) {
           case 'g':
-            if (strcmp(op, "local.get") == 0) { return makeLocalGet(s); }
+            if (op == "local.get"sv) { return makeLocalGet(s); }
             goto parse_error;
           case 's':
-            if (strcmp(op, "local.set") == 0) { return makeLocalSet(s); }
+            if (op == "local.set"sv) { return makeLocalSet(s); }
             goto parse_error;
           case 't':
-            if (strcmp(op, "local.tee") == 0) { return makeLocalTee(s); }
+            if (op == "local.tee"sv) { return makeLocalTee(s); }
             goto parse_error;
           default: goto parse_error;
         }
       }
       case 'o':
-        if (strcmp(op, "loop") == 0) { return makeLoop(s); }
+        if (op == "loop"sv) { return makeLoop(s); }
         goto parse_error;
       default: goto parse_error;
     }
@@ -2959,15 +2954,15 @@ switch (op[0]) {
       case 'a': {
         switch (op[14]) {
           case 'n':
-            if (strcmp(op, "memory.atomic.notify") == 0) { return makeAtomicNotify(s); }
+            if (op == "memory.atomic.notify"sv) { return makeAtomicNotify(s); }
             goto parse_error;
           case 'w': {
             switch (op[18]) {
               case '3':
-                if (strcmp(op, "memory.atomic.wait32") == 0) { return makeAtomicWait(s, Type::i32); }
+                if (op == "memory.atomic.wait32"sv) { return makeAtomicWait(s, Type::i32); }
                 goto parse_error;
               case '6':
-                if (strcmp(op, "memory.atomic.wait64") == 0) { return makeAtomicWait(s, Type::i64); }
+                if (op == "memory.atomic.wait64"sv) { return makeAtomicWait(s, Type::i64); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -2976,329 +2971,451 @@ switch (op[0]) {
         }
       }
       case 'c':
-        if (strcmp(op, "memory.copy") == 0) { return makeMemoryCopy(s); }
+        if (op == "memory.copy"sv) { return makeMemoryCopy(s); }
         goto parse_error;
       case 'f':
-        if (strcmp(op, "memory.fill") == 0) { return makeMemoryFill(s); }
+        if (op == "memory.fill"sv) { return makeMemoryFill(s); }
         goto parse_error;
       case 'g':
-        if (strcmp(op, "memory.grow") == 0) { return makeMemoryGrow(s); }
+        if (op == "memory.grow"sv) { return makeMemoryGrow(s); }
         goto parse_error;
       case 'i':
-        if (strcmp(op, "memory.init") == 0) { return makeMemoryInit(s); }
+        if (op == "memory.init"sv) { return makeMemoryInit(s); }
         goto parse_error;
       case 's':
-        if (strcmp(op, "memory.size") == 0) { return makeMemorySize(s); }
+        if (op == "memory.size"sv) { return makeMemorySize(s); }
         goto parse_error;
       default: goto parse_error;
     }
   }
   case 'n':
-    if (strcmp(op, "nop") == 0) { return makeNop(); }
+    if (op == "nop"sv) { return makeNop(); }
     goto parse_error;
   case 'p':
-    if (strcmp(op, "pop") == 0) { return makePop(s); }
+    if (op == "pop"sv) { return makePop(s); }
     goto parse_error;
   case 'r': {
-    switch (op[1]) {
-      case 'e': {
-        switch (op[2]) {
-          case 'f': {
-            switch (op[4]) {
-              case 'a': {
-                switch (op[7]) {
-                  case 'd':
-                    if (strcmp(op, "ref.as_data") == 0) { return makeRefAs(s, RefAsData); }
-                    goto parse_error;
-                  case 'f':
-                    if (strcmp(op, "ref.as_func") == 0) { return makeRefAs(s, RefAsFunc); }
-                    goto parse_error;
-                  case 'i':
-                    if (strcmp(op, "ref.as_i31") == 0) { return makeRefAs(s, RefAsI31); }
-                    goto parse_error;
-                  case 'n':
-                    if (strcmp(op, "ref.as_non_null") == 0) { return makeRefAs(s, RefAsNonNull); }
-                    goto parse_error;
-                  default: goto parse_error;
-                }
-              }
-              case 'c': {
-                switch (op[8]) {
-                  case '\0':
-                    if (strcmp(op, "ref.cast") == 0) { return makeRefCast(s); }
-                    goto parse_error;
-                  case '_': {
-                    switch (op[9]) {
-                      case 'n':
-                        if (strcmp(op, "ref.cast_nop_static") == 0) { return makeRefCastNopStatic(s); }
-                        goto parse_error;
-                      case 's':
-                        if (strcmp(op, "ref.cast_static") == 0) { return makeRefCastStatic(s); }
-                        goto parse_error;
-                      default: goto parse_error;
-                    }
-                  }
-                  default: goto parse_error;
-                }
-              }
-              case 'e':
-                if (strcmp(op, "ref.eq") == 0) { return makeRefEq(s); }
+    switch (op[2]) {
+      case 'f': {
+        switch (op[4]) {
+          case 'a': {
+            switch (op[7]) {
+              case 'd':
+                if (op == "ref.as_data"sv) { return makeRefAs(s, RefAsData); }
                 goto parse_error;
               case 'f':
-                if (strcmp(op, "ref.func") == 0) { return makeRefFunc(s); }
+                if (op == "ref.as_func"sv) { return makeRefAs(s, RefAsFunc); }
+                goto parse_error;
+              case 'i':
+                if (op == "ref.as_i31"sv) { return makeRefAs(s, RefAsI31); }
                 goto parse_error;
-              case 'i': {
-                switch (op[7]) {
-                  case 'd':
-                    if (strcmp(op, "ref.is_data") == 0) { return makeRefIs(s, RefIsData); }
-                    goto parse_error;
-                  case 'f':
-                    if (strcmp(op, "ref.is_func") == 0) { return makeRefIs(s, RefIsFunc); }
-                    goto parse_error;
-                  case 'i':
-                    if (strcmp(op, "ref.is_i31") == 0) { return makeRefIs(s, RefIsI31); }
-                    goto parse_error;
-                  case 'n':
-                    if (strcmp(op, "ref.is_null") == 0) { return makeRefIs(s, RefIsNull); }
-                    goto parse_error;
-                  default: goto parse_error;
-                }
-              }
               case 'n':
-                if (strcmp(op, "ref.null") == 0) { return makeRefNull(s); }
+                if (op == "ref.as_non_null"sv) { return makeRefAs(s, RefAsNonNull); }
                 goto parse_error;
-              case 't': {
-                switch (op[8]) {
-                  case '\0':
-                    if (strcmp(op, "ref.test") == 0) { return makeRefTest(s); }
-                    goto parse_error;
-                  case '_':
-                    if (strcmp(op, "ref.test_static") == 0) { return makeRefTestStatic(s); }
-                    goto parse_error;
-                  default: goto parse_error;
-                }
-              }
               default: goto parse_error;
             }
           }
-          case 't': {
-            switch (op[3]) {
-              case 'h':
-                if (strcmp(op, "rethrow") == 0) { return makeRethrow(s); }
+          case 'c': {
+            switch (op[9]) {
+              case 'n':
+                if (op == "ref.cast_nop_static"sv) { return makeRefCastNopStatic(s); }
+                goto parse_error;
+              case 's':
+                if (op == "ref.cast_static"sv) { return makeRefCastStatic(s); }
                 goto parse_error;
-              case 'u': {
-                switch (op[6]) {
-                  case '\0':
-                    if (strcmp(op, "return") == 0) { return makeReturn(s); }
-                    goto parse_error;
-                  case '_': {
-                    switch (op[11]) {
-                      case '\0':
-                        if (strcmp(op, "return_call") == 0) { return makeCall(s, /*isReturn=*/true); }
-                        goto parse_error;
-                      case '_': {
-                        switch (op[12]) {
-                          case 'i':
-                            if (strcmp(op, "return_call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/true); }
-                            goto parse_error;
-                          case 'r':
-                            if (strcmp(op, "return_call_ref") == 0) { return makeCallRef(s, /*isReturn=*/true); }
-                            goto parse_error;
-                          default: goto parse_error;
-                        }
-                      }
-                      default: goto parse_error;
-                    }
-                  }
-                  default: goto parse_error;
-                }
-              }
               default: goto parse_error;
             }
           }
-          default: goto parse_error;
-        }
-      }
-      case 't': {
-        switch (op[4]) {
-          case 'c':
-            if (strcmp(op, "rtt.canon") == 0) { return makeRttCanon(s); }
+          case 'e':
+            if (op == "ref.eq"sv) { return makeRefEq(s); }
             goto parse_error;
           case 'f':
-            if (strcmp(op, "rtt.fresh_sub") == 0) { return makeRttFreshSub(s); }
+            if (op == "ref.func"sv) { return makeRefFunc(s); }
             goto parse_error;
-          case 's':
-            if (strcmp(op, "rtt.sub") == 0) { return makeRttSub(s); }
+          case 'i': {
+            switch (op[7]) {
+              case 'd':
+                if (op == "ref.is_data"sv) { return makeRefIs(s, RefIsData); }
+                goto parse_error;
+              case 'f':
+                if (op == "ref.is_func"sv) { return makeRefIs(s, RefIsFunc); }
+                goto parse_error;
+              case 'i':
+                if (op == "ref.is_i31"sv) { return makeRefIs(s, RefIsI31); }
+                goto parse_error;
+              case 'n':
+                if (op == "ref.is_null"sv) { return makeRefIs(s, RefIsNull); }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'n':
+            if (op == "ref.null"sv) { return makeRefNull(s); }
+            goto parse_error;
+          case 't':
+            if (op == "ref.test_static"sv) { return makeRefTestStatic(s); }
             goto parse_error;
           default: goto parse_error;
         }
       }
-      default: goto parse_error;
-    }
-  }
-  case 's': {
-    switch (op[1]) {
-      case 'e':
-        if (strcmp(op, "select") == 0) { return makeSelect(s); }
-        goto parse_error;
       case 't': {
-        switch (op[7]) {
-          case 'g': {
-            switch (op[10]) {
+        switch (op[3]) {
+          case 'h':
+            if (op == "rethrow"sv) { return makeRethrow(s); }
+            goto parse_error;
+          case 'u': {
+            switch (op[6]) {
               case '\0':
-                if (strcmp(op, "struct.get") == 0) { return makeStructGet(s); }
+                if (op == "return"sv) { return makeReturn(s); }
                 goto parse_error;
               case '_': {
                 switch (op[11]) {
-                  case 's':
-                    if (strcmp(op, "struct.get_s") == 0) { return makeStructGet(s, true); }
-                    goto parse_error;
-                  case 'u':
-                    if (strcmp(op, "struct.get_u") == 0) { return makeStructGet(s, false); }
+                  case '\0':
+                    if (op == "return_call"sv) { return makeCall(s, /*isReturn=*/true); }
                     goto parse_error;
-                  default: goto parse_error;
-                }
-              }
-              default: goto parse_error;
-            }
-          }
-          case 'n': {
-            switch (op[10]) {
-              case '\0':
-                if (strcmp(op, "struct.new") == 0) { return makeStructNewStatic(s, false); }
-                goto parse_error;
-              case '_': {
-                switch (op[11]) {
-                  case 'd': {
-                    switch (op[18]) {
-                      case '\0':
-                        if (strcmp(op, "struct.new_default") == 0) { return makeStructNewStatic(s, true); }
+                  case '_': {
+                    switch (op[12]) {
+                      case 'i':
+                        if (op == "return_call_indirect"sv) { return makeCallIndirect(s, /*isReturn=*/true); }
                         goto parse_error;
-                      case '_':
-                        if (strcmp(op, "struct.new_default_with_rtt") == 0) { return makeStructNew(s, true); }
+                      case 'r':
+                        if (op == "return_call_ref"sv) { return makeCallRef(s, /*isReturn=*/true); }
                         goto parse_error;
                       default: goto parse_error;
                     }
                   }
-                  case 'w':
-                    if (strcmp(op, "struct.new_with_rtt") == 0) { return makeStructNew(s, false); }
-                    goto parse_error;
                   default: goto parse_error;
                 }
               }
               default: goto parse_error;
             }
           }
-          case 's':
-            if (strcmp(op, "struct.set") == 0) { return makeStructSet(s); }
-            goto parse_error;
           default: goto parse_error;
         }
       }
       default: goto parse_error;
     }
   }
-  case 't': {
+  case 's': {
     switch (op[1]) {
-      case 'a': {
-        switch (op[6]) {
-          case 'g': {
-            switch (op[7]) {
-              case 'e':
-                if (strcmp(op, "table.get") == 0) { return makeTableGet(s); }
-                goto parse_error;
-              case 'r':
-                if (strcmp(op, "table.grow") == 0) { return makeTableGrow(s); }
-                goto parse_error;
-              default: goto parse_error;
-            }
-          }
-          case 's': {
-            switch (op[7]) {
-              case 'e':
-                if (strcmp(op, "table.set") == 0) { return makeTableSet(s); }
-                goto parse_error;
-              case 'i':
-                if (strcmp(op, "table.size") == 0) { return makeTableSize(s); }
-                goto parse_error;
-              default: goto parse_error;
-            }
-          }
-          default: goto parse_error;
-        }
-      }
-      case 'h': {
-        switch (op[2]) {
-          case 'e':
-            if (strcmp(op, "then") == 0) { return makeThenOrElse(s); }
-            goto parse_error;
-          case 'r':
-            if (strcmp(op, "throw") == 0) { return makeThrow(s); }
-            goto parse_error;
-          default: goto parse_error;
-        }
-      }
-      case 'r':
-        if (strcmp(op, "try") == 0) { return makeTry(s); }
+      case 'e':
+        if (op == "select"sv) { return makeSelect(s); }
         goto parse_error;
-      case 'u': {
-        switch (op[6]) {
-          case 'e':
-            if (strcmp(op, "tuple.extract") == 0) { return makeTupleExtract(s); }
-            goto parse_error;
-          case 'm':
-            if (strcmp(op, "tuple.make") == 0) { return makeTupleMake(s); }
-            goto parse_error;
-          default: goto parse_error;
-        }
-      }
-      default: goto parse_error;
-    }
-  }
-  case 'u':
-    if (strcmp(op, "unreachable") == 0) { return makeUnreachable(); }
-    goto parse_error;
-  case 'v': {
-    switch (op[5]) {
-      case 'a': {
-        switch (op[7]) {
+      case 't': {
+        switch (op[3]) {
+          case 'i': {
+            switch (op[6]) {
+              case '.': {
+                switch (op[7]) {
+                  case 'a': {
+                    switch (op[10]) {
+                      case 'i':
+                        if (op == "string.as_iter"sv) { return makeStringAs(s, StringAsIter); }
+                        goto parse_error;
+                      case 'w': {
+                        switch (op[13]) {
+                          case '1':
+                            if (op == "string.as_wtf16"sv) { return makeStringAs(s, StringAsWTF16); }
+                            goto parse_error;
+                          case '8':
+                            if (op == "string.as_wtf8"sv) { return makeStringAs(s, StringAsWTF8); }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'c': {
+                    switch (op[10]) {
+                      case 'c':
+                        if (op == "string.concat"sv) { return makeStringConcat(s); }
+                        goto parse_error;
+                      case 's':
+                        if (op == "string.const"sv) { return makeStringConst(s); }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'e': {
+                    switch (op[8]) {
+                      case 'n': {
+                        switch (op[17]) {
+                          case '1': {
+                            switch (op[19]) {
+                              case '\0':
+                                if (op == "string.encode_wtf16"sv) { return makeStringEncode(s, StringEncodeWTF16); }
+                                goto parse_error;
+                              case '_':
+                                if (op == "string.encode_wtf16_array"sv) { return makeStringEncode(s, StringEncodeWTF16Array); }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case '8': {
+                            switch (op[18]) {
+                              case '\0':
+                                if (op == "string.encode_wtf8"sv) { return makeStringEncode(s, StringEncodeWTF8); }
+                                goto parse_error;
+                              case '_':
+                                if (op == "string.encode_wtf8_array"sv) { return makeStringEncode(s, StringEncodeWTF8Array); }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'q':
+                        if (op == "string.eq"sv) { return makeStringEq(s); }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'i':
+                    if (op == "string.is_usv_sequence"sv) { return makeStringMeasure(s, StringMeasureIsUSV); }
+                    goto parse_error;
+                  case 'm': {
+                    switch (op[18]) {
+                      case '1':
+                        if (op == "string.measure_wtf16"sv) { return makeStringMeasure(s, StringMeasureWTF16); }
+                        goto parse_error;
+                      case '8':
+                        if (op == "string.measure_wtf8"sv) { return makeStringMeasure(s, StringMeasureWTF8); }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'n': {
+                    switch (op[14]) {
+                      case '1': {
+                        switch (op[16]) {
+                          case '\0':
+                            if (op == "string.new_wtf16"sv) { return makeStringNew(s, StringNewWTF16); }
+                            goto parse_error;
+                          case '_':
+                            if (op == "string.new_wtf16_array"sv) { return makeStringNew(s, StringNewWTF16Array); }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '8': {
+                        switch (op[15]) {
+                          case '\0':
+                            if (op == "string.new_wtf8"sv) { return makeStringNew(s, StringNewWTF8); }
+                            goto parse_error;
+                          case '_':
+                            if (op == "string.new_wtf8_array"sv) { return makeStringNew(s, StringNewWTF8Array); }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'v': {
+                switch (op[11]) {
+                  case 'i': {
+                    switch (op[16]) {
+                      case 'a':
+                        if (op == "stringview_iter.advance"sv) { return makeStringIterMove(s, StringIterMoveAdvance); }
+                        goto parse_error;
+                      case 'n':
+                        if (op == "stringview_iter.next"sv) { return makeStringIterNext(s); }
+                        goto parse_error;
+                      case 'r':
+                        if (op == "stringview_iter.rewind"sv) { return makeStringIterMove(s, StringIterMoveRewind); }
+                        goto parse_error;
+                      case 's':
+                        if (op == "stringview_iter.slice"sv) { return makeStringSliceIter(s); }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'w': {
+                    switch (op[14]) {
+                      case '1': {
+                        switch (op[17]) {
+                          case 'g':
+                            if (op == "stringview_wtf16.get_codeunit"sv) { return makeStringWTF16Get(s); }
+                            goto parse_error;
+                          case 'l':
+                            if (op == "stringview_wtf16.length"sv) { return makeStringMeasure(s, StringMeasureWTF16View); }
+                            goto parse_error;
+                          case 's':
+                            if (op == "stringview_wtf16.slice"sv) { return makeStringSliceWTF(s, StringSliceWTF16); }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '8': {
+                        switch (op[16]) {
+                          case 'a':
+                            if (op == "stringview_wtf8.advance"sv) { return makeStringWTF8Advance(s); }
+                            goto parse_error;
+                          case 's':
+                            if (op == "stringview_wtf8.slice"sv) { return makeStringSliceWTF(s, StringSliceWTF8); }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'u': {
+            switch (op[7]) {
+              case 'g': {
+                switch (op[10]) {
+                  case '\0':
+                    if (op == "struct.get"sv) { return makeStructGet(s); }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[11]) {
+                      case 's':
+                        if (op == "struct.get_s"sv) { return makeStructGet(s, true); }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "struct.get_u"sv) { return makeStructGet(s, false); }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'n': {
+                switch (op[10]) {
+                  case '\0':
+                    if (op == "struct.new"sv) { return makeStructNewStatic(s, false); }
+                    goto parse_error;
+                  case '_':
+                    if (op == "struct.new_default"sv) { return makeStructNewStatic(s, true); }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 's':
+                if (op == "struct.set"sv) { return makeStructSet(s); }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 't': {
+    switch (op[1]) {
+      case 'a': {
+        switch (op[6]) {
+          case 'g': {
+            switch (op[7]) {
+              case 'e':
+                if (op == "table.get"sv) { return makeTableGet(s); }
+                goto parse_error;
+              case 'r':
+                if (op == "table.grow"sv) { return makeTableGrow(s); }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 's': {
+            switch (op[7]) {
+              case 'e':
+                if (op == "table.set"sv) { return makeTableSet(s); }
+                goto parse_error;
+              case 'i':
+                if (op == "table.size"sv) { return makeTableSize(s); }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case 'h': {
+        switch (op[2]) {
+          case 'e':
+            if (op == "then"sv) { return makeThenOrElse(s); }
+            goto parse_error;
+          case 'r':
+            if (op == "throw"sv) { return makeThrow(s); }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 'r':
+        if (op == "try"sv) { return makeTry(s); }
+        goto parse_error;
+      case 'u': {
+        switch (op[6]) {
+          case 'e':
+            if (op == "tuple.extract"sv) { return makeTupleExtract(s); }
+            goto parse_error;
+          case 'm':
+            if (op == "tuple.make"sv) { return makeTupleMake(s); }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 'u':
+    if (op == "unreachable"sv) { return makeUnreachable(); }
+    goto parse_error;
+  case 'v': {
+    switch (op[5]) {
+      case 'a': {
+        switch (op[7]) {
           case 'd': {
             switch (op[8]) {
               case '\0':
-                if (strcmp(op, "v128.and") == 0) { return makeBinary(s, BinaryOp::AndVec128); }
+                if (op == "v128.and"sv) { return makeBinary(s, BinaryOp::AndVec128); }
                 goto parse_error;
               case 'n':
-                if (strcmp(op, "v128.andnot") == 0) { return makeBinary(s, BinaryOp::AndNotVec128); }
+                if (op == "v128.andnot"sv) { return makeBinary(s, BinaryOp::AndNotVec128); }
                 goto parse_error;
               default: goto parse_error;
             }
           }
           case 'y':
-            if (strcmp(op, "v128.any_true") == 0) { return makeUnary(s, UnaryOp::AnyTrueVec128); }
+            if (op == "v128.any_true"sv) { return makeUnary(s, UnaryOp::AnyTrueVec128); }
             goto parse_error;
           default: goto parse_error;
         }
       }
       case 'b':
-        if (strcmp(op, "v128.bitselect") == 0) { return makeSIMDTernary(s, SIMDTernaryOp::Bitselect); }
+        if (op == "v128.bitselect"sv) { return makeSIMDTernary(s, SIMDTernaryOp::Bitselect); }
         goto parse_error;
       case 'c':
-        if (strcmp(op, "v128.const") == 0) { return makeConst(s, Type::v128); }
+        if (op == "v128.const"sv) { return makeConst(s, Type::v128); }
         goto parse_error;
       case 'l': {
         switch (op[9]) {
           case '\0':
-            if (strcmp(op, "v128.load") == 0) { return makeLoad(s, Type::v128, /*isAtomic=*/false); }
+            if (op == "v128.load"sv) { return makeLoad(s, Type::v128, /*signed=*/false, 16, /*isAtomic=*/false); }
             goto parse_error;
           case '1': {
             switch (op[11]) {
               case '_': {
                 switch (op[12]) {
                   case 'l':
-                    if (strcmp(op, "v128.load16_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load16LaneVec128); }
+                    if (op == "v128.load16_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load16LaneVec128, 2); }
                     goto parse_error;
                   case 's':
-                    if (strcmp(op, "v128.load16_splat") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load16SplatVec128); }
+                    if (op == "v128.load16_splat"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load16SplatVec128, 2); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -3306,10 +3423,10 @@ switch (op[0]) {
               case 'x': {
                 switch (op[14]) {
                   case 's':
-                    if (strcmp(op, "v128.load16x4_s") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load16x4SVec128); }
+                    if (op == "v128.load16x4_s"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load16x4SVec128, 8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "v128.load16x4_u") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load16x4UVec128); }
+                    if (op == "v128.load16x4_u"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load16x4UVec128, 8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -3322,13 +3439,13 @@ switch (op[0]) {
               case '_': {
                 switch (op[12]) {
                   case 'l':
-                    if (strcmp(op, "v128.load32_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load32LaneVec128); }
+                    if (op == "v128.load32_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load32LaneVec128, 4); }
                     goto parse_error;
                   case 's':
-                    if (strcmp(op, "v128.load32_splat") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load32SplatVec128); }
+                    if (op == "v128.load32_splat"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load32SplatVec128, 4); }
                     goto parse_error;
                   case 'z':
-                    if (strcmp(op, "v128.load32_zero") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load32ZeroVec128); }
+                    if (op == "v128.load32_zero"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load32ZeroVec128, 4); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -3336,10 +3453,10 @@ switch (op[0]) {
               case 'x': {
                 switch (op[14]) {
                   case 's':
-                    if (strcmp(op, "v128.load32x2_s") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load32x2SVec128); }
+                    if (op == "v128.load32x2_s"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load32x2SVec128, 8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "v128.load32x2_u") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load32x2UVec128); }
+                    if (op == "v128.load32x2_u"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load32x2UVec128, 8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -3350,13 +3467,13 @@ switch (op[0]) {
           case '6': {
             switch (op[12]) {
               case 'l':
-                if (strcmp(op, "v128.load64_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load64LaneVec128); }
+                if (op == "v128.load64_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load64LaneVec128, 8); }
                 goto parse_error;
               case 's':
-                if (strcmp(op, "v128.load64_splat") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load64SplatVec128); }
+                if (op == "v128.load64_splat"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load64SplatVec128, 8); }
                 goto parse_error;
               case 'z':
-                if (strcmp(op, "v128.load64_zero") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load64ZeroVec128); }
+                if (op == "v128.load64_zero"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load64ZeroVec128, 8); }
                 goto parse_error;
               default: goto parse_error;
             }
@@ -3366,10 +3483,10 @@ switch (op[0]) {
               case '_': {
                 switch (op[11]) {
                   case 'l':
-                    if (strcmp(op, "v128.load8_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load8LaneVec128); }
+                    if (op == "v128.load8_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Load8LaneVec128, 1); }
                     goto parse_error;
                   case 's':
-                    if (strcmp(op, "v128.load8_splat") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load8SplatVec128); }
+                    if (op == "v128.load8_splat"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load8SplatVec128, 1); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -3377,10 +3494,10 @@ switch (op[0]) {
               case 'x': {
                 switch (op[13]) {
                   case 's':
-                    if (strcmp(op, "v128.load8x8_s") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load8x8SVec128); }
+                    if (op == "v128.load8x8_s"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load8x8SVec128, 8); }
                     goto parse_error;
                   case 'u':
-                    if (strcmp(op, "v128.load8x8_u") == 0) { return makeSIMDLoad(s, SIMDLoadOp::Load8x8UVec128); }
+                    if (op == "v128.load8x8_u"sv) { return makeSIMDLoad(s, SIMDLoadOp::Load8x8UVec128, 8); }
                     goto parse_error;
                   default: goto parse_error;
                 }
@@ -3392,33 +3509,33 @@ switch (op[0]) {
         }
       }
       case 'n':
-        if (strcmp(op, "v128.not") == 0) { return makeUnary(s, UnaryOp::NotVec128); }
+        if (op == "v128.not"sv) { return makeUnary(s, UnaryOp::NotVec128); }
         goto parse_error;
       case 'o':
-        if (strcmp(op, "v128.or") == 0) { return makeBinary(s, BinaryOp::OrVec128); }
+        if (op == "v128.or"sv) { return makeBinary(s, BinaryOp::OrVec128); }
         goto parse_error;
       case 's': {
         switch (op[10]) {
           case '\0':
-            if (strcmp(op, "v128.store") == 0) { return makeStore(s, Type::v128, /*isAtomic=*/false); }
+            if (op == "v128.store"sv) { return makeStore(s, Type::v128, 16, /*isAtomic=*/false); }
             goto parse_error;
           case '1':
-            if (strcmp(op, "v128.store16_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store16LaneVec128); }
+            if (op == "v128.store16_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store16LaneVec128, 2); }
             goto parse_error;
           case '3':
-            if (strcmp(op, "v128.store32_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store32LaneVec128); }
+            if (op == "v128.store32_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store32LaneVec128, 4); }
             goto parse_error;
           case '6':
-            if (strcmp(op, "v128.store64_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store64LaneVec128); }
+            if (op == "v128.store64_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store64LaneVec128, 8); }
             goto parse_error;
           case '8':
-            if (strcmp(op, "v128.store8_lane") == 0) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store8LaneVec128); }
+            if (op == "v128.store8_lane"sv) { return makeSIMDLoadStoreLane(s, SIMDLoadStoreLaneOp::Store8LaneVec128, 1); }
             goto parse_error;
           default: goto parse_error;
         }
       }
       case 'x':
-        if (strcmp(op, "v128.xor") == 0) { return makeBinary(s, BinaryOp::XorVec128); }
+        if (op == "v128.xor"sv) { return makeBinary(s, BinaryOp::XorVec128); }
         goto parse_error;
       default: goto parse_error;
     }
@@ -3429,4 +3546,5947 @@ parse_error:
   throw ParseException(std::string(op), s.line, s.col);
 #endif // INSTRUCTION_PARSER
 
+#ifdef NEW_INSTRUCTION_PARSER
+#undef NEW_INSTRUCTION_PARSER
+char buf[33] = {};
+auto str = *keyword;
+memcpy(buf, str.data(), str.size());
+std::string_view op = {buf, str.size()};
+switch (op[0]) {
+  case 'a': {
+    switch (op[1]) {
+      case 'r': {
+        switch (op[6]) {
+          case 'c':
+            if (op == "array.copy"sv) {
+              auto ret = makeArrayCopy(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'g': {
+            switch (op[9]) {
+              case '\0':
+                if (op == "array.get"sv) {
+                  auto ret = makeArrayGet(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case '_': {
+                switch (op[10]) {
+                  case 's':
+                    if (op == "array.get_s"sv) {
+                      auto ret = makeArrayGet(ctx, pos, true);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "array.get_u"sv) {
+                      auto ret = makeArrayGet(ctx, pos, false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'i':
+            if (op == "array.init_static"sv) {
+              auto ret = makeArrayInitStatic(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'l':
+            if (op == "array.len"sv) {
+              auto ret = makeArrayLen(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'n': {
+            switch (op[9]) {
+              case '\0':
+                if (op == "array.new"sv) {
+                  auto ret = makeArrayNewStatic(ctx, pos, false);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case '_': {
+                switch (op[10]) {
+                  case 'd': {
+                    switch (op[11]) {
+                      case 'a':
+                        if (op == "array.new_data"sv) {
+                          auto ret = makeArrayNewSeg(ctx, pos, NewData);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'e':
+                        if (op == "array.new_default"sv) {
+                          auto ret = makeArrayNewStatic(ctx, pos, true);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'e':
+                    if (op == "array.new_elem"sv) {
+                      auto ret = makeArrayNewSeg(ctx, pos, NewElem);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 's':
+            if (op == "array.set"sv) {
+              auto ret = makeArraySet(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 't':
+        if (op == "atomic.fence"sv) {
+          auto ret = makeAtomicFence(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  case 'b': {
+    switch (op[1]) {
+      case 'l':
+        if (op == "block"sv) {
+          auto ret = makeBlock(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'r': {
+        switch (op[2]) {
+          case '\0':
+            if (op == "br"sv) {
+              auto ret = makeBreak(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case '_': {
+            switch (op[3]) {
+              case 'i':
+                if (op == "br_if"sv) {
+                  auto ret = makeBreak(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'o': {
+                switch (op[6]) {
+                  case 'c': {
+                    switch (op[10]) {
+                      case '\0':
+                        if (op == "br_on_cast"sv) {
+                          auto ret = makeBrOn(ctx, pos, BrOnCast);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '_': {
+                        switch (op[11]) {
+                          case 'f':
+                            if (op == "br_on_cast_fail"sv) {
+                              auto ret = makeBrOn(ctx, pos, BrOnCastFail);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 's': {
+                            switch (op[17]) {
+                              case '\0':
+                                if (op == "br_on_cast_static"sv) {
+                                  auto ret = makeBrOnStatic(ctx, pos, BrOnCast);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '_':
+                                if (op == "br_on_cast_static_fail"sv) {
+                                  auto ret = makeBrOnStatic(ctx, pos, BrOnCastFail);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'd':
+                    if (op == "br_on_data"sv) {
+                      auto ret = makeBrOn(ctx, pos, BrOnData);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'f':
+                    if (op == "br_on_func"sv) {
+                      auto ret = makeBrOn(ctx, pos, BrOnFunc);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "br_on_i31"sv) {
+                      auto ret = makeBrOn(ctx, pos, BrOnI31);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'n': {
+                    switch (op[7]) {
+                      case 'o': {
+                        switch (op[10]) {
+                          case 'd':
+                            if (op == "br_on_non_data"sv) {
+                              auto ret = makeBrOn(ctx, pos, BrOnNonData);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'f':
+                            if (op == "br_on_non_func"sv) {
+                              auto ret = makeBrOn(ctx, pos, BrOnNonFunc);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'i':
+                            if (op == "br_on_non_i31"sv) {
+                              auto ret = makeBrOn(ctx, pos, BrOnNonI31);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'n':
+                            if (op == "br_on_non_null"sv) {
+                              auto ret = makeBrOn(ctx, pos, BrOnNonNull);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'u':
+                        if (op == "br_on_null"sv) {
+                          auto ret = makeBrOn(ctx, pos, BrOnNull);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 't':
+                if (op == "br_table"sv) {
+                  auto ret = makeBreakTable(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 'c': {
+    switch (op[4]) {
+      case '\0':
+        if (op == "call"sv) {
+          auto ret = makeCall(ctx, pos, /*isReturn=*/false);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case '_': {
+        switch (op[5]) {
+          case 'i':
+            if (op == "call_indirect"sv) {
+              auto ret = makeCallIndirect(ctx, pos, /*isReturn=*/false);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'r':
+            if (op == "call_ref"sv) {
+              auto ret = makeCallRef(ctx, pos, /*isReturn=*/false);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 'd': {
+    switch (op[1]) {
+      case 'a':
+        if (op == "data.drop"sv) {
+          auto ret = makeDataDrop(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'r':
+        if (op == "drop"sv) {
+          auto ret = makeDrop(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  case 'e': {
+    switch (op[1]) {
+      case 'l':
+        if (op == "else"sv) {
+          auto ret = makeThenOrElse(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'x': {
+        switch (op[7]) {
+          case 'e':
+            if (op == "extern.externalize"sv) {
+              auto ret = makeRefAs(ctx, pos, ExternExternalize);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'i':
+            if (op == "extern.internalize"sv) {
+              auto ret = makeRefAs(ctx, pos, ExternInternalize);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 'f': {
+    switch (op[1]) {
+      case '3': {
+        switch (op[3]) {
+          case '.': {
+            switch (op[4]) {
+              case 'a': {
+                switch (op[5]) {
+                  case 'b':
+                    if (op == "f32.abs"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::AbsFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'd':
+                    if (op == "f32.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'c': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f32.ceil"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::CeilFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o': {
+                    switch (op[6]) {
+                      case 'n': {
+                        switch (op[7]) {
+                          case 's':
+                            if (op == "f32.const"sv) {
+                              auto ret = makeConst(ctx, pos, Type::f32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'v': {
+                            switch (op[13]) {
+                              case '3': {
+                                switch (op[16]) {
+                                  case 's':
+                                    if (op == "f32.convert_i32_s"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertSInt32ToFloat32);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "f32.convert_i32_u"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertUInt32ToFloat32);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case '6': {
+                                switch (op[16]) {
+                                  case 's':
+                                    if (op == "f32.convert_i64_s"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertSInt64ToFloat32);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "f32.convert_i64_u"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertUInt64ToFloat32);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'p':
+                        if (op == "f32.copysign"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::CopySignFloat32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'd': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f32.demote_f64"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::DemoteFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f32.div"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::DivFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'e':
+                if (op == "f32.eq"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::EqFloat32);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'f':
+                if (op == "f32.floor"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::FloorFloat32);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'g': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f32.ge"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f32.gt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'l': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f32.le"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o':
+                    if (op == "f32.load"sv) {
+                      auto ret = makeLoad(ctx, pos, Type::f32, /*signed=*/false, 4, /*isAtomic=*/false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f32.lt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'm': {
+                switch (op[5]) {
+                  case 'a':
+                    if (op == "f32.max"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f32.min"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f32.mul"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MulFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'n': {
+                switch (op[6]) {
+                  case '\0':
+                    if (op == "f32.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'a':
+                    if (op == "f32.nearest"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NearestFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "f32.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'r':
+                if (op == "f32.reinterpret_i32"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::ReinterpretInt32);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 's': {
+                switch (op[5]) {
+                  case 'q':
+                    if (op == "f32.sqrt"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SqrtFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f32.store"sv) {
+                      auto ret = makeStore(ctx, pos, Type::f32, 4, /*isAtomic=*/false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f32.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubFloat32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't':
+                if (op == "f32.trunc"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncFloat32);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'x': {
+            switch (op[6]) {
+              case 'a': {
+                switch (op[7]) {
+                  case 'b':
+                    if (op == "f32x4.abs"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::AbsVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'd':
+                    if (op == "f32x4.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'c': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f32x4.ceil"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::CeilVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o': {
+                    switch (op[20]) {
+                      case 's':
+                        if (op == "f32x4.convert_i32x4_s"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ConvertSVecI32x4ToVecF32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "f32x4.convert_i32x4_u"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ConvertUVecI32x4ToVecF32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'd': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f32x4.demote_f64x2_zero"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::DemoteZeroVecF64x2ToVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f32x4.div"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::DivVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'e': {
+                switch (op[7]) {
+                  case 'q':
+                    if (op == "f32x4.eq"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::EqVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'x':
+                    if (op == "f32x4.extract_lane"sv) {
+                      auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneVecF32x4, 4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'f':
+                if (op == "f32x4.floor"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::FloorVecF32x4);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'g': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f32x4.ge"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f32x4.gt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'l': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f32x4.le"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f32x4.lt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'm': {
+                switch (op[7]) {
+                  case 'a':
+                    if (op == "f32x4.max"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f32x4.min"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f32x4.mul"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MulVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'n': {
+                switch (op[8]) {
+                  case '\0':
+                    if (op == "f32x4.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'a':
+                    if (op == "f32x4.nearest"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NearestVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "f32x4.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'p': {
+                switch (op[8]) {
+                  case 'a':
+                    if (op == "f32x4.pmax"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::PMaxVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f32x4.pmin"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::PMinVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'r': {
+                switch (op[8]) {
+                  case 'l': {
+                    switch (op[14]) {
+                      case 'f': {
+                        switch (op[16]) {
+                          case 'a':
+                            if (op == "f32x4.relaxed_fma"sv) {
+                              auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::RelaxedFmaVecF32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 's':
+                            if (op == "f32x4.relaxed_fms"sv) {
+                              auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::RelaxedFmsVecF32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'm': {
+                        switch (op[15]) {
+                          case 'a':
+                            if (op == "f32x4.relaxed_max"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RelaxedMaxVecF32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'i':
+                            if (op == "f32x4.relaxed_min"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RelaxedMinVecF32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'p':
+                    if (op == "f32x4.replace_lane"sv) {
+                      auto ret = makeSIMDReplace(ctx, pos, SIMDReplaceOp::ReplaceLaneVecF32x4, 4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 's': {
+                switch (op[7]) {
+                  case 'p':
+                    if (op == "f32x4.splat"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SplatVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'q':
+                    if (op == "f32x4.sqrt"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SqrtVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f32x4.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubVecF32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't':
+                if (op == "f32x4.trunc"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncVecF32x4);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case '6': {
+        switch (op[3]) {
+          case '.': {
+            switch (op[4]) {
+              case 'a': {
+                switch (op[5]) {
+                  case 'b':
+                    if (op == "f64.abs"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::AbsFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'd':
+                    if (op == "f64.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'c': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f64.ceil"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::CeilFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o': {
+                    switch (op[6]) {
+                      case 'n': {
+                        switch (op[7]) {
+                          case 's':
+                            if (op == "f64.const"sv) {
+                              auto ret = makeConst(ctx, pos, Type::f64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'v': {
+                            switch (op[13]) {
+                              case '3': {
+                                switch (op[16]) {
+                                  case 's':
+                                    if (op == "f64.convert_i32_s"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertSInt32ToFloat64);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "f64.convert_i32_u"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertUInt32ToFloat64);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case '6': {
+                                switch (op[16]) {
+                                  case 's':
+                                    if (op == "f64.convert_i64_s"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertSInt64ToFloat64);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "f64.convert_i64_u"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ConvertUInt64ToFloat64);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'p':
+                        if (op == "f64.copysign"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::CopySignFloat64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'd':
+                if (op == "f64.div"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::DivFloat64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'e':
+                if (op == "f64.eq"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::EqFloat64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'f':
+                if (op == "f64.floor"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::FloorFloat64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'g': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f64.ge"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f64.gt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'l': {
+                switch (op[5]) {
+                  case 'e':
+                    if (op == "f64.le"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o':
+                    if (op == "f64.load"sv) {
+                      auto ret = makeLoad(ctx, pos, Type::f64, /*signed=*/false, 8, /*isAtomic=*/false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f64.lt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'm': {
+                switch (op[5]) {
+                  case 'a':
+                    if (op == "f64.max"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f64.min"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f64.mul"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MulFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'n': {
+                switch (op[6]) {
+                  case '\0':
+                    if (op == "f64.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'a':
+                    if (op == "f64.nearest"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NearestFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "f64.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'p':
+                if (op == "f64.promote_f32"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::PromoteFloat32);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'r':
+                if (op == "f64.reinterpret_i64"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::ReinterpretInt64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 's': {
+                switch (op[5]) {
+                  case 'q':
+                    if (op == "f64.sqrt"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SqrtFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f64.store"sv) {
+                      auto ret = makeStore(ctx, pos, Type::f64, 8, /*isAtomic=*/false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f64.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubFloat64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't':
+                if (op == "f64.trunc"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncFloat64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'x': {
+            switch (op[6]) {
+              case 'a': {
+                switch (op[7]) {
+                  case 'b':
+                    if (op == "f64x2.abs"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::AbsVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'd':
+                    if (op == "f64x2.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'c': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f64x2.ceil"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::CeilVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o': {
+                    switch (op[24]) {
+                      case 's':
+                        if (op == "f64x2.convert_low_i32x4_s"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ConvertLowSVecI32x4ToVecF64x2);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "f64x2.convert_low_i32x4_u"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ConvertLowUVecI32x4ToVecF64x2);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'd':
+                if (op == "f64x2.div"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::DivVecF64x2);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'e': {
+                switch (op[7]) {
+                  case 'q':
+                    if (op == "f64x2.eq"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::EqVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'x':
+                    if (op == "f64x2.extract_lane"sv) {
+                      auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneVecF64x2, 2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'f':
+                if (op == "f64x2.floor"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::FloorVecF64x2);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'g': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f64x2.ge"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f64x2.gt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'l': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "f64x2.le"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "f64x2.lt"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'm': {
+                switch (op[7]) {
+                  case 'a':
+                    if (op == "f64x2.max"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'i':
+                    if (op == "f64x2.min"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f64x2.mul"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MulVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'n': {
+                switch (op[8]) {
+                  case '\0':
+                    if (op == "f64x2.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'a':
+                    if (op == "f64x2.nearest"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NearestVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "f64x2.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'p': {
+                switch (op[7]) {
+                  case 'm': {
+                    switch (op[8]) {
+                      case 'a':
+                        if (op == "f64x2.pmax"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::PMaxVecF64x2);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'i':
+                        if (op == "f64x2.pmin"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::PMinVecF64x2);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'r':
+                    if (op == "f64x2.promote_low_f32x4"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::PromoteLowVecF32x4ToVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'r': {
+                switch (op[8]) {
+                  case 'l': {
+                    switch (op[14]) {
+                      case 'f': {
+                        switch (op[16]) {
+                          case 'a':
+                            if (op == "f64x2.relaxed_fma"sv) {
+                              auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::RelaxedFmaVecF64x2);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 's':
+                            if (op == "f64x2.relaxed_fms"sv) {
+                              auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::RelaxedFmsVecF64x2);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'm': {
+                        switch (op[15]) {
+                          case 'a':
+                            if (op == "f64x2.relaxed_max"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RelaxedMaxVecF64x2);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'i':
+                            if (op == "f64x2.relaxed_min"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RelaxedMinVecF64x2);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'p':
+                    if (op == "f64x2.replace_lane"sv) {
+                      auto ret = makeSIMDReplace(ctx, pos, SIMDReplaceOp::ReplaceLaneVecF64x2, 2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 's': {
+                switch (op[7]) {
+                  case 'p':
+                    if (op == "f64x2.splat"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SplatVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'q':
+                    if (op == "f64x2.sqrt"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SqrtVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "f64x2.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubVecF64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't':
+                if (op == "f64x2.trunc"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncVecF64x2);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 'g': {
+    switch (op[7]) {
+      case 'g':
+        if (op == "global.get"sv) {
+          auto ret = makeGlobalGet(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 's':
+        if (op == "global.set"sv) {
+          auto ret = makeGlobalSet(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  case 'i': {
+    switch (op[1]) {
+      case '1': {
+        switch (op[6]) {
+          case 'a': {
+            switch (op[7]) {
+              case 'b':
+                if (op == "i16x8.abs"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::AbsVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'd': {
+                switch (op[9]) {
+                  case '\0':
+                    if (op == "i16x8.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[14]) {
+                      case 's':
+                        if (op == "i16x8.add_sat_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AddSatSVecI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i16x8.add_sat_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AddSatUVecI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'l':
+                if (op == "i16x8.all_true"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::AllTrueVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'v':
+                if (op == "i16x8.avgr_u"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::AvgrUVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'b':
+            if (op == "i16x8.bitmask"sv) {
+              auto ret = makeUnary(ctx, pos, UnaryOp::BitmaskVecI16x8);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'd':
+            if (op == "i16x8.dot_i8x16_i7x16_s"sv) {
+              auto ret = makeBinary(ctx, pos, BinaryOp::DotI8x16I7x16SToVecI16x8);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'e': {
+            switch (op[7]) {
+              case 'q':
+                if (op == "i16x8.eq"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::EqVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'x': {
+                switch (op[9]) {
+                  case 'a': {
+                    switch (op[28]) {
+                      case 's':
+                        if (op == "i16x8.extadd_pairwise_i8x16_s"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ExtAddPairwiseSVecI8x16ToI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i16x8.extadd_pairwise_i8x16_u"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ExtAddPairwiseUVecI8x16ToI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'e': {
+                    switch (op[13]) {
+                      case 'h': {
+                        switch (op[24]) {
+                          case 's':
+                            if (op == "i16x8.extend_high_i8x16_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendHighSVecI8x16ToVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i16x8.extend_high_i8x16_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendHighUVecI8x16ToVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'l': {
+                        switch (op[23]) {
+                          case 's':
+                            if (op == "i16x8.extend_low_i8x16_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendLowSVecI8x16ToVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i16x8.extend_low_i8x16_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendLowUVecI8x16ToVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'm': {
+                    switch (op[13]) {
+                      case 'h': {
+                        switch (op[24]) {
+                          case 's':
+                            if (op == "i16x8.extmul_high_i8x16_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulHighSVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i16x8.extmul_high_i8x16_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulHighUVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'l': {
+                        switch (op[23]) {
+                          case 's':
+                            if (op == "i16x8.extmul_low_i8x16_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulLowSVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i16x8.extmul_low_i8x16_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulLowUVecI16x8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'r': {
+                    switch (op[19]) {
+                      case 's':
+                        if (op == "i16x8.extract_lane_s"sv) {
+                          auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneSVecI16x8, 8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i16x8.extract_lane_u"sv) {
+                          auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneUVecI16x8, 8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'g': {
+            switch (op[7]) {
+              case 'e': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i16x8.ge_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeSVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.ge_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeUVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i16x8.gt_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtSVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.gt_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtUVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'l': {
+            switch (op[7]) {
+              case 'a':
+                if (op == "i16x8.laneselect"sv) {
+                  auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::LaneselectI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'e': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i16x8.le_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeSVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.le_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeUVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i16x8.lt_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtSVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.lt_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtUVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'm': {
+            switch (op[7]) {
+              case 'a': {
+                switch (op[10]) {
+                  case 's':
+                    if (op == "i16x8.max_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxSVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.max_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxUVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'i': {
+                switch (op[10]) {
+                  case 's':
+                    if (op == "i16x8.min_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinSVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.min_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinUVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'u':
+                if (op == "i16x8.mul"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::MulVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'n': {
+            switch (op[7]) {
+              case 'a': {
+                switch (op[19]) {
+                  case 's':
+                    if (op == "i16x8.narrow_i32x4_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NarrowSVecI32x4ToVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i16x8.narrow_i32x4_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NarrowUVecI32x4ToVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'e': {
+                switch (op[8]) {
+                  case '\0':
+                    if (op == "i16x8.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "i16x8.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'q':
+            if (op == "i16x8.q15mulr_sat_s"sv) {
+              auto ret = makeBinary(ctx, pos, BinaryOp::Q15MulrSatSVecI16x8);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'r': {
+            switch (op[8]) {
+              case 'l':
+                if (op == "i16x8.relaxed_q15mulr_s"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::RelaxedQ15MulrSVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'p':
+                if (op == "i16x8.replace_lane"sv) {
+                  auto ret = makeSIMDReplace(ctx, pos, SIMDReplaceOp::ReplaceLaneVecI16x8, 8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 's': {
+            switch (op[7]) {
+              case 'h': {
+                switch (op[8]) {
+                  case 'l':
+                    if (op == "i16x8.shl"sv) {
+                      auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShlVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'r': {
+                    switch (op[10]) {
+                      case 's':
+                        if (op == "i16x8.shr_s"sv) {
+                          auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrSVecI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i16x8.shr_u"sv) {
+                          auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrUVecI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'p':
+                if (op == "i16x8.splat"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::SplatVecI16x8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'u': {
+                switch (op[9]) {
+                  case '\0':
+                    if (op == "i16x8.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubVecI16x8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[14]) {
+                      case 's':
+                        if (op == "i16x8.sub_sat_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::SubSatSVecI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i16x8.sub_sat_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::SubSatUVecI16x8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case '3': {
+        switch (op[2]) {
+          case '1': {
+            switch (op[4]) {
+              case 'g': {
+                switch (op[8]) {
+                  case 's':
+                    if (op == "i31.get_s"sv) {
+                      auto ret = makeI31Get(ctx, pos, true);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i31.get_u"sv) {
+                      auto ret = makeI31Get(ctx, pos, false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'n':
+                if (op == "i31.new"sv) {
+                  auto ret = makeI31New(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case '2': {
+            switch (op[3]) {
+              case '.': {
+                switch (op[4]) {
+                  case 'a': {
+                    switch (op[5]) {
+                      case 'd':
+                        if (op == "i32.add"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AddInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'n':
+                        if (op == "i32.and"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AndInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 't': {
+                        switch (op[11]) {
+                          case 'l': {
+                            switch (op[15]) {
+                              case '\0':
+                                if (op == "i32.atomic.load"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/false, 4, /*isAtomic=*/true);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '1':
+                                if (op == "i32.atomic.load16_u"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/false, 2, /*isAtomic=*/true);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '8':
+                                if (op == "i32.atomic.load8_u"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/false, 1, /*isAtomic=*/true);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case 'r': {
+                            switch (op[14]) {
+                              case '.': {
+                                switch (op[15]) {
+                                  case 'a': {
+                                    switch (op[16]) {
+                                      case 'd':
+                                        if (op == "i32.atomic.rmw.add"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i32, 4);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      case 'n':
+                                        if (op == "i32.atomic.rmw.and"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i32, 4);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      default: goto parse_error;
+                                    }
+                                  }
+                                  case 'c':
+                                    if (op == "i32.atomic.rmw.cmpxchg"sv) {
+                                      auto ret = makeAtomicCmpxchg(ctx, pos, Type::i32, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i32.atomic.rmw.or"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i32, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 's':
+                                    if (op == "i32.atomic.rmw.sub"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i32, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'x': {
+                                    switch (op[16]) {
+                                      case 'c':
+                                        if (op == "i32.atomic.rmw.xchg"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i32, 4);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      case 'o':
+                                        if (op == "i32.atomic.rmw.xor"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i32, 4);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      default: goto parse_error;
+                                    }
+                                  }
+                                  default: goto parse_error;
+                                }
+                              }
+                              case '1': {
+                                switch (op[17]) {
+                                  case 'a': {
+                                    switch (op[18]) {
+                                      case 'd':
+                                        if (op == "i32.atomic.rmw16.add_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i32, 2);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      case 'n':
+                                        if (op == "i32.atomic.rmw16.and_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i32, 2);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      default: goto parse_error;
+                                    }
+                                  }
+                                  case 'c':
+                                    if (op == "i32.atomic.rmw16.cmpxchg_u"sv) {
+                                      auto ret = makeAtomicCmpxchg(ctx, pos, Type::i32, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i32.atomic.rmw16.or_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i32, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 's':
+                                    if (op == "i32.atomic.rmw16.sub_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i32, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'x': {
+                                    switch (op[18]) {
+                                      case 'c':
+                                        if (op == "i32.atomic.rmw16.xchg_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i32, 2);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      case 'o':
+                                        if (op == "i32.atomic.rmw16.xor_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i32, 2);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      default: goto parse_error;
+                                    }
+                                  }
+                                  default: goto parse_error;
+                                }
+                              }
+                              case '8': {
+                                switch (op[16]) {
+                                  case 'a': {
+                                    switch (op[17]) {
+                                      case 'd':
+                                        if (op == "i32.atomic.rmw8.add_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i32, 1);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      case 'n':
+                                        if (op == "i32.atomic.rmw8.and_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i32, 1);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      default: goto parse_error;
+                                    }
+                                  }
+                                  case 'c':
+                                    if (op == "i32.atomic.rmw8.cmpxchg_u"sv) {
+                                      auto ret = makeAtomicCmpxchg(ctx, pos, Type::i32, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i32.atomic.rmw8.or_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i32, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 's':
+                                    if (op == "i32.atomic.rmw8.sub_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i32, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'x': {
+                                    switch (op[17]) {
+                                      case 'c':
+                                        if (op == "i32.atomic.rmw8.xchg_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i32, 1);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      case 'o':
+                                        if (op == "i32.atomic.rmw8.xor_u"sv) {
+                                          auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i32, 1);
+                                          CHECK_ERR(ret);
+                                          return *ret;
+                                        }
+                                        goto parse_error;
+                                      default: goto parse_error;
+                                    }
+                                  }
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          case 's': {
+                            switch (op[16]) {
+                              case '\0':
+                                if (op == "i32.atomic.store"sv) {
+                                  auto ret = makeStore(ctx, pos, Type::i32, 4, /*isAtomic=*/true);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '1':
+                                if (op == "i32.atomic.store16"sv) {
+                                  auto ret = makeStore(ctx, pos, Type::i32, 2, /*isAtomic=*/true);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '8':
+                                if (op == "i32.atomic.store8"sv) {
+                                  auto ret = makeStore(ctx, pos, Type::i32, 1, /*isAtomic=*/true);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'c': {
+                    switch (op[5]) {
+                      case 'l':
+                        if (op == "i32.clz"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ClzInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'o':
+                        if (op == "i32.const"sv) {
+                          auto ret = makeConst(ctx, pos, Type::i32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 't':
+                        if (op == "i32.ctz"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::CtzInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'd': {
+                    switch (op[8]) {
+                      case 's':
+                        if (op == "i32.div_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::DivSInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i32.div_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::DivUInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'e': {
+                    switch (op[5]) {
+                      case 'q': {
+                        switch (op[6]) {
+                          case '\0':
+                            if (op == "i32.eq"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::EqInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'z':
+                            if (op == "i32.eqz"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::EqZInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'x': {
+                        switch (op[10]) {
+                          case '1':
+                            if (op == "i32.extend16_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendS16Int32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '8':
+                            if (op == "i32.extend8_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendS8Int32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'g': {
+                    switch (op[5]) {
+                      case 'e': {
+                        switch (op[7]) {
+                          case 's':
+                            if (op == "i32.ge_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GeSInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32.ge_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GeUInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 't': {
+                        switch (op[7]) {
+                          case 's':
+                            if (op == "i32.gt_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GtSInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32.gt_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GtUInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'l': {
+                    switch (op[5]) {
+                      case 'e': {
+                        switch (op[7]) {
+                          case 's':
+                            if (op == "i32.le_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LeSInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32.le_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LeUInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'o': {
+                        switch (op[8]) {
+                          case '\0':
+                            if (op == "i32.load"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/false, 4, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '1': {
+                            switch (op[11]) {
+                              case 's':
+                                if (op == "i32.load16_s"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/true, 2, /*isAtomic=*/false);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.load16_u"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/false, 2, /*isAtomic=*/false);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case '8': {
+                            switch (op[10]) {
+                              case 's':
+                                if (op == "i32.load8_s"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/true, 1, /*isAtomic=*/false);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.load8_u"sv) {
+                                  auto ret = makeLoad(ctx, pos, Type::i32, /*signed=*/false, 1, /*isAtomic=*/false);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 't': {
+                        switch (op[7]) {
+                          case 's':
+                            if (op == "i32.lt_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LtSInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32.lt_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LtUInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'm':
+                    if (op == "i32.mul"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MulInt32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'n':
+                    if (op == "i32.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeInt32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o':
+                    if (op == "i32.or"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::OrInt32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'p':
+                    if (op == "i32.popcnt"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::PopcntInt32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'r': {
+                    switch (op[5]) {
+                      case 'e': {
+                        switch (op[6]) {
+                          case 'i':
+                            if (op == "i32.reinterpret_f32"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ReinterpretFloat32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'm': {
+                            switch (op[8]) {
+                              case 's':
+                                if (op == "i32.rem_s"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::RemSInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.rem_u"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::RemUInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'o': {
+                        switch (op[7]) {
+                          case 'l':
+                            if (op == "i32.rotl"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RotLInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'r':
+                            if (op == "i32.rotr"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RotRInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 's': {
+                    switch (op[5]) {
+                      case 'h': {
+                        switch (op[6]) {
+                          case 'l':
+                            if (op == "i32.shl"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ShlInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'r': {
+                            switch (op[8]) {
+                              case 's':
+                                if (op == "i32.shr_s"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::ShrSInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.shr_u"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::ShrUInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 't': {
+                        switch (op[9]) {
+                          case '\0':
+                            if (op == "i32.store"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i32, 4, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '1':
+                            if (op == "i32.store16"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i32, 2, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '8':
+                            if (op == "i32.store8"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i32, 1, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'u':
+                        if (op == "i32.sub"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::SubInt32);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 't': {
+                    switch (op[10]) {
+                      case 'f': {
+                        switch (op[11]) {
+                          case '3': {
+                            switch (op[14]) {
+                              case 's':
+                                if (op == "i32.trunc_f32_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncSFloat32ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.trunc_f32_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncUFloat32ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case '6': {
+                            switch (op[14]) {
+                              case 's':
+                                if (op == "i32.trunc_f64_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncSFloat64ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.trunc_f64_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncUFloat64ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 's': {
+                        switch (op[15]) {
+                          case '3': {
+                            switch (op[18]) {
+                              case 's':
+                                if (op == "i32.trunc_sat_f32_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatSFloat32ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.trunc_sat_f32_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatUFloat32ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case '6': {
+                            switch (op[18]) {
+                              case 's':
+                                if (op == "i32.trunc_sat_f64_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatSFloat64ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32.trunc_sat_f64_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatUFloat64ToInt32);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'w':
+                    if (op == "i32.wrap_i64"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::WrapInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'x':
+                    if (op == "i32.xor"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::XorInt32);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'x': {
+                switch (op[6]) {
+                  case 'a': {
+                    switch (op[7]) {
+                      case 'b':
+                        if (op == "i32x4.abs"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::AbsVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'd':
+                        if (op == "i32x4.add"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AddVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'l':
+                        if (op == "i32x4.all_true"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::AllTrueVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'b':
+                    if (op == "i32x4.bitmask"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::BitmaskVecI32x4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'd': {
+                    switch (op[11]) {
+                      case '1':
+                        if (op == "i32x4.dot_i16x8_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::DotSVecI16x8ToVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '8':
+                        if (op == "i32x4.dot_i8x16_i7x16_add_s"sv) {
+                          auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::DotI8x16I7x16AddSToVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'e': {
+                    switch (op[7]) {
+                      case 'q':
+                        if (op == "i32x4.eq"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::EqVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'x': {
+                        switch (op[9]) {
+                          case 'a': {
+                            switch (op[28]) {
+                              case 's':
+                                if (op == "i32x4.extadd_pairwise_i16x8_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::ExtAddPairwiseSVecI16x8ToI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32x4.extadd_pairwise_i16x8_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::ExtAddPairwiseUVecI16x8ToI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case 'e': {
+                            switch (op[13]) {
+                              case 'h': {
+                                switch (op[24]) {
+                                  case 's':
+                                    if (op == "i32x4.extend_high_i16x8_s"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ExtendHighSVecI16x8ToVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "i32x4.extend_high_i16x8_u"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ExtendHighUVecI16x8ToVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case 'l': {
+                                switch (op[23]) {
+                                  case 's':
+                                    if (op == "i32x4.extend_low_i16x8_s"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ExtendLowSVecI16x8ToVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "i32x4.extend_low_i16x8_u"sv) {
+                                      auto ret = makeUnary(ctx, pos, UnaryOp::ExtendLowUVecI16x8ToVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          case 'm': {
+                            switch (op[13]) {
+                              case 'h': {
+                                switch (op[24]) {
+                                  case 's':
+                                    if (op == "i32x4.extmul_high_i16x8_s"sv) {
+                                      auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulHighSVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "i32x4.extmul_high_i16x8_u"sv) {
+                                      auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulHighUVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case 'l': {
+                                switch (op[23]) {
+                                  case 's':
+                                    if (op == "i32x4.extmul_low_i16x8_s"sv) {
+                                      auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulLowSVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'u':
+                                    if (op == "i32x4.extmul_low_i16x8_u"sv) {
+                                      auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulLowUVecI32x4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          case 'r':
+                            if (op == "i32x4.extract_lane"sv) {
+                              auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneVecI32x4, 4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'g': {
+                    switch (op[7]) {
+                      case 'e': {
+                        switch (op[9]) {
+                          case 's':
+                            if (op == "i32x4.ge_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GeSVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.ge_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GeUVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 't': {
+                        switch (op[9]) {
+                          case 's':
+                            if (op == "i32x4.gt_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GtSVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.gt_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::GtUVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'l': {
+                    switch (op[7]) {
+                      case 'a':
+                        if (op == "i32x4.laneselect"sv) {
+                          auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::LaneselectI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'e': {
+                        switch (op[9]) {
+                          case 's':
+                            if (op == "i32x4.le_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LeSVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.le_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LeUVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 't': {
+                        switch (op[9]) {
+                          case 's':
+                            if (op == "i32x4.lt_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LtSVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.lt_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::LtUVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'm': {
+                    switch (op[7]) {
+                      case 'a': {
+                        switch (op[10]) {
+                          case 's':
+                            if (op == "i32x4.max_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::MaxSVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.max_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::MaxUVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'i': {
+                        switch (op[10]) {
+                          case 's':
+                            if (op == "i32x4.min_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::MinSVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.min_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::MinUVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'u':
+                        if (op == "i32x4.mul"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::MulVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'n': {
+                    switch (op[8]) {
+                      case '\0':
+                        if (op == "i32x4.ne"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::NeVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'g':
+                        if (op == "i32x4.neg"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::NegVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'r': {
+                    switch (op[8]) {
+                      case 'l': {
+                        switch (op[21]) {
+                          case '3': {
+                            switch (op[26]) {
+                              case 's':
+                                if (op == "i32x4.relaxed_trunc_f32x4_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::RelaxedTruncSVecF32x4ToVecI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32x4.relaxed_trunc_f32x4_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::RelaxedTruncUVecF32x4ToVecI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case '6': {
+                            switch (op[26]) {
+                              case 's':
+                                if (op == "i32x4.relaxed_trunc_f64x2_s_zero"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::RelaxedTruncZeroSVecF64x2ToVecI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32x4.relaxed_trunc_f64x2_u_zero"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::RelaxedTruncZeroUVecF64x2ToVecI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'p':
+                        if (op == "i32x4.replace_lane"sv) {
+                          auto ret = makeSIMDReplace(ctx, pos, SIMDReplaceOp::ReplaceLaneVecI32x4, 4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 's': {
+                    switch (op[7]) {
+                      case 'h': {
+                        switch (op[8]) {
+                          case 'l':
+                            if (op == "i32x4.shl"sv) {
+                              auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShlVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'r': {
+                            switch (op[10]) {
+                              case 's':
+                                if (op == "i32x4.shr_s"sv) {
+                                  auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrSVecI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i32x4.shr_u"sv) {
+                                  auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrUVecI32x4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'p':
+                        if (op == "i32x4.splat"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::SplatVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i32x4.sub"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::SubVecI32x4);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 't': {
+                    switch (op[17]) {
+                      case '3': {
+                        switch (op[22]) {
+                          case 's':
+                            if (op == "i32x4.trunc_sat_f32x4_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatSVecF32x4ToVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.trunc_sat_f32x4_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatUVecF32x4ToVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '6': {
+                        switch (op[22]) {
+                          case 's':
+                            if (op == "i32x4.trunc_sat_f64x2_s_zero"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatZeroSVecF64x2ToVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i32x4.trunc_sat_f64x2_u_zero"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatZeroUVecF64x2ToVecI32x4);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case '6': {
+        switch (op[3]) {
+          case '.': {
+            switch (op[4]) {
+              case 'a': {
+                switch (op[5]) {
+                  case 'd':
+                    if (op == "i64.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'n':
+                    if (op == "i64.and"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AndInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't': {
+                    switch (op[11]) {
+                      case 'l': {
+                        switch (op[15]) {
+                          case '\0':
+                            if (op == "i64.atomic.load"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 8, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '1':
+                            if (op == "i64.atomic.load16_u"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 2, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '3':
+                            if (op == "i64.atomic.load32_u"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 4, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '8':
+                            if (op == "i64.atomic.load8_u"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 1, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'r': {
+                        switch (op[14]) {
+                          case '.': {
+                            switch (op[15]) {
+                              case 'a': {
+                                switch (op[16]) {
+                                  case 'd':
+                                    if (op == "i64.atomic.rmw.add"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i64, 8);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'n':
+                                    if (op == "i64.atomic.rmw.and"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i64, 8);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case 'c':
+                                if (op == "i64.atomic.rmw.cmpxchg"sv) {
+                                  auto ret = makeAtomicCmpxchg(ctx, pos, Type::i64, 8);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'o':
+                                if (op == "i64.atomic.rmw.or"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i64, 8);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 's':
+                                if (op == "i64.atomic.rmw.sub"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i64, 8);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'x': {
+                                switch (op[16]) {
+                                  case 'c':
+                                    if (op == "i64.atomic.rmw.xchg"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i64, 8);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i64.atomic.rmw.xor"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i64, 8);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          case '1': {
+                            switch (op[17]) {
+                              case 'a': {
+                                switch (op[18]) {
+                                  case 'd':
+                                    if (op == "i64.atomic.rmw16.add_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i64, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'n':
+                                    if (op == "i64.atomic.rmw16.and_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i64, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case 'c':
+                                if (op == "i64.atomic.rmw16.cmpxchg_u"sv) {
+                                  auto ret = makeAtomicCmpxchg(ctx, pos, Type::i64, 2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'o':
+                                if (op == "i64.atomic.rmw16.or_u"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i64, 2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 's':
+                                if (op == "i64.atomic.rmw16.sub_u"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i64, 2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'x': {
+                                switch (op[18]) {
+                                  case 'c':
+                                    if (op == "i64.atomic.rmw16.xchg_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i64, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i64.atomic.rmw16.xor_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i64, 2);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          case '3': {
+                            switch (op[17]) {
+                              case 'a': {
+                                switch (op[18]) {
+                                  case 'd':
+                                    if (op == "i64.atomic.rmw32.add_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i64, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'n':
+                                    if (op == "i64.atomic.rmw32.and_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i64, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case 'c':
+                                if (op == "i64.atomic.rmw32.cmpxchg_u"sv) {
+                                  auto ret = makeAtomicCmpxchg(ctx, pos, Type::i64, 4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'o':
+                                if (op == "i64.atomic.rmw32.or_u"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i64, 4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 's':
+                                if (op == "i64.atomic.rmw32.sub_u"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i64, 4);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'x': {
+                                switch (op[18]) {
+                                  case 'c':
+                                    if (op == "i64.atomic.rmw32.xchg_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i64, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i64.atomic.rmw32.xor_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i64, 4);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          case '8': {
+                            switch (op[16]) {
+                              case 'a': {
+                                switch (op[17]) {
+                                  case 'd':
+                                    if (op == "i64.atomic.rmw8.add_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAdd, Type::i64, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'n':
+                                    if (op == "i64.atomic.rmw8.and_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWAnd, Type::i64, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              case 'c':
+                                if (op == "i64.atomic.rmw8.cmpxchg_u"sv) {
+                                  auto ret = makeAtomicCmpxchg(ctx, pos, Type::i64, 1);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'o':
+                                if (op == "i64.atomic.rmw8.or_u"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWOr, Type::i64, 1);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 's':
+                                if (op == "i64.atomic.rmw8.sub_u"sv) {
+                                  auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWSub, Type::i64, 1);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'x': {
+                                switch (op[17]) {
+                                  case 'c':
+                                    if (op == "i64.atomic.rmw8.xchg_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXchg, Type::i64, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  case 'o':
+                                    if (op == "i64.atomic.rmw8.xor_u"sv) {
+                                      auto ret = makeAtomicRMW(ctx, pos, AtomicRMWOp::RMWXor, Type::i64, 1);
+                                      CHECK_ERR(ret);
+                                      return *ret;
+                                    }
+                                    goto parse_error;
+                                  default: goto parse_error;
+                                }
+                              }
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 's': {
+                        switch (op[16]) {
+                          case '\0':
+                            if (op == "i64.atomic.store"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i64, 8, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '1':
+                            if (op == "i64.atomic.store16"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i64, 2, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '3':
+                            if (op == "i64.atomic.store32"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i64, 4, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '8':
+                            if (op == "i64.atomic.store8"sv) {
+                              auto ret = makeStore(ctx, pos, Type::i64, 1, /*isAtomic=*/true);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'c': {
+                switch (op[5]) {
+                  case 'l':
+                    if (op == "i64.clz"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::ClzInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'o':
+                    if (op == "i64.const"sv) {
+                      auto ret = makeConst(ctx, pos, Type::i64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "i64.ctz"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::CtzInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'd': {
+                switch (op[8]) {
+                  case 's':
+                    if (op == "i64.div_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::DivSInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i64.div_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::DivUInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'e': {
+                switch (op[5]) {
+                  case 'q': {
+                    switch (op[6]) {
+                      case '\0':
+                        if (op == "i64.eq"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::EqInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'z':
+                        if (op == "i64.eqz"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::EqZInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'x': {
+                    switch (op[10]) {
+                      case '1':
+                        if (op == "i64.extend16_s"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ExtendS16Int64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '3':
+                        if (op == "i64.extend32_s"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ExtendS32Int64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '8':
+                        if (op == "i64.extend8_s"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ExtendS8Int64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '_': {
+                        switch (op[15]) {
+                          case 's':
+                            if (op == "i64.extend_i32_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendSInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.extend_i32_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::ExtendUInt32);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'g': {
+                switch (op[5]) {
+                  case 'e': {
+                    switch (op[7]) {
+                      case 's':
+                        if (op == "i64.ge_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::GeSInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i64.ge_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::GeUInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 't': {
+                    switch (op[7]) {
+                      case 's':
+                        if (op == "i64.gt_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::GtSInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i64.gt_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::GtUInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'l': {
+                switch (op[5]) {
+                  case 'e': {
+                    switch (op[7]) {
+                      case 's':
+                        if (op == "i64.le_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::LeSInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i64.le_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::LeUInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'o': {
+                    switch (op[8]) {
+                      case '\0':
+                        if (op == "i64.load"sv) {
+                          auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 8, /*isAtomic=*/false);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '1': {
+                        switch (op[11]) {
+                          case 's':
+                            if (op == "i64.load16_s"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/true, 2, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.load16_u"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 2, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '3': {
+                        switch (op[11]) {
+                          case 's':
+                            if (op == "i64.load32_s"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/true, 4, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.load32_u"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 4, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '8': {
+                        switch (op[10]) {
+                          case 's':
+                            if (op == "i64.load8_s"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/true, 1, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.load8_u"sv) {
+                              auto ret = makeLoad(ctx, pos, Type::i64, /*signed=*/false, 1, /*isAtomic=*/false);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 't': {
+                    switch (op[7]) {
+                      case 's':
+                        if (op == "i64.lt_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::LtSInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i64.lt_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::LtUInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'm':
+                if (op == "i64.mul"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::MulInt64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'n':
+                if (op == "i64.ne"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::NeInt64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'o':
+                if (op == "i64.or"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::OrInt64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'p':
+                if (op == "i64.popcnt"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::PopcntInt64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'r': {
+                switch (op[5]) {
+                  case 'e': {
+                    switch (op[6]) {
+                      case 'i':
+                        if (op == "i64.reinterpret_f64"sv) {
+                          auto ret = makeUnary(ctx, pos, UnaryOp::ReinterpretFloat64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'm': {
+                        switch (op[8]) {
+                          case 's':
+                            if (op == "i64.rem_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RemSInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.rem_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::RemUInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'o': {
+                    switch (op[7]) {
+                      case 'l':
+                        if (op == "i64.rotl"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::RotLInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'r':
+                        if (op == "i64.rotr"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::RotRInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 's': {
+                switch (op[5]) {
+                  case 'h': {
+                    switch (op[6]) {
+                      case 'l':
+                        if (op == "i64.shl"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::ShlInt64);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'r': {
+                        switch (op[8]) {
+                          case 's':
+                            if (op == "i64.shr_s"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ShrSInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.shr_u"sv) {
+                              auto ret = makeBinary(ctx, pos, BinaryOp::ShrUInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 't': {
+                    switch (op[9]) {
+                      case '\0':
+                        if (op == "i64.store"sv) {
+                          auto ret = makeStore(ctx, pos, Type::i64, 8, /*isAtomic=*/false);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '1':
+                        if (op == "i64.store16"sv) {
+                          auto ret = makeStore(ctx, pos, Type::i64, 2, /*isAtomic=*/false);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '3':
+                        if (op == "i64.store32"sv) {
+                          auto ret = makeStore(ctx, pos, Type::i64, 4, /*isAtomic=*/false);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '8':
+                        if (op == "i64.store8"sv) {
+                          auto ret = makeStore(ctx, pos, Type::i64, 1, /*isAtomic=*/false);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'u':
+                    if (op == "i64.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubInt64);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't': {
+                switch (op[10]) {
+                  case 'f': {
+                    switch (op[11]) {
+                      case '3': {
+                        switch (op[14]) {
+                          case 's':
+                            if (op == "i64.trunc_f32_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSFloat32ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.trunc_f32_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncUFloat32ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '6': {
+                        switch (op[14]) {
+                          case 's':
+                            if (op == "i64.trunc_f64_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSFloat64ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.trunc_f64_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncUFloat64ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 's': {
+                    switch (op[15]) {
+                      case '3': {
+                        switch (op[18]) {
+                          case 's':
+                            if (op == "i64.trunc_sat_f32_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatSFloat32ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.trunc_sat_f32_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatUFloat32ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '6': {
+                        switch (op[18]) {
+                          case 's':
+                            if (op == "i64.trunc_sat_f64_s"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatSFloat64ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64.trunc_sat_f64_u"sv) {
+                              auto ret = makeUnary(ctx, pos, UnaryOp::TruncSatUFloat64ToInt64);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'x':
+                if (op == "i64.xor"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::XorInt64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'x': {
+            switch (op[6]) {
+              case 'a': {
+                switch (op[7]) {
+                  case 'b':
+                    if (op == "i64x2.abs"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::AbsVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'd':
+                    if (op == "i64x2.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'l':
+                    if (op == "i64x2.all_true"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::AllTrueVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'b':
+                if (op == "i64x2.bitmask"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::BitmaskVecI64x2);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'e': {
+                switch (op[7]) {
+                  case 'q':
+                    if (op == "i64x2.eq"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::EqVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'x': {
+                    switch (op[9]) {
+                      case 'e': {
+                        switch (op[13]) {
+                          case 'h': {
+                            switch (op[24]) {
+                              case 's':
+                                if (op == "i64x2.extend_high_i32x4_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::ExtendHighSVecI32x4ToVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i64x2.extend_high_i32x4_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::ExtendHighUVecI32x4ToVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case 'l': {
+                            switch (op[23]) {
+                              case 's':
+                                if (op == "i64x2.extend_low_i32x4_s"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::ExtendLowSVecI32x4ToVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i64x2.extend_low_i32x4_u"sv) {
+                                  auto ret = makeUnary(ctx, pos, UnaryOp::ExtendLowUVecI32x4ToVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'm': {
+                        switch (op[13]) {
+                          case 'h': {
+                            switch (op[24]) {
+                              case 's':
+                                if (op == "i64x2.extmul_high_i32x4_s"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulHighSVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i64x2.extmul_high_i32x4_u"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulHighUVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case 'l': {
+                            switch (op[23]) {
+                              case 's':
+                                if (op == "i64x2.extmul_low_i32x4_s"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulLowSVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case 'u':
+                                if (op == "i64x2.extmul_low_i32x4_u"sv) {
+                                  auto ret = makeBinary(ctx, pos, BinaryOp::ExtMulLowUVecI64x2);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'r':
+                        if (op == "i64x2.extract_lane"sv) {
+                          auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneVecI64x2, 2);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'g': {
+                switch (op[7]) {
+                  case 'e':
+                    if (op == "i64x2.ge_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeSVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "i64x2.gt_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtSVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'l': {
+                switch (op[7]) {
+                  case 'a':
+                    if (op == "i64x2.laneselect"sv) {
+                      auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::LaneselectI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'e':
+                    if (op == "i64x2.le_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeSVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 't':
+                    if (op == "i64x2.lt_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtSVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'm':
+                if (op == "i64x2.mul"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::MulVecI64x2);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'n': {
+                switch (op[8]) {
+                  case '\0':
+                    if (op == "i64x2.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "i64x2.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'r':
+                if (op == "i64x2.replace_lane"sv) {
+                  auto ret = makeSIMDReplace(ctx, pos, SIMDReplaceOp::ReplaceLaneVecI64x2, 2);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 's': {
+                switch (op[7]) {
+                  case 'h': {
+                    switch (op[8]) {
+                      case 'l':
+                        if (op == "i64x2.shl"sv) {
+                          auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShlVecI64x2);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'r': {
+                        switch (op[10]) {
+                          case 's':
+                            if (op == "i64x2.shr_s"sv) {
+                              auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrSVecI64x2);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'u':
+                            if (op == "i64x2.shr_u"sv) {
+                              auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrUVecI64x2);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'p':
+                    if (op == "i64x2.splat"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::SplatVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i64x2.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubVecI64x2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case '8': {
+        switch (op[6]) {
+          case 'a': {
+            switch (op[7]) {
+              case 'b':
+                if (op == "i8x16.abs"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::AbsVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'd': {
+                switch (op[9]) {
+                  case '\0':
+                    if (op == "i8x16.add"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::AddVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[14]) {
+                      case 's':
+                        if (op == "i8x16.add_sat_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AddSatSVecI8x16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i8x16.add_sat_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::AddSatUVecI8x16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'l':
+                if (op == "i8x16.all_true"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::AllTrueVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'v':
+                if (op == "i8x16.avgr_u"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::AvgrUVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'b':
+            if (op == "i8x16.bitmask"sv) {
+              auto ret = makeUnary(ctx, pos, UnaryOp::BitmaskVecI8x16);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'e': {
+            switch (op[7]) {
+              case 'q':
+                if (op == "i8x16.eq"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::EqVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'x': {
+                switch (op[19]) {
+                  case 's':
+                    if (op == "i8x16.extract_lane_s"sv) {
+                      auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneSVecI8x16, 16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.extract_lane_u"sv) {
+                      auto ret = makeSIMDExtract(ctx, pos, SIMDExtractOp::ExtractLaneUVecI8x16, 16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'g': {
+            switch (op[7]) {
+              case 'e': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i8x16.ge_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeSVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.ge_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GeUVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i8x16.gt_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtSVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.gt_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::GtUVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'l': {
+            switch (op[7]) {
+              case 'a':
+                if (op == "i8x16.laneselect"sv) {
+                  auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::LaneselectI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'e': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i8x16.le_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeSVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.le_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LeUVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 't': {
+                switch (op[9]) {
+                  case 's':
+                    if (op == "i8x16.lt_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtSVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.lt_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::LtUVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'm': {
+            switch (op[7]) {
+              case 'a': {
+                switch (op[10]) {
+                  case 's':
+                    if (op == "i8x16.max_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxSVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.max_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MaxUVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'i': {
+                switch (op[10]) {
+                  case 's':
+                    if (op == "i8x16.min_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinSVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.min_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::MinUVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'n': {
+            switch (op[7]) {
+              case 'a': {
+                switch (op[19]) {
+                  case 's':
+                    if (op == "i8x16.narrow_i16x8_s"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NarrowSVecI16x8ToVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "i8x16.narrow_i16x8_u"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NarrowUVecI16x8ToVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'e': {
+                switch (op[8]) {
+                  case '\0':
+                    if (op == "i8x16.ne"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::NeVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'g':
+                    if (op == "i8x16.neg"sv) {
+                      auto ret = makeUnary(ctx, pos, UnaryOp::NegVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'p':
+            if (op == "i8x16.popcnt"sv) {
+              auto ret = makeUnary(ctx, pos, UnaryOp::PopcntVecI8x16);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'r': {
+            switch (op[8]) {
+              case 'l':
+                if (op == "i8x16.relaxed_swizzle"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::RelaxedSwizzleVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'p':
+                if (op == "i8x16.replace_lane"sv) {
+                  auto ret = makeSIMDReplace(ctx, pos, SIMDReplaceOp::ReplaceLaneVecI8x16, 16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 's': {
+            switch (op[7]) {
+              case 'h': {
+                switch (op[8]) {
+                  case 'l':
+                    if (op == "i8x16.shl"sv) {
+                      auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShlVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'r': {
+                    switch (op[10]) {
+                      case 's':
+                        if (op == "i8x16.shr_s"sv) {
+                          auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrSVecI8x16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i8x16.shr_u"sv) {
+                          auto ret = makeSIMDShift(ctx, pos, SIMDShiftOp::ShrUVecI8x16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'u':
+                    if (op == "i8x16.shuffle"sv) {
+                      auto ret = makeSIMDShuffle(ctx, pos);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'p':
+                if (op == "i8x16.splat"sv) {
+                  auto ret = makeUnary(ctx, pos, UnaryOp::SplatVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'u': {
+                switch (op[9]) {
+                  case '\0':
+                    if (op == "i8x16.sub"sv) {
+                      auto ret = makeBinary(ctx, pos, BinaryOp::SubVecI8x16);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[14]) {
+                      case 's':
+                        if (op == "i8x16.sub_sat_s"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::SubSatSVecI8x16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "i8x16.sub_sat_u"sv) {
+                          auto ret = makeBinary(ctx, pos, BinaryOp::SubSatUVecI8x16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'w':
+                if (op == "i8x16.swizzle"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::SwizzleVecI8x16);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case 'f':
+        if (op == "if"sv) {
+          auto ret = makeIf(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  case 'l': {
+    switch (op[2]) {
+      case 'c': {
+        switch (op[6]) {
+          case 'g':
+            if (op == "local.get"sv) {
+              auto ret = makeLocalGet(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 's':
+            if (op == "local.set"sv) {
+              auto ret = makeLocalSet(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 't':
+            if (op == "local.tee"sv) {
+              auto ret = makeLocalTee(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 'o':
+        if (op == "loop"sv) {
+          auto ret = makeLoop(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  case 'm': {
+    switch (op[7]) {
+      case 'a': {
+        switch (op[14]) {
+          case 'n':
+            if (op == "memory.atomic.notify"sv) {
+              auto ret = makeAtomicNotify(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'w': {
+            switch (op[18]) {
+              case '3':
+                if (op == "memory.atomic.wait32"sv) {
+                  auto ret = makeAtomicWait(ctx, pos, Type::i32);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case '6':
+                if (op == "memory.atomic.wait64"sv) {
+                  auto ret = makeAtomicWait(ctx, pos, Type::i64);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case 'c':
+        if (op == "memory.copy"sv) {
+          auto ret = makeMemoryCopy(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'f':
+        if (op == "memory.fill"sv) {
+          auto ret = makeMemoryFill(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'g':
+        if (op == "memory.grow"sv) {
+          auto ret = makeMemoryGrow(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'i':
+        if (op == "memory.init"sv) {
+          auto ret = makeMemoryInit(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 's':
+        if (op == "memory.size"sv) {
+          auto ret = makeMemorySize(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  case 'n':
+    if (op == "nop"sv) {
+      auto ret = makeNop(ctx, pos);
+      CHECK_ERR(ret);
+      return *ret;
+    }
+    goto parse_error;
+  case 'p':
+    if (op == "pop"sv) {
+      auto ret = makePop(ctx, pos);
+      CHECK_ERR(ret);
+      return *ret;
+    }
+    goto parse_error;
+  case 'r': {
+    switch (op[2]) {
+      case 'f': {
+        switch (op[4]) {
+          case 'a': {
+            switch (op[7]) {
+              case 'd':
+                if (op == "ref.as_data"sv) {
+                  auto ret = makeRefAs(ctx, pos, RefAsData);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'f':
+                if (op == "ref.as_func"sv) {
+                  auto ret = makeRefAs(ctx, pos, RefAsFunc);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'i':
+                if (op == "ref.as_i31"sv) {
+                  auto ret = makeRefAs(ctx, pos, RefAsI31);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'n':
+                if (op == "ref.as_non_null"sv) {
+                  auto ret = makeRefAs(ctx, pos, RefAsNonNull);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'c': {
+            switch (op[9]) {
+              case 'n':
+                if (op == "ref.cast_nop_static"sv) {
+                  auto ret = makeRefCastNopStatic(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 's':
+                if (op == "ref.cast_static"sv) {
+                  auto ret = makeRefCastStatic(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'e':
+            if (op == "ref.eq"sv) {
+              auto ret = makeRefEq(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'f':
+            if (op == "ref.func"sv) {
+              auto ret = makeRefFunc(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'i': {
+            switch (op[7]) {
+              case 'd':
+                if (op == "ref.is_data"sv) {
+                  auto ret = makeRefIs(ctx, pos, RefIsData);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'f':
+                if (op == "ref.is_func"sv) {
+                  auto ret = makeRefIs(ctx, pos, RefIsFunc);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'i':
+                if (op == "ref.is_i31"sv) {
+                  auto ret = makeRefIs(ctx, pos, RefIsI31);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'n':
+                if (op == "ref.is_null"sv) {
+                  auto ret = makeRefIs(ctx, pos, RefIsNull);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'n':
+            if (op == "ref.null"sv) {
+              auto ret = makeRefNull(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 't':
+            if (op == "ref.test_static"sv) {
+              auto ret = makeRefTestStatic(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 't': {
+        switch (op[3]) {
+          case 'h':
+            if (op == "rethrow"sv) {
+              auto ret = makeRethrow(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'u': {
+            switch (op[6]) {
+              case '\0':
+                if (op == "return"sv) {
+                  auto ret = makeReturn(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case '_': {
+                switch (op[11]) {
+                  case '\0':
+                    if (op == "return_call"sv) {
+                      auto ret = makeCall(ctx, pos, /*isReturn=*/true);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[12]) {
+                      case 'i':
+                        if (op == "return_call_indirect"sv) {
+                          auto ret = makeCallIndirect(ctx, pos, /*isReturn=*/true);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'r':
+                        if (op == "return_call_ref"sv) {
+                          auto ret = makeCallRef(ctx, pos, /*isReturn=*/true);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 's': {
+    switch (op[1]) {
+      case 'e':
+        if (op == "select"sv) {
+          auto ret = makeSelect(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 't': {
+        switch (op[3]) {
+          case 'i': {
+            switch (op[6]) {
+              case '.': {
+                switch (op[7]) {
+                  case 'a': {
+                    switch (op[10]) {
+                      case 'i':
+                        if (op == "string.as_iter"sv) {
+                          auto ret = makeStringAs(ctx, pos, StringAsIter);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'w': {
+                        switch (op[13]) {
+                          case '1':
+                            if (op == "string.as_wtf16"sv) {
+                              auto ret = makeStringAs(ctx, pos, StringAsWTF16);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '8':
+                            if (op == "string.as_wtf8"sv) {
+                              auto ret = makeStringAs(ctx, pos, StringAsWTF8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'c': {
+                    switch (op[10]) {
+                      case 'c':
+                        if (op == "string.concat"sv) {
+                          auto ret = makeStringConcat(ctx, pos);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 's':
+                        if (op == "string.const"sv) {
+                          auto ret = makeStringConst(ctx, pos);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'e': {
+                    switch (op[8]) {
+                      case 'n': {
+                        switch (op[17]) {
+                          case '1': {
+                            switch (op[19]) {
+                              case '\0':
+                                if (op == "string.encode_wtf16"sv) {
+                                  auto ret = makeStringEncode(ctx, pos, StringEncodeWTF16);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '_':
+                                if (op == "string.encode_wtf16_array"sv) {
+                                  auto ret = makeStringEncode(ctx, pos, StringEncodeWTF16Array);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          case '8': {
+                            switch (op[18]) {
+                              case '\0':
+                                if (op == "string.encode_wtf8"sv) {
+                                  auto ret = makeStringEncode(ctx, pos, StringEncodeWTF8);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              case '_':
+                                if (op == "string.encode_wtf8_array"sv) {
+                                  auto ret = makeStringEncode(ctx, pos, StringEncodeWTF8Array);
+                                  CHECK_ERR(ret);
+                                  return *ret;
+                                }
+                                goto parse_error;
+                              default: goto parse_error;
+                            }
+                          }
+                          default: goto parse_error;
+                        }
+                      }
+                      case 'q':
+                        if (op == "string.eq"sv) {
+                          auto ret = makeStringEq(ctx, pos);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'i':
+                    if (op == "string.is_usv_sequence"sv) {
+                      auto ret = makeStringMeasure(ctx, pos, StringMeasureIsUSV);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'm': {
+                    switch (op[18]) {
+                      case '1':
+                        if (op == "string.measure_wtf16"sv) {
+                          auto ret = makeStringMeasure(ctx, pos, StringMeasureWTF16);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case '8':
+                        if (op == "string.measure_wtf8"sv) {
+                          auto ret = makeStringMeasure(ctx, pos, StringMeasureWTF8);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'n': {
+                    switch (op[14]) {
+                      case '1': {
+                        switch (op[16]) {
+                          case '\0':
+                            if (op == "string.new_wtf16"sv) {
+                              auto ret = makeStringNew(ctx, pos, StringNewWTF16);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '_':
+                            if (op == "string.new_wtf16_array"sv) {
+                              auto ret = makeStringNew(ctx, pos, StringNewWTF16Array);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '8': {
+                        switch (op[15]) {
+                          case '\0':
+                            if (op == "string.new_wtf8"sv) {
+                              auto ret = makeStringNew(ctx, pos, StringNewWTF8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case '_':
+                            if (op == "string.new_wtf8_array"sv) {
+                              auto ret = makeStringNew(ctx, pos, StringNewWTF8Array);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'v': {
+                switch (op[11]) {
+                  case 'i': {
+                    switch (op[16]) {
+                      case 'a':
+                        if (op == "stringview_iter.advance"sv) {
+                          auto ret = makeStringIterMove(ctx, pos, StringIterMoveAdvance);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'n':
+                        if (op == "stringview_iter.next"sv) {
+                          auto ret = makeStringIterNext(ctx, pos);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'r':
+                        if (op == "stringview_iter.rewind"sv) {
+                          auto ret = makeStringIterMove(ctx, pos, StringIterMoveRewind);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 's':
+                        if (op == "stringview_iter.slice"sv) {
+                          auto ret = makeStringSliceIter(ctx, pos);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  case 'w': {
+                    switch (op[14]) {
+                      case '1': {
+                        switch (op[17]) {
+                          case 'g':
+                            if (op == "stringview_wtf16.get_codeunit"sv) {
+                              auto ret = makeStringWTF16Get(ctx, pos);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 'l':
+                            if (op == "stringview_wtf16.length"sv) {
+                              auto ret = makeStringMeasure(ctx, pos, StringMeasureWTF16View);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 's':
+                            if (op == "stringview_wtf16.slice"sv) {
+                              auto ret = makeStringSliceWTF(ctx, pos, StringSliceWTF16);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      case '8': {
+                        switch (op[16]) {
+                          case 'a':
+                            if (op == "stringview_wtf8.advance"sv) {
+                              auto ret = makeStringWTF8Advance(ctx, pos);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          case 's':
+                            if (op == "stringview_wtf8.slice"sv) {
+                              auto ret = makeStringSliceWTF(ctx, pos, StringSliceWTF8);
+                              CHECK_ERR(ret);
+                              return *ret;
+                            }
+                            goto parse_error;
+                          default: goto parse_error;
+                        }
+                      }
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case 'u': {
+            switch (op[7]) {
+              case 'g': {
+                switch (op[10]) {
+                  case '\0':
+                    if (op == "struct.get"sv) {
+                      auto ret = makeStructGet(ctx, pos);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (op[11]) {
+                      case 's':
+                        if (op == "struct.get_s"sv) {
+                          auto ret = makeStructGet(ctx, pos, true);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      case 'u':
+                        if (op == "struct.get_u"sv) {
+                          auto ret = makeStructGet(ctx, pos, false);
+                          CHECK_ERR(ret);
+                          return *ret;
+                        }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
+                  default: goto parse_error;
+                }
+              }
+              case 'n': {
+                switch (op[10]) {
+                  case '\0':
+                    if (op == "struct.new"sv) {
+                      auto ret = makeStructNewStatic(ctx, pos, false);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case '_':
+                    if (op == "struct.new_default"sv) {
+                      auto ret = makeStructNewStatic(ctx, pos, true);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 's':
+                if (op == "struct.set"sv) {
+                  auto ret = makeStructSet(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 't': {
+    switch (op[1]) {
+      case 'a': {
+        switch (op[6]) {
+          case 'g': {
+            switch (op[7]) {
+              case 'e':
+                if (op == "table.get"sv) {
+                  auto ret = makeTableGet(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'r':
+                if (op == "table.grow"sv) {
+                  auto ret = makeTableGrow(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 's': {
+            switch (op[7]) {
+              case 'e':
+                if (op == "table.set"sv) {
+                  auto ret = makeTableSet(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'i':
+                if (op == "table.size"sv) {
+                  auto ret = makeTableSize(ctx, pos);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case 'h': {
+        switch (op[2]) {
+          case 'e':
+            if (op == "then"sv) {
+              auto ret = makeThenOrElse(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'r':
+            if (op == "throw"sv) {
+              auto ret = makeThrow(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 'r':
+        if (op == "try"sv) {
+          auto ret = makeTry(ctx, pos);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'u': {
+        switch (op[6]) {
+          case 'e':
+            if (op == "tuple.extract"sv) {
+              auto ret = makeTupleExtract(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case 'm':
+            if (op == "tuple.make"sv) {
+              auto ret = makeTupleMake(ctx, pos);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      default: goto parse_error;
+    }
+  }
+  case 'u':
+    if (op == "unreachable"sv) {
+      auto ret = makeUnreachable(ctx, pos);
+      CHECK_ERR(ret);
+      return *ret;
+    }
+    goto parse_error;
+  case 'v': {
+    switch (op[5]) {
+      case 'a': {
+        switch (op[7]) {
+          case 'd': {
+            switch (op[8]) {
+              case '\0':
+                if (op == "v128.and"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::AndVec128);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'n':
+                if (op == "v128.andnot"sv) {
+                  auto ret = makeBinary(ctx, pos, BinaryOp::AndNotVec128);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case 'y':
+            if (op == "v128.any_true"sv) {
+              auto ret = makeUnary(ctx, pos, UnaryOp::AnyTrueVec128);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 'b':
+        if (op == "v128.bitselect"sv) {
+          auto ret = makeSIMDTernary(ctx, pos, SIMDTernaryOp::Bitselect);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'c':
+        if (op == "v128.const"sv) {
+          auto ret = makeConst(ctx, pos, Type::v128);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'l': {
+        switch (op[9]) {
+          case '\0':
+            if (op == "v128.load"sv) {
+              auto ret = makeLoad(ctx, pos, Type::v128, /*signed=*/false, 16, /*isAtomic=*/false);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case '1': {
+            switch (op[11]) {
+              case '_': {
+                switch (op[12]) {
+                  case 'l':
+                    if (op == "v128.load16_lane"sv) {
+                      auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Load16LaneVec128, 2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 's':
+                    if (op == "v128.load16_splat"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load16SplatVec128, 2);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'x': {
+                switch (op[14]) {
+                  case 's':
+                    if (op == "v128.load16x4_s"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load16x4SVec128, 8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "v128.load16x4_u"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load16x4UVec128, 8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case '3': {
+            switch (op[11]) {
+              case '_': {
+                switch (op[12]) {
+                  case 'l':
+                    if (op == "v128.load32_lane"sv) {
+                      auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Load32LaneVec128, 4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 's':
+                    if (op == "v128.load32_splat"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load32SplatVec128, 4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'z':
+                    if (op == "v128.load32_zero"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load32ZeroVec128, 4);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'x': {
+                switch (op[14]) {
+                  case 's':
+                    if (op == "v128.load32x2_s"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load32x2SVec128, 8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "v128.load32x2_u"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load32x2UVec128, 8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          case '6': {
+            switch (op[12]) {
+              case 'l':
+                if (op == "v128.load64_lane"sv) {
+                  auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Load64LaneVec128, 8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 's':
+                if (op == "v128.load64_splat"sv) {
+                  auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load64SplatVec128, 8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              case 'z':
+                if (op == "v128.load64_zero"sv) {
+                  auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load64ZeroVec128, 8);
+                  CHECK_ERR(ret);
+                  return *ret;
+                }
+                goto parse_error;
+              default: goto parse_error;
+            }
+          }
+          case '8': {
+            switch (op[10]) {
+              case '_': {
+                switch (op[11]) {
+                  case 'l':
+                    if (op == "v128.load8_lane"sv) {
+                      auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Load8LaneVec128, 1);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 's':
+                    if (op == "v128.load8_splat"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load8SplatVec128, 1);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              case 'x': {
+                switch (op[13]) {
+                  case 's':
+                    if (op == "v128.load8x8_s"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load8x8SVec128, 8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  case 'u':
+                    if (op == "v128.load8x8_u"sv) {
+                      auto ret = makeSIMDLoad(ctx, pos, SIMDLoadOp::Load8x8UVec128, 8);
+                      CHECK_ERR(ret);
+                      return *ret;
+                    }
+                    goto parse_error;
+                  default: goto parse_error;
+                }
+              }
+              default: goto parse_error;
+            }
+          }
+          default: goto parse_error;
+        }
+      }
+      case 'n':
+        if (op == "v128.not"sv) {
+          auto ret = makeUnary(ctx, pos, UnaryOp::NotVec128);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 'o':
+        if (op == "v128.or"sv) {
+          auto ret = makeBinary(ctx, pos, BinaryOp::OrVec128);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      case 's': {
+        switch (op[10]) {
+          case '\0':
+            if (op == "v128.store"sv) {
+              auto ret = makeStore(ctx, pos, Type::v128, 16, /*isAtomic=*/false);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case '1':
+            if (op == "v128.store16_lane"sv) {
+              auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Store16LaneVec128, 2);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case '3':
+            if (op == "v128.store32_lane"sv) {
+              auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Store32LaneVec128, 4);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case '6':
+            if (op == "v128.store64_lane"sv) {
+              auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Store64LaneVec128, 8);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          case '8':
+            if (op == "v128.store8_lane"sv) {
+              auto ret = makeSIMDLoadStoreLane(ctx, pos, SIMDLoadStoreLaneOp::Store8LaneVec128, 1);
+              CHECK_ERR(ret);
+              return *ret;
+            }
+            goto parse_error;
+          default: goto parse_error;
+        }
+      }
+      case 'x':
+        if (op == "v128.xor"sv) {
+          auto ret = makeBinary(ctx, pos, BinaryOp::XorVec128);
+          CHECK_ERR(ret);
+          return *ret;
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
+  default: goto parse_error;
+}
+parse_error:
+  return ctx.in.err("unrecognized instruction");
+#endif // NEW_INSTRUCTION_PARSER
+
 // clang-format on
diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt
index bdf75bb..2dadb74 100644
--- a/src/ir/CMakeLists.txt
+++ b/src/ir/CMakeLists.txt
@@ -2,14 +2,17 @@ FILE(GLOB ir_HEADERS *.h)
 set(ir_SOURCES
   ExpressionAnalyzer.cpp
   ExpressionManipulator.cpp
+  drop.cpp
   eh-utils.cpp
   intrinsics.cpp
   lubs.cpp
   memory-utils.cpp
   module-utils.cpp
   names.cpp
+  possible-contents.cpp
   properties.cpp
   LocalGraph.cpp
+  LocalStructuralDominance.cpp
   ReFinalize.cpp
   stack-utils.cpp
   table-utils.cpp
diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp
index acd446a..924040e 100644
--- a/src/ir/ExpressionAnalyzer.cpp
+++ b/src/ir/ExpressionAnalyzer.cpp
@@ -364,7 +364,7 @@ struct Hasher {
     rehash(digest, 2);
     rehash(digest, internalNames[curr]);
   }
-  void visitNonScopeName(Name curr) { rehash(digest, uint64_t(curr.str)); }
+  void visitNonScopeName(Name curr) { rehash(digest, curr); }
   void visitType(Type curr) { rehash(digest, curr.getID()); }
   void visitHeapType(HeapType curr) { rehash(digest, curr.getID()); }
   void visitAddress(Address curr) { rehash(digest, curr.addr); }
diff --git a/src/ir/LocalGraph.cpp b/src/ir/LocalGraph.cpp
index 2b26ee8..8e7d2cc 100644
--- a/src/ir/LocalGraph.cpp
+++ b/src/ir/LocalGraph.cpp
@@ -143,12 +143,12 @@ struct Flower : public CFGWalker<Flower, Visitor<Flower>, Info> {
     size_t currentIteration = 0;
     for (auto& block : flowBlocks) {
 #ifdef LOCAL_GRAPH_DEBUG
-      std::cout << "basic block " << block.get() << " :\n";
-      for (auto& action : block->contents.actions) {
+      std::cout << "basic block " << &block << " :\n";
+      for (auto& action : block.actions) {
         std::cout << "  action: " << *action << '\n';
       }
-      for (auto* lastSet : block->contents.lastSets) {
-        std::cout << "  last set " << lastSet << '\n';
+      for (auto& val : block.lastSets) {
+        std::cout << "  last set " << val.second << '\n';
       }
 #endif
       // go through the block, finding each get and adding it to its index,
diff --git a/src/ir/LocalStructuralDominance.cpp b/src/ir/LocalStructuralDominance.cpp
new file mode 100644
index 0000000..cfb75e0
--- /dev/null
+++ b/src/ir/LocalStructuralDominance.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ir/iteration.h"
+#include "ir/local-structural-dominance.h"
+#include "support/small_vector.h"
+
+namespace wasm {
+
+LocalStructuralDominance::LocalStructuralDominance(Function* func,
+                                                   Module& wasm,
+                                                   Mode mode) {
+  if (!wasm.features.hasReferenceTypes()) {
+    // No references, so nothing to look at.
+    return;
+  }
+
+  bool hasRefVar = false;
+  for (auto var : func->vars) {
+    if (var.isRef()) {
+      hasRefVar = true;
+      break;
+    }
+  }
+  if (!hasRefVar) {
+    return;
+  }
+
+  if (mode == NonNullableOnly) {
+    bool hasNonNullableVar = false;
+    for (auto var : func->vars) {
+      // Check if we have any non-nullable vars at all.
+      if (var.isNonNullable()) {
+        hasNonNullableVar = true;
+        break;
+      }
+    }
+    if (!hasNonNullableVar) {
+      return;
+    }
+  }
+
+  struct Scanner : public PostWalker<Scanner> {
+    std::set<Index>& nonDominatingIndices;
+
+    // The locals that have been set, and so at the current time, they
+    // structurally dominate.
+    std::vector<bool> localsSet;
+
+    Scanner(Function* func, Mode mode, std::set<Index>& nonDominatingIndices)
+      : nonDominatingIndices(nonDominatingIndices) {
+      localsSet.resize(func->getNumLocals());
+
+      // Parameters always dominate.
+      for (Index i = 0; i < func->getNumParams(); i++) {
+        localsSet[i] = true;
+      }
+
+      for (Index i = func->getNumParams(); i < func->getNumLocals(); i++) {
+        auto type = func->getLocalType(i);
+        // Mark locals we don't need to care about as "set". We never do any
+        // work for such a local.
+        if (!type.isRef() || (mode == NonNullableOnly && type.isNullable())) {
+          localsSet[i] = true;
+        }
+      }
+
+      // Note that we do not need to start a scope for the function body.
+      // Logically there is a scope there, but there is no code after it, so
+      // there is nothing to clean up when that scope exits, so we may as well
+      // not even create a scope. Just start walking the body now.
+      walk(func->body);
+    }
+
+    using Locals = SmallVector<Index, 5>;
+
+    // When we exit a control flow scope, we must undo the locals that it set.
+    std::vector<Locals> cleanupStack;
+
+    static void doBeginScope(Scanner* self, Expression** currp) {
+      self->cleanupStack.emplace_back();
+    }
+
+    static void doEndScope(Scanner* self, Expression** currp) {
+      for (auto index : self->cleanupStack.back()) {
+        assert(self->localsSet[index]);
+        self->localsSet[index] = false;
+      }
+      self->cleanupStack.pop_back();
+    }
+
+    static void doLocalSet(Scanner* self, Expression** currp) {
+      auto index = (*currp)->cast<LocalSet>()->index;
+      if (!self->localsSet[index]) {
+        // This local is now set until the end of this scope.
+        self->localsSet[index] = true;
+        // If we are not in the topmost scope, note this for later cleanup.
+        if (!self->cleanupStack.empty()) {
+          self->cleanupStack.back().push_back(index);
+        }
+      }
+    }
+
+    static void scan(Scanner* self, Expression** currp) {
+      // Use a loop to avoid recursing on the last child - we can just go
+      // straight into a loop iteration for it.
+      while (1) {
+        Expression* curr = *currp;
+
+        switch (curr->_id) {
+          case Expression::Id::InvalidId:
+            WASM_UNREACHABLE("bad id");
+
+          // local.get can just be visited immediately, as it has no children.
+          case Expression::Id::LocalGetId: {
+            auto index = curr->cast<LocalGet>()->index;
+            if (!self->localsSet[index]) {
+              self->nonDominatingIndices.insert(index);
+            }
+            return;
+          }
+          case Expression::Id::LocalSetId: {
+            auto* set = curr->cast<LocalSet>();
+            if (!self->localsSet[set->index]) {
+              self->pushTask(doLocalSet, currp);
+            }
+            // Immediately continue in the loop.
+            currp = &set->value;
+            continue;
+          }
+
+          // Control flow structures.
+          case Expression::Id::BlockId: {
+            auto* block = curr->cast<Block>();
+            // Blocks with no name are never emitted in the binary format, so do
+            // not create a scope for them.
+            if (block->name.is()) {
+              self->pushTask(Scanner::doEndScope, currp);
+            }
+            auto& list = block->list;
+            for (int i = int(list.size()) - 1; i >= 0; i--) {
+              self->pushTask(Scanner::scan, &list[i]);
+            }
+            if (block->name.is()) {
+              // Just call the task immediately.
+              doBeginScope(self, currp);
+            }
+            return;
+          }
+          case Expression::Id::IfId: {
+            if (curr->cast<If>()->ifFalse) {
+              self->pushTask(Scanner::doEndScope, currp);
+              self->maybePushTask(Scanner::scan, &curr->cast<If>()->ifFalse);
+              self->pushTask(Scanner::doBeginScope, currp);
+            }
+            self->pushTask(Scanner::doEndScope, currp);
+            self->pushTask(Scanner::scan, &curr->cast<If>()->ifTrue);
+            self->pushTask(Scanner::doBeginScope, currp);
+            // Immediately continue in the loop.
+            currp = &curr->cast<If>()->condition;
+            continue;
+          }
+          case Expression::Id::LoopId: {
+            self->pushTask(Scanner::doEndScope, currp);
+            // Just call the task immediately.
+            doBeginScope(self, currp);
+            // Immediately continue in the loop.
+            currp = &curr->cast<Loop>()->body;
+            continue;
+          }
+          case Expression::Id::TryId: {
+            auto& list = curr->cast<Try>()->catchBodies;
+            for (int i = int(list.size()) - 1; i >= 0; i--) {
+              self->pushTask(Scanner::doEndScope, currp);
+              self->pushTask(Scanner::scan, &list[i]);
+              self->pushTask(Scanner::doBeginScope, currp);
+            }
+            self->pushTask(Scanner::doEndScope, currp);
+            // Just call the task immediately.
+            doBeginScope(self, currp);
+            // Immediately continue in the loop.
+            currp = &curr->cast<Try>()->body;
+            continue;
+          }
+
+          default: {
+            // Control flow structures have been handled. This is an expression,
+            // which we scan normally.
+            assert(!Properties::isControlFlowStructure(curr));
+            PostWalker<Scanner>::scan(self, currp);
+            return;
+          }
+        }
+      }
+    }
+
+    // Only local.set needs to be visited.
+    void pushTask(TaskFunc func, Expression** currp) {
+      // Visits to anything but a set can be ignored, so only very specific
+      // tasks need to actually be pushed here. In particular, we don't want to
+      // push tasks to call doVisit* when those callbacks do nothing.
+      if (func == scan || func == doLocalSet || func == doBeginScope ||
+          func == doEndScope) {
+        PostWalker<Scanner>::pushTask(func, currp);
+      }
+    }
+    void maybePushTask(TaskFunc func, Expression** currp) {
+      if (*currp) {
+        pushTask(func, currp);
+      }
+    }
+  };
+
+  Scanner(func, mode, nonDominatingIndices);
+}
+
+} // namespace wasm
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 79a4c93..5ae6a67 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -160,26 +160,33 @@ void ReFinalize::visitBrOn(BrOn* curr) {
     updateBreakValueType(curr->name, curr->getSentType());
   }
 }
-void ReFinalize::visitRttCanon(RttCanon* curr) { curr->finalize(); }
-void ReFinalize::visitRttSub(RttSub* curr) { curr->finalize(); }
 void ReFinalize::visitStructNew(StructNew* curr) { curr->finalize(); }
 void ReFinalize::visitStructGet(StructGet* curr) { curr->finalize(); }
 void ReFinalize::visitStructSet(StructSet* curr) { curr->finalize(); }
 void ReFinalize::visitArrayNew(ArrayNew* curr) { curr->finalize(); }
+void ReFinalize::visitArrayNewSeg(ArrayNewSeg* curr) { curr->finalize(); }
 void ReFinalize::visitArrayInit(ArrayInit* curr) { curr->finalize(); }
 void ReFinalize::visitArrayGet(ArrayGet* curr) { curr->finalize(); }
 void ReFinalize::visitArraySet(ArraySet* curr) { curr->finalize(); }
 void ReFinalize::visitArrayLen(ArrayLen* curr) { curr->finalize(); }
 void ReFinalize::visitArrayCopy(ArrayCopy* curr) { curr->finalize(); }
 void ReFinalize::visitRefAs(RefAs* curr) { curr->finalize(); }
-
-void ReFinalize::visitFunction(Function* curr) {
-  // we may have changed the body from unreachable to none, which might be bad
-  // if the function has a return value
-  if (curr->getResults() != Type::none && curr->body->type == Type::none) {
-    Builder builder(*getModule());
-    curr->body = builder.blockify(curr->body, builder.makeUnreachable());
-  }
+void ReFinalize::visitStringNew(StringNew* curr) { curr->finalize(); }
+void ReFinalize::visitStringConst(StringConst* curr) { curr->finalize(); }
+void ReFinalize::visitStringMeasure(StringMeasure* curr) { curr->finalize(); }
+void ReFinalize::visitStringEncode(StringEncode* curr) { curr->finalize(); }
+void ReFinalize::visitStringConcat(StringConcat* curr) { curr->finalize(); }
+void ReFinalize::visitStringEq(StringEq* curr) { curr->finalize(); }
+void ReFinalize::visitStringAs(StringAs* curr) { curr->finalize(); }
+void ReFinalize::visitStringWTF8Advance(StringWTF8Advance* curr) {
+  curr->finalize();
+}
+void ReFinalize::visitStringWTF16Get(StringWTF16Get* curr) { curr->finalize(); }
+void ReFinalize::visitStringIterNext(StringIterNext* curr) { curr->finalize(); }
+void ReFinalize::visitStringIterMove(StringIterMove* curr) { curr->finalize(); }
+void ReFinalize::visitStringSliceWTF(StringSliceWTF* curr) { curr->finalize(); }
+void ReFinalize::visitStringSliceIter(StringSliceIter* curr) {
+  curr->finalize();
 }
 
 void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); }
@@ -189,6 +196,9 @@ void ReFinalize::visitElementSegment(ElementSegment* curr) {
   WASM_UNREACHABLE("unimp");
 }
 void ReFinalize::visitMemory(Memory* curr) { WASM_UNREACHABLE("unimp"); }
+void ReFinalize::visitDataSegment(DataSegment* curr) {
+  WASM_UNREACHABLE("unimp");
+}
 void ReFinalize::visitTag(Tag* curr) { WASM_UNREACHABLE("unimp"); }
 void ReFinalize::visitModule(Module* curr) { WASM_UNREACHABLE("unimp"); }
 
diff --git a/src/ir/abstract.h b/src/ir/abstract.h
index 63057a9..04b04e3 100644
--- a/src/ir/abstract.h
+++ b/src/ir/abstract.h
@@ -34,7 +34,6 @@ enum Op {
   Mul,
   DivU,
   DivS,
-  Rem,
   RemU,
   RemS,
   Shl,
@@ -45,6 +44,7 @@ enum Op {
   And,
   Or,
   Xor,
+  CopySign,
   // Relational
   EqZ,
   Eq,
@@ -59,13 +59,17 @@ enum Op {
   GeU
 };
 
-inline bool hasAnyShift(BinaryOp op) {
-  return op == ShlInt32 || op == ShrSInt32 || op == ShrUInt32 ||
-         op == RotLInt32 || op == RotRInt32 || op == ShlInt64 ||
-         op == ShrSInt64 || op == ShrUInt64 || op == RotLInt64 ||
+inline bool hasAnyRotateShift(BinaryOp op) {
+  return op == RotLInt32 || op == RotRInt32 || op == RotLInt64 ||
          op == RotRInt64;
 }
 
+inline bool hasAnyShift(BinaryOp op) {
+  return hasAnyRotateShift(op) || op == ShlInt32 || op == ShrSInt32 ||
+         op == ShrUInt32 || op == ShlInt64 || op == ShrSInt64 ||
+         op == ShrUInt64;
+}
+
 inline bool hasAnyReinterpret(UnaryOp op) {
   return op == ReinterpretInt32 || op == ReinterpretInt64 ||
          op == ReinterpretFloat32 || op == ReinterpretFloat64;
@@ -121,11 +125,6 @@ inline UnaryOp getUnary(Type type, Op op) {
       break;
     }
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable: {
       return InvalidUnary;
@@ -262,6 +261,8 @@ inline BinaryOp getBinary(Type type, Op op) {
           return DivFloat32;
         case DivS:
           return DivFloat32;
+        case CopySign:
+          return CopySignFloat32;
         case Eq:
           return EqFloat32;
         case Ne:
@@ -283,6 +284,8 @@ inline BinaryOp getBinary(Type type, Op op) {
           return DivFloat64;
         case DivS:
           return DivFloat64;
+        case CopySign:
+          return CopySignFloat64;
         case Eq:
           return EqFloat64;
         case Ne:
@@ -293,11 +296,6 @@ inline BinaryOp getBinary(Type type, Op op) {
       break;
     }
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable: {
       return InvalidBinary;
diff --git a/src/ir/bits.h b/src/ir/bits.h
index 21146b3..25d80fb 100644
--- a/src/ir/bits.h
+++ b/src/ir/bits.h
@@ -17,10 +17,11 @@
 #ifndef wasm_ir_bits_h
 #define wasm_ir_bits_h
 
+#include "ir/boolean.h"
 #include "ir/literal-utils.h"
+#include "ir/load-utils.h"
 #include "support/bits.h"
 #include "wasm-builder.h"
-#include <ir/load-utils.h>
 
 namespace wasm::Bits {
 
@@ -125,6 +126,9 @@ struct DummyLocalInfoProvider {
 template<typename LocalInfoProvider = DummyLocalInfoProvider>
 Index getMaxBits(Expression* curr,
                  LocalInfoProvider* localInfoProvider = nullptr) {
+  if (Properties::emitsBoolean(curr)) {
+    return 1;
+  }
   if (auto* c = curr->dynCast<Const>()) {
     switch (curr->type.getBasic()) {
       case Type::i32:
@@ -363,7 +367,7 @@ Index getMaxBits(Expression* curr,
       case LeFloat64:
       case GtFloat64:
       case GeFloat64:
-        return 1;
+        WASM_UNREACHABLE("relationals handled before");
       default: {
       }
     }
@@ -379,7 +383,7 @@ Index getMaxBits(Expression* curr,
         return 7;
       case EqZInt32:
       case EqZInt64:
-        return 1;
+        WASM_UNREACHABLE("relationals handled before");
       case WrapInt64:
       case ExtendUInt32:
         return std::min(Index(32), getMaxBits(unary->value, localInfoProvider));
@@ -433,6 +437,17 @@ Index getMaxBits(Expression* curr,
   }
 }
 
+// As getMaxBits, but returns the minimum amount of bits.
+inline Index getMinBits(Expression* curr) {
+  if (auto* c = curr->dynCast<Const>()) {
+    // Constants are simple: the min and max are identical.
+    return getMaxBits(c);
+  }
+
+  // TODO: everything else
+  return 0;
+}
+
 } // namespace wasm::Bits
 
 #endif // wasm_ir_bits_h
diff --git a/src/ir/boolean.h b/src/ir/boolean.h
new file mode 100644
index 0000000..58601c2
--- /dev/null
+++ b/src/ir/boolean.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_boolean_h
+#define wasm_ir_boolean_h
+
+#include "wasm.h"
+
+namespace wasm::Properties {
+
+inline bool emitsBoolean(Expression* curr) {
+  if (auto* unary = curr->dynCast<Unary>()) {
+    return unary->isRelational();
+  } else if (auto* binary = curr->dynCast<Binary>()) {
+    return binary->isRelational();
+  } else if (curr->is<RefIs>() || curr->is<RefEq>() || curr->is<RefTest>() ||
+             curr->is<StringEq>()) {
+    return true;
+  }
+  return false;
+}
+
+} // namespace wasm::Properties
+
+#endif // wasm_ir_boolean_h
diff --git a/src/ir/cost.h b/src/ir/cost.h
index c0a8a76..59b738b 100644
--- a/src/ir/cost.h
+++ b/src/ir/cost.h
@@ -31,6 +31,12 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
 
   CostType cost;
 
+  // A cost that is extremely high, something that is far, far more expensive
+  // than a fast operation like an add. This cost is so high it is unacceptable
+  // to add any more of it, say by an If=>Select operation (which would execute
+  // both arms; if either arm contains an unacceptable cost, we do not do it).
+  static const CostType Unacceptable = 100;
+
   CostType maybeVisit(Expression* curr) { return curr ? visit(curr) : 0; }
 
   CostType visitBlock(Block* curr) {
@@ -85,20 +91,20 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
     return 2 + visit(curr->ptr) + visit(curr->value) + 10 * curr->isAtomic;
   }
   CostType visitAtomicRMW(AtomicRMW* curr) {
-    return 100 + visit(curr->ptr) + visit(curr->value);
+    return Unacceptable + visit(curr->ptr) + visit(curr->value);
   }
   CostType visitAtomicCmpxchg(AtomicCmpxchg* curr) {
-    return 100 + visit(curr->ptr) + visit(curr->expected) +
+    return Unacceptable + visit(curr->ptr) + visit(curr->expected) +
            visit(curr->replacement);
   }
   CostType visitAtomicWait(AtomicWait* curr) {
-    return 100 + visit(curr->ptr) + visit(curr->expected) +
+    return Unacceptable + visit(curr->ptr) + visit(curr->expected) +
            visit(curr->timeout);
   }
   CostType visitAtomicNotify(AtomicNotify* curr) {
-    return 100 + visit(curr->ptr) + visit(curr->notifyCount);
+    return Unacceptable + visit(curr->ptr) + visit(curr->notifyCount);
   }
-  CostType visitAtomicFence(AtomicFence* curr) { return 100; }
+  CostType visitAtomicFence(AtomicFence* curr) { return Unacceptable; }
   CostType visitConst(Const* curr) { return 1; }
   CostType visitUnary(Unary* curr) {
     CostType ret = 0;
@@ -495,7 +501,6 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
       case RelaxedSwizzleVecI8x16:
       case RelaxedQ15MulrSVecI16x8:
       case DotI8x16I7x16SToVecI16x8:
-      case DotI8x16I7x16UToVecI16x8:
         ret = 1;
         break;
       case InvalidBinary:
@@ -511,7 +516,7 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
   CostType visitReturn(Return* curr) { return maybeVisit(curr->value); }
   CostType visitMemorySize(MemorySize* curr) { return 1; }
   CostType visitMemoryGrow(MemoryGrow* curr) {
-    return 100 + visit(curr->delta);
+    return Unacceptable + visit(curr->delta);
   }
   CostType visitMemoryInit(MemoryInit* curr) {
     return 6 + visit(curr->dest) + visit(curr->offset) + visit(curr->size);
@@ -544,7 +549,6 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
       case RelaxedFmaVecF64x2:
       case RelaxedFmsVecF64x2:
       case DotI8x16I7x16AddSToVecI32x4:
-      case DotI8x16I7x16AddUToVecI32x4:
         ret = 1;
         break;
     }
@@ -568,20 +572,20 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
   }
   CostType visitTableSize(TableSize* curr) { return 1; }
   CostType visitTableGrow(TableGrow* curr) {
-    return 100 + visit(curr->value) + visit(curr->delta);
+    return Unacceptable + visit(curr->value) + visit(curr->delta);
   }
   CostType visitTry(Try* curr) {
     // We assume no exception will be thrown in most cases
     return visit(curr->body);
   }
   CostType visitThrow(Throw* curr) {
-    CostType ret = 100;
+    CostType ret = Unacceptable;
     for (auto* child : curr->operands) {
       ret += visit(child);
     }
     return ret;
   }
-  CostType visitRethrow(Rethrow* curr) { return 100; }
+  CostType visitRethrow(Rethrow* curr) { return Unacceptable; }
   CostType visitTupleMake(TupleMake* curr) {
     CostType ret = 0;
     for (auto* child : curr->operands) {
@@ -597,33 +601,27 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
   CostType visitI31New(I31New* curr) { return 3 + visit(curr->value); }
   CostType visitI31Get(I31Get* curr) { return 2 + visit(curr->i31); }
   CostType visitRefTest(RefTest* curr) {
-    return 2 + nullCheckCost(curr->ref) + visit(curr->ref) +
-           maybeVisit(curr->rtt);
+    // Casts have a very high cost because in the VM they end up implemented as
+    // a combination of loads and branches. Given they contain branches, we do
+    // not want to add any more such work.
+    return Unacceptable + nullCheckCost(curr->ref) + visit(curr->ref);
   }
   CostType visitRefCast(RefCast* curr) {
-    return 2 + nullCheckCost(curr->ref) + visit(curr->ref) +
-           maybeVisit(curr->rtt);
+    return Unacceptable + nullCheckCost(curr->ref) + visit(curr->ref);
   }
   CostType visitBrOn(BrOn* curr) {
-    // BrOnCast has more work to do with the rtt, so add a little there.
-    CostType base = curr->op == BrOnCast ? 3 : 2;
-    return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref) +
-           maybeVisit(curr->rtt);
-  }
-  CostType visitRttCanon(RttCanon* curr) {
-    // TODO: investigate actual RTT costs in VMs
-    return 1;
-  }
-  CostType visitRttSub(RttSub* curr) {
-    // TODO: investigate actual RTT costs in VMs
-    return 2 + visit(curr->parent);
+    // BrOn of a null can be fairly fast, but anything else is a cast check
+    // basically, and an unacceptable cost.
+    CostType base =
+      curr->op == BrOnNull || curr->op == BrOnNonNull ? 2 : Unacceptable;
+    return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref);
   }
   CostType visitStructNew(StructNew* curr) {
     // While allocation itself is almost free with generational GC, there is
     // at least some baseline cost, plus writing the fields. (If we use default
     // values for the fields, then it is possible they are all 0 and if so, we
     // can get that almost for free as well, so don't add anything there.)
-    CostType ret = 4 + maybeVisit(curr->rtt) + curr->operands.size();
+    CostType ret = 4 + curr->operands.size();
     for (auto* child : curr->operands) {
       ret += visit(child);
     }
@@ -636,11 +634,13 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
     return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->value);
   }
   CostType visitArrayNew(ArrayNew* curr) {
-    return 4 + maybeVisit(curr->rtt) + visit(curr->size) +
-           maybeVisit(curr->init);
+    return 4 + visit(curr->size) + maybeVisit(curr->init);
+  }
+  CostType visitArrayNewSeg(ArrayNewSeg* curr) {
+    return 4 + visit(curr->offset) + visit(curr->size);
   }
   CostType visitArrayInit(ArrayInit* curr) {
-    CostType ret = 4 + maybeVisit(curr->rtt);
+    CostType ret = 4;
     for (auto* child : curr->values) {
       ret += visit(child);
     }
@@ -662,6 +662,43 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
            visit(curr->srcRef) + visit(curr->srcIndex) + visit(curr->length);
   }
   CostType visitRefAs(RefAs* curr) { return 1 + visit(curr->value); }
+  CostType visitStringNew(StringNew* curr) {
+    return 8 + visit(curr->ptr) + maybeVisit(curr->length) +
+           maybeVisit(curr->start) + maybeVisit(curr->end);
+  }
+  CostType visitStringConst(StringConst* curr) { return 4; }
+  CostType visitStringMeasure(StringMeasure* curr) {
+    return 6 + visit(curr->ref);
+  }
+  CostType visitStringEncode(StringEncode* curr) {
+    return 6 + visit(curr->ref) + visit(curr->ptr);
+  }
+  CostType visitStringConcat(StringConcat* curr) {
+    return 10 + visit(curr->left) + visit(curr->right);
+  }
+  CostType visitStringEq(StringEq* curr) {
+    // "3" is chosen since strings might or might not be interned in the engine.
+    return 3 + visit(curr->left) + visit(curr->right);
+  }
+  CostType visitStringAs(StringAs* curr) { return 4 + visit(curr->ref); }
+  CostType visitStringWTF8Advance(StringWTF8Advance* curr) {
+    return 4 + visit(curr->ref) + visit(curr->pos) + visit(curr->bytes);
+  }
+  CostType visitStringWTF16Get(StringWTF16Get* curr) {
+    return 1 + visit(curr->ref) + visit(curr->pos);
+  }
+  CostType visitStringIterNext(StringIterNext* curr) {
+    return 2 + visit(curr->ref);
+  }
+  CostType visitStringIterMove(StringIterMove* curr) {
+    return 4 + visit(curr->ref) + visit(curr->num);
+  }
+  CostType visitStringSliceWTF(StringSliceWTF* curr) {
+    return 8 + visit(curr->ref) + visit(curr->start) + visit(curr->end);
+  }
+  CostType visitStringSliceIter(StringSliceIter* curr) {
+    return 8 + visit(curr->ref) + visit(curr->num);
+  }
 
 private:
   CostType nullCheckCost(Expression* ref) {
diff --git a/src/ir/drop.cpp b/src/ir/drop.cpp
new file mode 100644
index 0000000..49bff55
--- /dev/null
+++ b/src/ir/drop.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_drop_h
+#define wasm_ir_drop_h
+
+#include "ir/branch-utils.h"
+#include "ir/effects.h"
+#include "ir/iteration.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+// Given an expression, returns a new expression that drops the given
+// expression's children that cannot be removed outright due to their side
+// effects. Note that this only operates on children that execute
+// unconditionally. That is the case in almost all expressions, except for those
+// with conditional execution, like if, which unconditionally executes the
+// condition but then conditionally executes one of the two arms.
+Expression* getDroppedChildrenAndAppend(Expression* curr,
+                                        Module& wasm,
+                                        const PassOptions& options,
+                                        Expression* last) {
+  // We check for shallow effects here, since we may be able to remove |curr|
+  // itself but keep its children around - we don't want effects in the children
+  // to stop us from improving the code. Note that there are cases where the
+  // combined curr+children has fewer effects than curr itself, such as if curr
+  // is a block and the child branches to it, but in such cases we cannot remove
+  // curr anyhow (those cases are ruled out below), so looking at non-shallow
+  // effects would never help us (and would be slower to run).
+  ShallowEffectAnalyzer effects(options, wasm, curr);
+  // Ignore a trap, as the unreachable replacement would trap too.
+  if (last->is<Unreachable>()) {
+    effects.trap = false;
+  }
+
+  // We cannot remove
+  // 1. Expressions with unremovable side effects
+  // 2. if: 'if's contains conditional expressions
+  // 3. try: Removing a try could leave a pop without a proper parent
+  // 4. pop: Pops are struturally necessary in catch bodies
+  // 5. Branch targets: We will need the target for the branches to it to
+  //                    validate.
+  Builder builder(wasm);
+  if (effects.hasUnremovableSideEffects() || curr->is<If>() ||
+      curr->is<Try>() || curr->is<Pop>() ||
+      BranchUtils::getDefinedName(curr).is()) {
+    return builder.makeSequence(builder.makeDrop(curr), last);
+  }
+
+  std::vector<Expression*> contents;
+  for (auto* child : ChildIterator(curr)) {
+    if (!EffectAnalyzer(options, wasm, child).hasUnremovableSideEffects()) {
+      continue;
+    }
+    if (child->type.isConcrete()) {
+      contents.push_back(builder.makeDrop(child));
+    } else {
+      // The child is unreachable, or none (none is possible as a child of a
+      // block or loop, etc.); in both cases we do not need a drop.
+      contents.push_back(child);
+    }
+  }
+  if (contents.empty()) {
+    return last;
+  }
+  contents.push_back(last);
+  return builder.makeBlock(contents);
+}
+
+} // namespace wasm
+
+#endif // wasm_ir_drop_h
diff --git a/src/ir/drop.h b/src/ir/drop.h
new file mode 100644
index 0000000..78ff98c
--- /dev/null
+++ b/src/ir/drop.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_drop_h
+#define wasm_ir_drop_h
+
+#include "wasm.h"
+
+namespace wasm {
+
+struct PassOptions;
+
+// Given an expression, returns a new expression that drops the given
+// expression's unconditional children that cannot be removed outright due to
+// their side effects. This is useful if we know the node is not needed but may
+// need to keep the children around; this utility will automatically remove any
+// children we do not actually need to keep, based on their effects.
+//
+// The caller must also pass in a last item to append to the output (which is
+// typically what the original expression is replaced with).
+//
+// This function only operates on children that executes unconditionally. That
+// is the case in almost all expressions, except for those with conditional
+// execution, like if, which unconditionally executes the condition but then
+// conditionally executes one of the two arms. The above function simply returns
+// all children in order, so it does this to if:
+//
+//  (if
+//    (condition)
+//    (arm-A)
+//    (arm-B)
+//  )
+// =>
+//  (drop
+//    (condition)
+//  )
+//  (drop
+//    (arm-A)
+//  )
+//  (drop
+//    (arm-B)
+//  )
+//  (appended last item)
+//
+// This is dangerous as it executes what were conditional children in an
+// unconditional way. To avoid that issue, this function will only operate on
+// unconditional children, and keep conditional ones as they were. That means
+// it will not split up and drop the children of an if, for example. All we do
+// in that case is drop the entire if and append the last item:
+//
+//  (drop
+//    (if
+//      (condition)
+//      (arm-A)
+//      (arm-B)
+//    )
+//  )
+//  (appended last item)
+//
+//  Also this function preserves other unremovable expressions like trys, pops,
+//  and named blocks.
+Expression* getDroppedChildrenAndAppend(Expression* curr,
+                                        Module& wasm,
+                                        const PassOptions& options,
+                                        Expression* last);
+
+} // namespace wasm
+
+#endif // wasm_ir_drop_h
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 2bd209d..70e9192 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -27,19 +27,27 @@ namespace wasm {
 
 class EffectAnalyzer {
 public:
+  EffectAnalyzer(const PassOptions& passOptions, Module& module)
+    : ignoreImplicitTraps(passOptions.ignoreImplicitTraps),
+      trapsNeverHappen(passOptions.trapsNeverHappen),
+      funcEffectsMap(passOptions.funcEffectsMap), module(module),
+      features(module.features) {}
+
   EffectAnalyzer(const PassOptions& passOptions,
                  Module& module,
-                 Expression* ast = nullptr)
-    : ignoreImplicitTraps(passOptions.ignoreImplicitTraps),
-      trapsNeverHappen(passOptions.trapsNeverHappen), module(module),
-      features(module.features) {
-    if (ast) {
-      walk(ast);
-    }
+                 Expression* ast)
+    : EffectAnalyzer(passOptions, module) {
+    walk(ast);
+  }
+
+  EffectAnalyzer(const PassOptions& passOptions, Module& module, Function* func)
+    : EffectAnalyzer(passOptions, module) {
+    walk(func);
   }
 
   bool ignoreImplicitTraps;
   bool trapsNeverHappen;
+  std::shared_ptr<FuncEffectsMap> funcEffectsMap;
   Module& module;
   FeatureSet features;
 
@@ -57,6 +65,22 @@ public:
     post();
   }
 
+  // Walk an entire function body. This will ignore effects that are not
+  // noticeable from the perspective of the caller, that is, effects that are
+  // only noticeable during the call, but "vanish" when the call stack is
+  // unwound.
+  void walk(Function* func) {
+    walk(func->body);
+
+    // We can ignore branching out of the function body - this can only be
+    // a return, and that is only noticeable in the function, not outside.
+    branchesOut = false;
+
+    // When the function exits, changes to locals cannot be noticed any more.
+    localsWritten.clear();
+    localsRead.clear();
+  }
+
   // Core effect tracking
 
   // Definitely branches out of this expression, or does a return, etc.
@@ -88,18 +112,20 @@ public:
   // each other, but it is not ok to remove them or reorder them with other
   // effects in a noticeable way.
   //
-  // Note also that we ignore runtime-dependent traps, such as hitting a
-  // recursion limit or running out of memory. Such traps are not part of wasm's
-  // official semantics, and they can occur anywhere: *any* instruction could in
-  // theory be implemented by a VM call (as will be the case when running in an
-  // interpreter), and such a call could run out of stack or memory in
-  // principle. To put it another way, an i32 division by zero is the program
-  // doing something bad that causes a trap, but the VM running out of memory is
-  // the VM doing something bad - and therefore the VM behaving in a way that is
-  // not according to the wasm semantics - and we do not model such things. Note
-  // that as a result we do *not* mark things like GC allocation instructions as
-  // having side effects, which has the nice benefit of making it possible to
-  // eliminate an allocation whose result is not captured.
+  // Note also that we ignore *optional* runtime-specific traps: we only
+  // consider as trapping something that will trap in *all* VMs, and *all* the
+  // time. For example, a single allocation might trap in a VM in a particular
+  // execution, if it happens to run out of memory just there, but that is not
+  // enough for us to mark it as having a trap effect. (Note that not marking
+  // each allocation as possibly trapping has the nice benefit of making it
+  // possible to eliminate an allocation whose result is not captured.) OTOH, we
+  // *do* mark a potentially infinite number of allocations as trapping, as all
+  // VMs would trap eventually, and the same for potentially infinite recursion,
+  // etc.
+  //   * We assume that VMs will timeout eventually, so any loop that we cannot
+  //     prove terminates is considered to trap. (Some VMs might not have
+  //     such timeouts, but even they will error before the heat death of the
+  //     universe, which is a kind of trap.)
   bool trap = false;
   // A trap from an instruction like a load or div/rem, which may trap on corner
   // cases. If we do not ignore implicit traps then these are counted as a trap.
@@ -188,8 +214,7 @@ public:
   }
 
   bool hasAnything() const {
-    return hasSideEffects() || accessesLocal() || readsMemory || readsTable ||
-           accessesMutableGlobal();
+    return hasSideEffects() || accessesLocal() || readsMutableGlobalState();
   }
 
   // check if we break to anything external from ourselves
@@ -262,7 +287,7 @@ public:
     return false;
   }
 
-  void mergeIn(EffectAnalyzer& other) {
+  void mergeIn(const EffectAnalyzer& other) {
     branchesOut = branchesOut || other.branchesOut;
     calls = calls || other.calls;
     readsMemory = readsMemory || other.readsMemory;
@@ -393,20 +418,13 @@ private:
     }
     void visitIf(If* curr) {}
     void visitLoop(Loop* curr) {
-      if (curr->name.is()) {
-        parent.breakTargets.erase(curr->name); // these were internal breaks
-      }
-      // if the loop is unreachable, then there is branching control flow:
-      //  (1) if the body is unreachable because of a (return), uncaught (br)
-      //      etc., then we already noted branching, so it is ok to mark it
-      //      again (if we have *caught* (br)s, then they did not lead to the
-      //      loop body being unreachable). (same logic applies to blocks)
-      //  (2) if the loop is unreachable because it only has branches up to the
-      //      loop top, but no way to get out, then it is an infinite loop, and
-      //      we consider that a branching side effect (note how the same logic
-      //      does not apply to blocks).
-      if (curr->type == Type::unreachable) {
-        parent.branchesOut = true;
+      if (curr->name.is() && parent.breakTargets.erase(curr->name) > 0) {
+        // Breaks to this loop exist, which we just removed as they do not have
+        // further effect outside of this loop. One additional thing we need to
+        // take into account is infinite looping, which is a noticeable side
+        // effect we can't normally remove - eventually the VM will time out and
+        // error (see more details in the comment on trapping above).
+        parent.implicitTrap = true;
       }
     }
     void visitBreak(Break* curr) { parent.breakTargets.insert(curr->name); }
@@ -423,14 +441,35 @@ private:
         return;
       }
 
+      if (curr->isReturn) {
+        parent.branchesOut = true;
+      }
+
+      if (parent.funcEffectsMap) {
+        auto iter = parent.funcEffectsMap->find(curr->target);
+        if (iter != parent.funcEffectsMap->end()) {
+          // We have effect information for this call target, and can just use
+          // that. The one change we may want to make is to remove throws_, if
+          // the target function throws and we know that will be caught anyhow,
+          // the same as the code below for the general path.
+          const auto& targetEffects = iter->second;
+          if (targetEffects.throws_ && parent.tryDepth > 0) {
+            auto filteredEffects = targetEffects;
+            filteredEffects.throws_ = false;
+            parent.mergeIn(filteredEffects);
+          } else {
+            // Just merge in all the effects.
+            parent.mergeIn(targetEffects);
+          }
+          return;
+        }
+      }
+
       parent.calls = true;
       // When EH is enabled, any call can throw.
       if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
         parent.throws_ = true;
       }
-      if (curr->isReturn) {
-        parent.branchesOut = true;
-      }
     }
     void visitCallIndirect(CallIndirect* curr) {
       parent.calls = true;
@@ -553,7 +592,8 @@ private:
           parent.implicitTrap = true;
           break;
         }
-        default: {}
+        default: {
+        }
       }
     }
     void visitBinary(Binary* curr) {
@@ -582,7 +622,8 @@ private:
           }
           break;
         }
-        default: {}
+        default: {
+        }
       }
     }
     void visitSelect(Select* curr) {}
@@ -653,8 +694,17 @@ private:
     void visitTupleMake(TupleMake* curr) {}
     void visitTupleExtract(TupleExtract* curr) {}
     void visitI31New(I31New* curr) {}
-    void visitI31Get(I31Get* curr) {}
+    void visitI31Get(I31Get* curr) {
+      // traps when the ref is null
+      if (curr->i31->type.isNullable()) {
+        parent.implicitTrap = true;
+      }
+    }
     void visitCallRef(CallRef* curr) {
+      if (curr->target->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       parent.calls = true;
       if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
         parent.throws_ = true;
@@ -662,22 +712,26 @@ private:
       if (curr->isReturn) {
         parent.branchesOut = true;
       }
-      // traps when the arg is null
-      parent.implicitTrap = true;
+      // traps when the call target is null
+      if (curr->target->type.isNullable()) {
+        parent.implicitTrap = true;
+      }
     }
     void visitRefTest(RefTest* curr) {}
     void visitRefCast(RefCast* curr) {
-      // Traps if the ref is not null and it has an invalid rtt.
+      // Traps if the ref is not null and the cast fails.
       parent.implicitTrap = true;
     }
     void visitBrOn(BrOn* curr) { parent.breakTargets.insert(curr->name); }
-    void visitRttCanon(RttCanon* curr) {}
-    void visitRttSub(RttSub* curr) {}
     void visitStructNew(StructNew* curr) {}
     void visitStructGet(StructGet* curr) {
       if (curr->ref->type == Type::unreachable) {
         return;
       }
+      if (curr->ref->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       if (curr->ref->type.getHeapType()
             .getStruct()
             .fields[curr->index]
@@ -690,6 +744,10 @@ private:
       }
     }
     void visitStructSet(StructSet* curr) {
+      if (curr->ref->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       parent.writesStruct = true;
       // traps when the arg is null
       if (curr->ref->type.isNullable()) {
@@ -697,30 +755,55 @@ private:
       }
     }
     void visitArrayNew(ArrayNew* curr) {}
+    void visitArrayNewSeg(ArrayNewSeg* curr) {
+      // Traps on out of bounds access to segments or access to dropped
+      // segments.
+      parent.implicitTrap = true;
+    }
     void visitArrayInit(ArrayInit* curr) {}
     void visitArrayGet(ArrayGet* curr) {
+      if (curr->ref->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       parent.readsArray = true;
       // traps when the arg is null or the index out of bounds
       parent.implicitTrap = true;
     }
     void visitArraySet(ArraySet* curr) {
+      if (curr->ref->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       parent.writesArray = true;
       // traps when the arg is null or the index out of bounds
       parent.implicitTrap = true;
     }
     void visitArrayLen(ArrayLen* curr) {
+      if (curr->ref->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       // traps when the arg is null
       if (curr->ref->type.isNullable()) {
         parent.implicitTrap = true;
       }
     }
     void visitArrayCopy(ArrayCopy* curr) {
+      if (curr->destRef->type.isNull() || curr->srcRef->type.isNull()) {
+        parent.trap = true;
+        return;
+      }
       parent.readsArray = true;
       parent.writesArray = true;
       // traps when a ref is null, or when out of bounds.
       parent.implicitTrap = true;
     }
     void visitRefAs(RefAs* curr) {
+      if (curr->op == ExternInternalize || curr->op == ExternExternalize) {
+        // These conversions are infallible.
+        return;
+      }
       // traps when the arg is not valid
       parent.implicitTrap = true;
       // Note: We could be more precise here and report the lack of a possible
@@ -732,6 +815,92 @@ private:
       // we keep the code here simpler, but it does mean another optimization
       // cycle may be needed in some cases.
     }
+    void visitStringNew(StringNew* curr) {
+      // traps when out of bounds in linear memory or ref is null
+      parent.implicitTrap = true;
+      switch (curr->op) {
+        case StringNewUTF8:
+        case StringNewWTF8:
+        case StringNewReplace:
+        case StringNewWTF16:
+          parent.readsMemory = true;
+          break;
+        case StringNewUTF8Array:
+        case StringNewWTF8Array:
+        case StringNewReplaceArray:
+        case StringNewWTF16Array:
+          parent.readsArray = true;
+          break;
+        default: {
+        }
+      }
+    }
+    void visitStringConst(StringConst* curr) {}
+    void visitStringMeasure(StringMeasure* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+    }
+    void visitStringEncode(StringEncode* curr) {
+      // traps when ref is null or we write out of bounds.
+      parent.implicitTrap = true;
+      switch (curr->op) {
+        case StringEncodeUTF8:
+        case StringEncodeWTF8:
+        case StringEncodeWTF16:
+          parent.writesMemory = true;
+          break;
+        case StringEncodeUTF8Array:
+        case StringEncodeWTF8Array:
+        case StringEncodeWTF16Array:
+          parent.writesArray = true;
+          break;
+        default: {
+        }
+      }
+    }
+    void visitStringConcat(StringConcat* curr) {
+      // traps when an input is null.
+      parent.implicitTrap = true;
+    }
+    void visitStringEq(StringEq* curr) {}
+    void visitStringAs(StringAs* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+    }
+    void visitStringWTF8Advance(StringWTF8Advance* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+    }
+    void visitStringWTF16Get(StringWTF16Get* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+    }
+    void visitStringIterNext(StringIterNext* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+      // modifies state in the iterator. we model that as accessing heap memory
+      // in an array atm TODO consider adding a new effect type for this (we
+      // added one for arrays because struct/array operations often interleave,
+      // say with vtable accesses, but it's not clear adding overhead to this
+      // class is worth it for string iters)
+      parent.readsArray = true;
+      parent.writesArray = true;
+    }
+    void visitStringIterMove(StringIterMove* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+      // see StringIterNext.
+      parent.readsArray = true;
+      parent.writesArray = true;
+    }
+    void visitStringSliceWTF(StringSliceWTF* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+    }
+    void visitStringSliceIter(StringSliceIter* curr) {
+      // traps when ref is null.
+      parent.implicitTrap = true;
+    }
   };
 
 public:
@@ -817,9 +986,19 @@ public:
     return effects;
   }
 
-  void ignoreBranches() {
+  // Ignores all forms of control flow transfers: breaks, returns, and
+  // exceptions. (Note that traps are not considered relevant here - a trap does
+  // not just transfer control flow, but can be seen as halting the entire
+  // program.)
+  //
+  // This function matches transfersControlFlow(), that is, after calling this
+  // method transfersControlFlow() will always return false.
+  void ignoreControlFlowTransfers() {
     branchesOut = false;
     breakTargets.clear();
+    throws_ = false;
+    delegateTargets.clear();
+    assert(!transfersControlFlow());
   }
 
 private:
diff --git a/src/ir/eh-utils.cpp b/src/ir/eh-utils.cpp
index 3d36957..f9cd2d9 100644
--- a/src/ir/eh-utils.cpp
+++ b/src/ir/eh-utils.cpp
@@ -17,7 +17,6 @@
 #include "ir/eh-utils.h"
 #include "ir/branch-utils.h"
 #include "ir/find_all.h"
-#include "ir/type-updating.h"
 
 namespace wasm {
 
@@ -157,11 +156,39 @@ void handleBlockNestedPops(Function* func, Module& wasm) {
   for (auto* try_ : trys.list) {
     handleBlockNestedPop(try_, func, wasm);
   }
-  // Pops we handled can be of non-defaultable types, so we may have created
-  // non-nullable type locals. Fix them.
-  TypeUpdating::handleNonDefaultableLocals(func, wasm);
 }
 
+Pop* findPop(Expression* expr) {
+  auto pops = findPops(expr);
+  if (pops.size() == 0) {
+    return nullptr;
+  }
+  assert(pops.size() == 1);
+  return pops[0];
+}
+
+SmallVector<Pop*, 1> findPops(Expression* expr) {
+  SmallVector<Pop*, 1> pops;
+  SmallVector<Expression*, 8> work;
+  work.push_back(expr);
+  while (!work.empty()) {
+    auto* curr = work.back();
+    work.pop_back();
+    if (auto* pop = curr->dynCast<Pop>()) {
+      pops.push_back(pop);
+    } else if (auto* try_ = curr->dynCast<Try>()) {
+      // We don't go into inner catch bodies; pops in inner catch bodies
+      // belong to the inner catches
+      work.push_back(try_->body);
+    } else {
+      for (auto* child : ChildIterator(curr)) {
+        work.push_back(child);
+      }
+    }
+  }
+  return pops;
+};
+
 } // namespace EHUtils
 
 } // namespace wasm
diff --git a/src/ir/eh-utils.h b/src/ir/eh-utils.h
index 25677a3..79ccbc5 100644
--- a/src/ir/eh-utils.h
+++ b/src/ir/eh-utils.h
@@ -17,6 +17,7 @@
 #ifndef wasm_ir_eh_h
 #define wasm_ir_eh_h
 
+#include "support/small_vector.h"
 #include "wasm.h"
 
 namespace wasm {
@@ -40,6 +41,25 @@ void handleBlockNestedPop(Try* try_, Function* func, Module& wasm);
 // Calls handleBlockNestedPop for each 'Try's in a given function.
 void handleBlockNestedPops(Function* func, Module& wasm);
 
+// Given a catch body, find the pop corresponding to the catch. There might be
+// pops nested inside a try inside this catch, and we must ignore them, like
+// here:
+//
+//  (catch
+//    (pop) ;; we want this for the outer catch
+//    (try
+//      (catch
+//        (pop) ;; but we do not want this for the outer catch
+//
+// If there is no pop, which can happen if the tag has no params, then nullptr
+// is returned.
+Pop* findPop(Expression* expr);
+
+// Like findPop(), but it does *not* assume that the module validates. A catch
+// might therefore have any number of pops. This function is primarily useful in
+// the validator - normally you should call findPop(), above.
+SmallVector<Pop*, 1> findPops(Expression* expr);
+
 } // namespace EHUtils
 
 } // namespace wasm
diff --git a/src/ir/equivalent_sets.h b/src/ir/equivalent_sets.h
index 657ff98..4b2cd7d 100644
--- a/src/ir/equivalent_sets.h
+++ b/src/ir/equivalent_sets.h
@@ -26,7 +26,7 @@ namespace wasm {
 //
 struct EquivalentSets {
   // A set of indexes. This is ordered for deterministic iteration.
-  typedef std::set<Index> Set;
+  using Set = std::set<Index>;
 
   std::unordered_map<Index, std::shared_ptr<Set>> indexSets;
 
diff --git a/src/ir/flat.h b/src/ir/flat.h
index 37b5dfb..1a87227 100644
--- a/src/ir/flat.h
+++ b/src/ir/flat.h
@@ -116,7 +116,11 @@ inline void verifyFlatness(Module* module) {
         PostWalker<VerifyFlatness, UnifiedExpressionVisitor<VerifyFlatness>>> {
     bool isFunctionParallel() override { return true; }
 
-    VerifyFlatness* create() override { return new VerifyFlatness(); }
+    bool modifiesBinaryenIR() override { return false; }
+
+    std::unique_ptr<Pass> create() override {
+      return std::make_unique<VerifyFlatness>();
+    }
 
     void doVisitFunction(Function* func) { verifyFlatness(func); }
   };
diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h
index 584cde8..abe5f2d 100644
--- a/src/ir/gc-type-utils.h
+++ b/src/ir/gc-type-utils.h
@@ -56,14 +56,11 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) {
         flip = true;
         [[fallthrough]];
       case BrOnCast:
-        if (!br->rtt) {
-          // This is a static cast check, which we may be able to resolve at
-          // compile time. Note that the type must be non-nullable for us to
-          // succeed at that inference, as otherwise a null can make us fail.
-          if (Type::isSubType(br->ref->type,
-                              Type(br->intendedType, NonNullable))) {
-            return flip ? Failure : Success;
-          }
+        // Note that the type must be non-nullable for us to succeed since a
+        // null would make us fail.
+        if (Type::isSubType(br->ref->type,
+                            Type(br->intendedType, NonNullable))) {
+          return flip ? Failure : Success;
         }
         return Unknown;
       case BrOnNonFunc:
@@ -103,7 +100,7 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) {
         expected = I31;
         break;
       default:
-        WASM_UNREACHABLE("unhandled BrOn");
+        WASM_UNREACHABLE("unhandled RefIs");
     }
     child = is->value;
   } else if (auto* as = curr->dynCast<RefAs>()) {
@@ -120,8 +117,12 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) {
       case RefAsI31:
         expected = I31;
         break;
+      case ExternInternalize:
+      case ExternExternalize:
+        // These instructions can never be removed.
+        return Unknown;
       default:
-        WASM_UNREACHABLE("unhandled BrOn");
+        WASM_UNREACHABLE("unhandled RefAs");
     }
     child = as->value;
   } else {
diff --git a/src/ir/hashed.h b/src/ir/hashed.h
index b1fd659..4e90951 100644
--- a/src/ir/hashed.h
+++ b/src/ir/hashed.h
@@ -29,6 +29,8 @@ namespace wasm {
 struct FunctionHasher : public WalkerPass<PostWalker<FunctionHasher>> {
   bool isFunctionParallel() override { return true; }
 
+  bool modifiesBinaryenIR() override { return false; }
+
   struct Map : public std::map<Function*, size_t> {};
 
   FunctionHasher(Map* output, ExpressionAnalyzer::ExprHasher customHasher)
@@ -36,8 +38,8 @@ struct FunctionHasher : public WalkerPass<PostWalker<FunctionHasher>> {
   FunctionHasher(Map* output)
     : output(output), customHasher(ExpressionAnalyzer::nothingHasher) {}
 
-  FunctionHasher* create() override {
-    return new FunctionHasher(output, customHasher);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FunctionHasher>(output, customHasher);
   }
 
   static Map createMap(Module* module) {
diff --git a/src/ir/import-utils.h b/src/ir/import-utils.h
index 2e7a4f4..d0b5a80 100644
--- a/src/ir/import-utils.h
+++ b/src/ir/import-utils.h
@@ -30,6 +30,7 @@ struct ImportInfo {
   std::vector<Global*> importedGlobals;
   std::vector<Function*> importedFunctions;
   std::vector<Table*> importedTables;
+  std::vector<Memory*> importedMemories;
   std::vector<Tag*> importedTags;
 
   ImportInfo(Module& wasm) : wasm(wasm) {
@@ -48,6 +49,11 @@ struct ImportInfo {
         importedTables.push_back(import.get());
       }
     }
+    for (auto& import : wasm.memories) {
+      if (import->imported()) {
+        importedMemories.push_back(import.get());
+      }
+    }
     for (auto& import : wasm.tags) {
       if (import->imported()) {
         importedTags.push_back(import.get());
@@ -88,11 +94,13 @@ struct ImportInfo {
 
   Index getNumImportedTables() { return importedTables.size(); }
 
+  Index getNumImportedMemories() { return importedMemories.size(); }
+
   Index getNumImportedTags() { return importedTags.size(); }
 
   Index getNumImports() {
     return getNumImportedGlobals() + getNumImportedFunctions() +
-           getNumImportedTags() + (wasm.memory.imported() ? 1 : 0) +
+           getNumImportedTags() + getNumImportedMemories() +
            getNumImportedTables();
   }
 
@@ -108,6 +116,10 @@ struct ImportInfo {
     return wasm.tables.size() - getNumImportedTables();
   }
 
+  Index getNumDefinedMemories() {
+    return wasm.memories.size() - getNumImportedMemories();
+  }
+
   Index getNumDefinedTags() { return wasm.tags.size() - getNumImportedTags(); }
 };
 
diff --git a/src/ir/intrinsics.cpp b/src/ir/intrinsics.cpp
index c2318ca..26b6243 100644
--- a/src/ir/intrinsics.cpp
+++ b/src/ir/intrinsics.cpp
@@ -19,11 +19,17 @@
 
 namespace wasm {
 
-static Name BinaryenIntrinsics("binaryen-intrinsics"),
+static Name BinaryenIntrinsicsModule("binaryen-intrinsics"),
   CallWithoutEffects("call.without.effects");
 
 bool Intrinsics::isCallWithoutEffects(Function* func) {
-  return func->module == BinaryenIntrinsics && func->base == CallWithoutEffects;
+  if (func->module != BinaryenIntrinsicsModule) {
+    return false;
+  }
+  if (func->base == CallWithoutEffects) {
+    return true;
+  }
+  Fatal() << "Unrecognized intrinsic";
 }
 
 Call* Intrinsics::isCallWithoutEffects(Expression* curr) {
diff --git a/src/ir/iteration.h b/src/ir/iteration.h
index e897c51..a2c9cee 100644
--- a/src/ir/iteration.h
+++ b/src/ir/iteration.h
@@ -76,6 +76,7 @@ template<class Specific> class AbstractChildIterator {
 public:
   // The vector of children in the order emitted by wasm-delegations-fields
   // (which is in reverse execution order).
+  // TODO: rename this "reverseChildren"?
   SmallVector<Expression**, 4> children;
 
   AbstractChildIterator(Expression* parent) {
diff --git a/src/ir/linear-execution.h b/src/ir/linear-execution.h
index 230bafe..c88d0e4 100644
--- a/src/ir/linear-execution.h
+++ b/src/ir/linear-execution.h
@@ -49,7 +49,7 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
 
     switch (curr->_id) {
       case Expression::Id::InvalidId:
-        abort();
+        WASM_UNREACHABLE("bad id");
       case Expression::Id::BlockId: {
         self->pushTask(SubType::doVisitBlock, currp);
         if (curr->cast<Block>()->name.is()) {
@@ -130,7 +130,6 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
       case Expression::Id::BrOnId: {
         self->pushTask(SubType::doVisitBrOn, currp);
         self->pushTask(SubType::doNoteNonLinear, currp);
-        self->maybePushTask(SubType::scan, &curr->cast<BrOn>()->rtt);
         self->pushTask(SubType::scan, &curr->cast<BrOn>()->ref);
         break;
       }
diff --git a/src/ir/literal-utils.h b/src/ir/literal-utils.h
index ad02234..0131ecd 100644
--- a/src/ir/literal-utils.h
+++ b/src/ir/literal-utils.h
@@ -33,17 +33,9 @@ inline bool canMakeZero(Type type) {
   if (type.isNonNullable()) {
     return false;
   }
-  if (type.isRtt() && type.getRtt().hasDepth()) {
-    // An rtt with depth cannot be constructed as a simple zero: we'd need to
-    // create not just a zero (an rtt.canon) but also some rtt.subs that add to
-    // the depth, so disallow that. Also, there is no practical way to create a
-    // zero Literal for such a type, as we'd need to supply the list of super
-    // types somehow, and creating a zero Literal is how makeZero works.
-    return false;
-  }
   if (type.isTuple()) {
     for (auto t : type) {
-      if (!canMakeZero(t)) {
+      if (t.isNonNullable()) {
         return false;
       }
     }
diff --git a/src/ir/local-structural-dominance.h b/src/ir/local-structural-dominance.h
new file mode 100644
index 0000000..e0cc2f3
--- /dev/null
+++ b/src/ir/local-structural-dominance.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_local_structural_dominance_h
+#define wasm_ir_local_structural_dominance_h
+
+#include "wasm.h"
+
+namespace wasm {
+
+//
+// This class is useful for the handling of non-nullable locals that is in the
+// wasm spec: a local.get validates if it is structurally dominated by a set.
+// That dominance proves the get cannot access the default null value, and,
+// nicely, it can be validated in linear time. (Historically, this was called
+// "1a" during spec discussions.)
+//
+// Concretely, this class finds which local indexes lack the structural
+// dominance property. It only looks at reference types and not anything else.
+// It can look at both nullable and non-nullable references, though, as it can
+// be used to validate non-nullable ones, and also to check if a nullable one
+// could become non-nullable and still validate.
+//
+// The property of "structural dominance" means that the set dominates the gets
+// using wasm's structured control flow constructs, like this:
+//
+//  (block $A
+//    (local.set $x ..)
+//    (local.get $x) ;; use in the same scope.
+//    (block $B
+//      (local.get $x) ;; use in an inner scope.
+//    )
+//  )
+//
+// That set structurally dominates both of those gets. However, in this example
+// it does not:
+//
+//  (block $A
+//    (local.set $x ..)
+//  )
+//  (local.get $x) ;; use in an outer scope.
+//
+// This is a little similar to breaks: (br $foo) can only go to a label $foo
+// that is in scope.
+//
+// Note that this property must hold on the final wasm binary, while we are
+// checking it on Binaryen IR. The property will carry over, however: when
+// lowering to wasm we may remove some Block nodes, but removing nodes cannot
+// break validation.
+//
+// In fact, since Blocks without names are not emitted in the binary format (we
+// never need them, since nothing can branch to them, so we just emit their
+// contents), we can ignore them here. That means that this will validate, which
+// is identical to the last example but the block has no name:
+//
+//  (block ;; no name here
+//    (local.set $x ..)
+//  )
+//  (local.get $x)
+//
+// It is useful to ignore such blocks here, as various passes emit them
+// temporarily.
+//
+struct LocalStructuralDominance {
+  // We always look at non-nullable locals, but can be configured to ignore
+  // or process nullable ones.
+  enum Mode {
+    All,
+    NonNullableOnly,
+  };
+
+  LocalStructuralDominance(Function* func, Module& wasm, Mode mode = All);
+
+  // Local indexes for whom a local.get exists that is not structurally
+  // dominated.
+  std::set<Index> nonDominatingIndices;
+};
+
+} // namespace wasm
+
+#endif // wasm_ir_local_structural_dominance_h
diff --git a/src/ir/manipulation.h b/src/ir/manipulation.h
index 54822f2..33c7d1b 100644
--- a/src/ir/manipulation.h
+++ b/src/ir/manipulation.h
@@ -41,6 +41,7 @@ template<typename InputType> inline Nop* nop(InputType* target) {
 
 template<typename InputType>
 inline RefNull* refNull(InputType* target, Type type) {
+  assert(type.isNullable() && type.getHeapType().isBottom());
   auto* ret = convert<InputType, RefNull>(target);
   ret->finalize(type);
   return ret;
diff --git a/src/ir/memory-utils.cpp b/src/ir/memory-utils.cpp
index b7c92fe..f1471b7 100644
--- a/src/ir/memory-utils.cpp
+++ b/src/ir/memory-utils.cpp
@@ -20,6 +20,10 @@
 namespace wasm::MemoryUtils {
 
 bool flatten(Module& wasm) {
+  // Flatten does not currently have support for multi-memories
+  if (wasm.memories.size() > 1) {
+    return false;
+  }
   // The presence of any MemoryInit instructions is a problem because they care
   // about segment identity, which flattening gets rid of ( when it merges them
   // all into one big segment).
@@ -37,34 +41,35 @@ bool flatten(Module& wasm) {
     }
   }
 
-  auto& memory = wasm.memory;
+  auto& dataSegments = wasm.dataSegments;
 
-  if (memory.segments.size() == 0) {
+  if (dataSegments.size() == 0) {
     return true;
   }
 
   std::vector<char> data;
-  for (auto& segment : memory.segments) {
-    if (segment.isPassive) {
+  for (auto& segment : dataSegments) {
+    if (segment->isPassive) {
       return false;
     }
-    auto* offset = segment.offset->dynCast<Const>();
+    auto* offset = segment->offset->dynCast<Const>();
     if (!offset) {
       return false;
     }
   }
-  for (auto& segment : memory.segments) {
-    auto* offset = segment.offset->dynCast<Const>();
+  for (auto& segment : dataSegments) {
+    auto* offset = segment->offset->dynCast<Const>();
     Index start = offset->value.getInteger();
-    Index end = start + segment.data.size();
+    Index end = start + segment->data.size();
     if (end > data.size()) {
       data.resize(end);
     }
-    std::copy(segment.data.begin(), segment.data.end(), data.begin() + start);
+    std::copy(segment->data.begin(), segment->data.end(), data.begin() + start);
   }
-  memory.segments.resize(1);
-  memory.segments[0].offset->cast<Const>()->value = Literal(int32_t(0));
-  memory.segments[0].data.swap(data);
+  dataSegments[0]->offset->cast<Const>()->value = Literal(int32_t(0));
+  dataSegments[0]->data.swap(data);
+  wasm.removeDataSegments(
+    [&](DataSegment* curr) { return curr->name != dataSegments[0]->name; });
 
   return true;
 }
diff --git a/src/ir/memory-utils.h b/src/ir/memory-utils.h
index 9b33253..9bdd002 100644
--- a/src/ir/memory-utils.h
+++ b/src/ir/memory-utils.h
@@ -30,21 +30,27 @@ namespace wasm::MemoryUtils {
 // Flattens memory into a single data segment, or no segment. If there is
 // a segment, it starts at 0.
 // Returns true if successful (e.g. relocatable segments cannot be flattened).
+// Does not yet support multi-memories
 bool flatten(Module& wasm);
 
-// Ensures that the memory exists (of minimal size).
-inline void ensureExists(Memory& memory) {
-  if (!memory.exists) {
-    memory.exists = true;
-    memory.initial = memory.max = 1;
+// Ensures that a memory exists (of minimal size).
+inline void ensureExists(Module* wasm) {
+  if (wasm->memories.empty()) {
+    auto memory = Builder::makeMemory("0");
+    memory->initial = memory->max = 1;
+    wasm->addMemory(std::move(memory));
   }
 }
 
 // Try to merge segments until they fit into web limitations.
 // Return true if successful.
+// Does not yet support multi-memories
 inline bool ensureLimitedSegments(Module& module) {
-  Memory& memory = module.memory;
-  if (memory.segments.size() <= WebLimitations::MaxDataSegments) {
+  if (module.memories.size() > 1) {
+    return false;
+  }
+  auto& dataSegments = module.dataSegments;
+  if (dataSegments.size() <= WebLimitations::MaxDataSegments) {
     return true;
   }
 
@@ -54,25 +60,23 @@ inline bool ensureLimitedSegments(Module& module) {
     return false;
   }
 
-  auto isEmpty = [](Memory::Segment& segment) {
-    return segment.data.size() == 0;
-  };
+  auto isEmpty = [](DataSegment& segment) { return segment.data.size() == 0; };
 
-  auto isConstantOffset = [](Memory::Segment& segment) {
+  auto isConstantOffset = [](DataSegment& segment) {
     return segment.offset && segment.offset->is<Const>();
   };
 
   Index numConstant = 0, numDynamic = 0;
   bool hasPassiveSegments = false;
-  for (auto& segment : memory.segments) {
-    if (!isEmpty(segment)) {
-      if (isConstantOffset(segment)) {
+  for (auto& segment : dataSegments) {
+    if (!isEmpty(*segment)) {
+      if (isConstantOffset(*segment)) {
         numConstant++;
       } else {
         numDynamic++;
       }
     }
-    hasPassiveSegments |= segment.isPassive;
+    hasPassiveSegments |= segment->isPassive;
   }
 
   if (hasPassiveSegments) {
@@ -93,43 +97,43 @@ inline bool ensureLimitedSegments(Module& module) {
     assert(num == WebLimitations::MaxDataSegments - 1);
   }
 
-  std::vector<Memory::Segment> mergedSegments;
+  std::vector<std::unique_ptr<wasm::DataSegment>> mergedSegments;
   mergedSegments.reserve(WebLimitations::MaxDataSegments);
 
   // drop empty segments and pass through dynamic-offset segments
-  for (auto& segment : memory.segments) {
-    if (isEmpty(segment)) {
+  for (auto& segment : dataSegments) {
+    if (isEmpty(*segment)) {
       continue;
     }
-    if (isConstantOffset(segment)) {
+    if (isConstantOffset(*segment)) {
       continue;
     }
-    mergedSegments.push_back(segment);
+    mergedSegments.push_back(std::move(segment));
   }
 
   // from here on, we concern ourselves with non-empty constant-offset
   // segments, the ones which we may need to merge
-  auto isRelevant = [&](Memory::Segment& segment) {
+  auto isRelevant = [&](DataSegment& segment) {
     return !isEmpty(segment) && isConstantOffset(segment);
   };
-  for (Index i = 0; i < memory.segments.size(); i++) {
-    auto& segment = memory.segments[i];
-    if (!isRelevant(segment)) {
+  for (Index i = 0; i < dataSegments.size(); i++) {
+    auto& segment = dataSegments[i];
+    if (!isRelevant(*segment)) {
       continue;
     }
     if (mergedSegments.size() + 2 < WebLimitations::MaxDataSegments) {
-      mergedSegments.push_back(segment);
+      mergedSegments.push_back(std::move(segment));
       continue;
     }
     // we can emit only one more segment! merge everything into one
     // start the combined segment at the bottom of them all
-    auto start = segment.offset->cast<Const>()->value.getInteger();
-    for (Index j = i + 1; j < memory.segments.size(); j++) {
-      auto& segment = memory.segments[j];
-      if (!isRelevant(segment)) {
+    auto start = segment->offset->cast<Const>()->value.getInteger();
+    for (Index j = i + 1; j < dataSegments.size(); j++) {
+      auto& segment = dataSegments[j];
+      if (!isRelevant(*segment)) {
         continue;
       }
-      auto offset = segment.offset->cast<Const>()->value.getInteger();
+      auto offset = segment->offset->cast<Const>()->value.getInteger();
       start = std::min(start, offset);
     }
     // create the segment and add in all the data
@@ -137,26 +141,29 @@ inline bool ensureLimitedSegments(Module& module) {
     c->value = Literal(int32_t(start));
     c->type = Type::i32;
 
-    Memory::Segment combined(c);
-    for (Index j = i; j < memory.segments.size(); j++) {
-      auto& segment = memory.segments[j];
-      if (!isRelevant(segment)) {
+    auto combined = Builder::makeDataSegment();
+    combined->memory = module.memories[0]->name;
+    combined->offset = c;
+    for (Index j = i; j < dataSegments.size(); j++) {
+      auto& segment = dataSegments[j];
+      if (!isRelevant(*segment)) {
         continue;
       }
-      auto offset = segment.offset->cast<Const>()->value.getInteger();
-      auto needed = offset + segment.data.size() - start;
-      if (combined.data.size() < needed) {
-        combined.data.resize(needed);
+      auto offset = segment->offset->cast<Const>()->value.getInteger();
+      auto needed = offset + segment->data.size() - start;
+      if (combined->data.size() < needed) {
+        combined->data.resize(needed);
       }
-      std::copy(segment.data.begin(),
-                segment.data.end(),
-                combined.data.begin() + (offset - start));
+      std::copy(segment->data.begin(),
+                segment->data.end(),
+                combined->data.begin() + (offset - start));
     }
-    mergedSegments.push_back(combined);
+    mergedSegments.push_back(std::move(combined));
     break;
   }
 
-  memory.segments.swap(mergedSegments);
+  dataSegments.swap(mergedSegments);
+  module.updateDataSegmentsMap();
   return true;
 }
 } // namespace wasm::MemoryUtils
diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp
index dc04cdb..a350f0e 100644
--- a/src/ir/module-splitting.cpp
+++ b/src/ir/module-splitting.cpp
@@ -143,11 +143,11 @@ void TableSlotManager::addSlot(Name func, Slot slot) {
 
 TableSlotManager::TableSlotManager(Module& module) : module(module) {
   // TODO: Reject or handle passive element segments
-  auto it = std::find_if(module.tables.begin(),
-                         module.tables.end(),
-                         [&](std::unique_ptr<Table>& table) {
-                           return table->type == Type::funcref;
-                         });
+  auto funcref = Type(HeapType::func, Nullable);
+  auto it = std::find_if(
+    module.tables.begin(),
+    module.tables.end(),
+    [&](std::unique_ptr<Table>& table) { return table->type == funcref; });
   if (it == module.tables.end()) {
     return;
   }
@@ -163,7 +163,7 @@ TableSlotManager::TableSlotManager(Module& module) : module(module) {
   // append new items at constant offsets after all existing items at constant
   // offsets.
   if (activeTableSegments.size() == 1 &&
-      activeTableSegments[0]->type == Type::funcref &&
+      activeTableSegments[0]->type == funcref &&
       !activeTableSegments[0]->offset->is<Const>()) {
     assert(activeTableSegments[0]->offset->is<GlobalGet>() &&
            "Unexpected initializer instruction");
@@ -353,7 +353,7 @@ void ModuleSplitter::exportImportFunction(Name funcName) {
       } while (primary.getExportOrNull(exportName) != nullptr);
     } else {
       exportName = Names::getValidExportName(
-        primary, config.newExportPrefix + funcName.c_str());
+        primary, config.newExportPrefix + funcName.toString());
     }
     primary.addExport(
       Builder::makeExport(exportName, funcName, ExternalKind::Function));
@@ -492,8 +492,7 @@ void ModuleSplitter::setupTablePatching() {
       placeholder->module = config.placeholderNamespace;
       placeholder->base = std::to_string(index);
       placeholder->name = Names::getValidFunctionName(
-        primary,
-        std::string("placeholder_") + std::string(placeholder->base.c_str()));
+        primary, std::string("placeholder_") + placeholder->base.toString());
       placeholder->hasExplicitName = false;
       placeholder->type = secondaryFunc->type;
       elem = placeholder->name;
@@ -612,14 +611,9 @@ void ModuleSplitter::shareImportableItems() {
   // TODO: Be more selective by only sharing global items that are actually used
   // in the secondary module, just like we do for functions.
 
-  if (primary.memory.exists) {
-    secondary.memory.exists = true;
-    secondary.memory.initial = primary.memory.initial;
-    secondary.memory.max = primary.memory.max;
-    secondary.memory.shared = primary.memory.shared;
-    secondary.memory.indexType = primary.memory.indexType;
-    makeImportExport(
-      primary.memory, secondary.memory, "memory", ExternalKind::Memory);
+  for (auto& memory : primary.memories) {
+    auto secondaryMemory = ModuleUtils::copyMemory(memory.get(), secondary);
+    makeImportExport(*memory, *secondaryMemory, "memory", ExternalKind::Memory);
   }
 
   for (auto& table : primary.tables) {
diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp
index bf1a452..c10a45b 100644
--- a/src/ir/module-utils.cpp
+++ b/src/ir/module-utils.cpp
@@ -40,6 +40,11 @@ struct Counts : public InsertOrderedMap<HeapType, size_t> {
       (*this)[type];
     }
   }
+  void include(Type type) {
+    for (HeapType ht : type.getHeapTypeChildren()) {
+      include(ht);
+    }
+  }
 };
 
 struct CodeScanner
@@ -53,28 +58,42 @@ struct CodeScanner
   void visitExpression(Expression* curr) {
     if (auto* call = curr->dynCast<CallIndirect>()) {
       counts.note(call->heapType);
+    } else if (auto* call = curr->dynCast<CallRef>()) {
+      counts.note(call->target->type);
     } else if (curr->is<RefNull>()) {
       counts.note(curr->type);
-    } else if (curr->is<RttCanon>() || curr->is<RttSub>()) {
-      counts.note(curr->type.getRtt().heapType);
-    } else if (auto* make = curr->dynCast<StructNew>()) {
-      handleMake(make);
-    } else if (auto* make = curr->dynCast<ArrayNew>()) {
-      handleMake(make);
-    } else if (auto* make = curr->dynCast<ArrayInit>()) {
-      handleMake(make);
+    } else if (curr->is<StructNew>()) {
+      counts.note(curr->type);
+    } else if (curr->is<ArrayNew>()) {
+      counts.note(curr->type);
+    } else if (curr->is<ArrayNewSeg>()) {
+      counts.note(curr->type);
+    } else if (curr->is<ArrayInit>()) {
+      counts.note(curr->type);
     } else if (auto* cast = curr->dynCast<RefCast>()) {
-      handleCast(cast);
+      counts.note(cast->intendedType);
     } else if (auto* cast = curr->dynCast<RefTest>()) {
-      handleCast(cast);
+      counts.note(cast->intendedType);
     } else if (auto* cast = curr->dynCast<BrOn>()) {
       if (cast->op == BrOnCast || cast->op == BrOnCastFail) {
-        handleCast(cast);
+        counts.note(cast->intendedType);
       }
     } else if (auto* get = curr->dynCast<StructGet>()) {
       counts.note(get->ref->type);
+      // If the type we read is a reference type then we must include it. It is
+      // not written in the binary format, so it doesn't need to be counted, but
+      // it does need to be taken into account in the IR (this may be the only
+      // place this type appears in the entire binary, and we must scan all
+      // types as the analyses that use us depend on that).
+      counts.include(get->type);
     } else if (auto* set = curr->dynCast<StructSet>()) {
       counts.note(set->ref->type);
+    } else if (auto* get = curr->dynCast<ArrayGet>()) {
+      counts.note(get->ref->type);
+      // See note on StructGet above.
+      counts.include(get->type);
+    } else if (auto* set = curr->dynCast<ArraySet>()) {
+      counts.note(set->ref->type);
     } else if (Properties::isControlFlowStructure(curr)) {
       if (curr->type.isTuple()) {
         // TODO: Allow control flow to have input types as well
@@ -84,26 +103,15 @@ struct CodeScanner
       }
     }
   }
-
-  template<typename T> void handleMake(T* curr) {
-    if (!curr->rtt && curr->type != Type::unreachable) {
-      counts.note(curr->type.getHeapType());
-    }
-  }
-
-  template<typename T> void handleCast(T* curr) {
-    // Some operations emit a HeapType in the binary format, if they are
-    // static and not dynamic (if dynamic, the RTT provides the heap type).
-    if (!curr->rtt) {
-      counts.note(curr->intendedType);
-    }
-  }
 };
 
 Counts getHeapTypeCounts(Module& wasm) {
   // Collect module-level info.
   Counts counts;
   CodeScanner(wasm, counts).walkModuleCode(&wasm);
+  for (auto& curr : wasm.globals) {
+    counts.note(curr->type);
+  }
   for (auto& curr : wasm.tags) {
     counts.note(curr->sig);
   }
diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h
index 5030bf8..f68e2a9 100644
--- a/src/ir/module-utils.h
+++ b/src/ir/module-utils.h
@@ -106,6 +106,32 @@ inline Table* copyTable(const Table* table, Module& out) {
   return out.addTable(std::move(ret));
 }
 
+inline Memory* copyMemory(const Memory* memory, Module& out) {
+  auto ret = Builder::makeMemory(memory->name);
+  ret->hasExplicitName = memory->hasExplicitName;
+  ret->initial = memory->initial;
+  ret->max = memory->max;
+  ret->shared = memory->shared;
+  ret->indexType = memory->indexType;
+
+  return out.addMemory(std::move(ret));
+}
+
+inline DataSegment* copyDataSegment(const DataSegment* segment, Module& out) {
+  auto ret = Builder::makeDataSegment();
+  ret->name = segment->name;
+  ret->hasExplicitName = segment->hasExplicitName;
+  ret->memory = segment->memory;
+  ret->isPassive = segment->isPassive;
+  if (!segment->isPassive) {
+    auto offset = ExpressionManipulator::copy(segment->offset, out);
+    ret->offset = offset;
+  }
+  ret->data = segment->data;
+
+  return out.addDataSegment(std::move(ret));
+}
+
 inline void copyModule(const Module& in, Module& out) {
   // we use names throughout, not raw pointers, so simple copying is fine
   // for everything *but* expressions
@@ -127,10 +153,11 @@ inline void copyModule(const Module& in, Module& out) {
   for (auto& curr : in.tables) {
     copyTable(curr.get(), out);
   }
-
-  out.memory = in.memory;
-  for (auto& segment : out.memory.segments) {
-    segment.offset = ExpressionManipulator::copy(segment.offset, out);
+  for (auto& curr : in.memories) {
+    copyMemory(curr.get(), out);
+  }
+  for (auto& curr : in.dataSegments) {
+    copyDataSegment(curr.get(), out);
   }
   out.start = in.start;
   out.userSections = in.userSections;
@@ -149,40 +176,45 @@ inline void clearModule(Module& wasm) {
 // Rename functions along with all their uses.
 // Note that for this to work the functions themselves don't necessarily need
 // to exist.  For example, it is possible to remove a given function and then
-// call this redirect all of its uses.
+// call this to redirect all of its uses.
 template<typename T> inline void renameFunctions(Module& wasm, T& map) {
   // Update the function itself.
   for (auto& [oldName, newName] : map) {
-    if (Function* F = wasm.getFunctionOrNull(oldName)) {
-      assert(!wasm.getFunctionOrNull(newName) || F->name == newName);
-      F->name = newName;
+    if (Function* func = wasm.getFunctionOrNull(oldName)) {
+      assert(!wasm.getFunctionOrNull(newName) || func->name == newName);
+      func->name = newName;
     }
   }
   wasm.updateMaps();
-  // Update other global things.
-  auto maybeUpdate = [&](Name& name) {
-    auto iter = map.find(name);
-    if (iter != map.end()) {
-      name = iter->second;
-    }
-  };
-  maybeUpdate(wasm.start);
-  ElementUtils::iterAllElementFunctionNames(&wasm, maybeUpdate);
-  for (auto& exp : wasm.exports) {
-    if (exp->kind == ExternalKind::Function) {
-      maybeUpdate(exp->value);
-    }
-  }
-  // Update call instructions.
-  for (auto& func : wasm.functions) {
-    // TODO: parallelize
-    if (!func->imported()) {
-      FindAll<Call> calls(func->body);
-      for (auto* call : calls.list) {
-        maybeUpdate(call->target);
+
+  // Update all references to it.
+  struct Updater : public WalkerPass<PostWalker<Updater>> {
+    bool isFunctionParallel() override { return true; }
+
+    T& map;
+
+    void maybeUpdate(Name& name) {
+      if (auto iter = map.find(name); iter != map.end()) {
+        name = iter->second;
       }
     }
-  }
+
+    Updater(T& map) : map(map) {}
+
+    std::unique_ptr<Pass> create() override {
+      return std::make_unique<Updater>(map);
+    }
+
+    void visitCall(Call* curr) { maybeUpdate(curr->target); }
+
+    void visitRefFunc(RefFunc* curr) { maybeUpdate(curr->func); }
+  };
+
+  Updater updater(map);
+  updater.maybeUpdate(wasm.start);
+  PassRunner runner(&wasm);
+  updater.run(&runner, &wasm);
+  updater.runOnModuleCode(&runner, &wasm);
 }
 
 inline void renameFunction(Module& wasm, Name oldName, Name newName) {
@@ -194,14 +226,36 @@ inline void renameFunction(Module& wasm, Name oldName, Name newName) {
 // Convenient iteration over imported/non-imported module elements
 
 template<typename T> inline void iterImportedMemories(Module& wasm, T visitor) {
-  if (wasm.memory.exists && wasm.memory.imported()) {
-    visitor(&wasm.memory);
+  for (auto& import : wasm.memories) {
+    if (import->imported()) {
+      visitor(import.get());
+    }
   }
 }
 
 template<typename T> inline void iterDefinedMemories(Module& wasm, T visitor) {
-  if (wasm.memory.exists && !wasm.memory.imported()) {
-    visitor(&wasm.memory);
+  for (auto& import : wasm.memories) {
+    if (!import->imported()) {
+      visitor(import.get());
+    }
+  }
+}
+
+template<typename T>
+inline void iterMemorySegments(Module& wasm, Name memory, T visitor) {
+  for (auto& segment : wasm.dataSegments) {
+    if (!segment->isPassive && segment->memory == memory) {
+      visitor(segment.get());
+    }
+  }
+}
+
+template<typename T>
+inline void iterActiveDataSegments(Module& wasm, T visitor) {
+  for (auto& segment : wasm.dataSegments) {
+    if (!segment->isPassive) {
+      visitor(segment.get());
+    }
   }
 }
 
@@ -314,10 +368,10 @@ template<typename T,
 struct ParallelFunctionAnalysis {
   Module& wasm;
 
-  typedef MapT<Function*, T> Map;
+  using Map = MapT<Function*, T>;
   Map map;
 
-  typedef std::function<void(Function*, T&)> Func;
+  using Func = std::function<void(Function*, T&)>;
 
   ParallelFunctionAnalysis(Module& wasm, Func work) : wasm(wasm) {
     // Fill in map, as we operate on it in parallel (each function to its own
@@ -340,7 +394,9 @@ struct ParallelFunctionAnalysis {
       Mapper(Module& module, Map& map, Func work)
         : module(module), map(map), work(work) {}
 
-      Mapper* create() override { return new Mapper(module, map, work); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<Mapper>(module, map, work);
+      }
 
       void doWalkFunction(Function* curr) {
         assert(map.count(curr));
@@ -382,10 +438,10 @@ template<typename T> struct CallGraphPropertyAnalysis {
     bool hasNonDirectCall = false;
   };
 
-  typedef std::map<Function*, T> Map;
+  using Map = std::map<Function*, T>;
   Map map;
 
-  typedef std::function<void(Function*, T&)> Func;
+  using Func = std::function<void(Function*, T&)>;
 
   CallGraphPropertyAnalysis(Module& wasm, Func work) : wasm(wasm) {
     ParallelFunctionAnalysis<T> analysis(wasm, [&](Function* func, T& info) {
diff --git a/src/ir/names.h b/src/ir/names.h
index af6ca15..b2165c2 100644
--- a/src/ir/names.h
+++ b/src/ir/names.h
@@ -83,6 +83,14 @@ inline Name getValidElementSegmentName(Module& module, Name root) {
   return getValidName(
     root, [&](Name test) { return !module.getElementSegmentOrNull(test); });
 }
+inline Name getValidMemoryName(Module& module, Name root) {
+  return getValidName(root,
+                      [&](Name test) { return !module.getMemoryOrNull(test); });
+}
+inline Name getValidLocalName(Function& func, Name root) {
+  return getValidName(root,
+                      [&](Name test) { return !func.hasLocalIndex(test); });
+}
 
 class MinifiedNameGenerator {
   size_t state = 0;
diff --git a/src/ir/ordering.h b/src/ir/ordering.h
index 5516375..bc8d690 100644
--- a/src/ir/ordering.h
+++ b/src/ir/ordering.h
@@ -34,15 +34,29 @@ namespace wasm {
 //
 //   (temp = first, second, temp)
 //
-Expression* getResultOfFirst(Expression* first,
-                             Expression* second,
-                             Function* func,
-                             Module* wasm,
-                             const PassOptions& passOptions) {
+// The first expression is assumed to not be unreachable (otherwise, there is no
+// value to get the result of). If the second is unreachable, this returns
+// something with type unreachable (that avoids returning something with a
+// concrete type, which might replace something with unreachable type - we want
+// to keep the type the same, in most cases).
+inline Expression* getResultOfFirst(Expression* first,
+                                    Expression* second,
+                                    Function* func,
+                                    Module* wasm,
+                                    const PassOptions& passOptions) {
   assert(first->type.isConcrete());
 
   Builder builder(*wasm);
 
+  if (second->type == Type::unreachable) {
+    // No value is actually consumed here. Emit something with unreachable type.
+    // (Note that if we continued to the canReorder code after us, and emitted
+    // second followed by first, then the block would have a concrete type due
+    // to the last element having such a type - which would not have unreachable
+    // type.)
+    return builder.makeSequence(builder.makeDrop(first), second);
+  }
+
   if (EffectAnalyzer::canReorder(passOptions, *wasm, first, second)) {
     return builder.makeSequence(second, first);
   }
diff --git a/src/ir/possible-constant.h b/src/ir/possible-constant.h
index c75669d..21f1cfa 100644
--- a/src/ir/possible-constant.h
+++ b/src/ir/possible-constant.h
@@ -72,22 +72,9 @@ public:
 
   // Note either a Literal or a Name.
   template<typename T> void note(T curr) {
-    if (std::get_if<None>(&value)) {
-      // This is the first value.
-      value = curr;
-      return;
-    }
-
-    if (std::get_if<Many>(&value)) {
-      // This was already representing multiple values; nothing changes.
-      return;
-    }
-
-    // This is a subsequent value. Check if it is different from all previous
-    // ones.
-    if (Variant(curr) != value) {
-      noteUnknown();
-    }
+    PossibleConstantValues other;
+    other.value = curr;
+    combine(other);
   }
 
   // Notes a value that is unknown - it can be anything. We have failed to
@@ -118,6 +105,28 @@ public:
       return true;
     }
 
+    // Nulls compare equal, and we could consider any of the input nulls as the
+    // combination of the two (as any of them would be valid to place in the
+    // location we are working to optimize). In order to have simple symmetric
+    // behavior here, which does not depend on the order of the inputs, use the
+    // LUB.
+    if (isNull() && other.isNull()) {
+      auto type = getConstantLiteral().type.getHeapType();
+      auto otherType = other.getConstantLiteral().type.getHeapType();
+      auto lub = HeapType::getLeastUpperBound(type, otherType);
+      if (!lub) {
+        // TODO: Remove this workaround once we have bottom types to assign to
+        // null literals.
+        value = Many();
+        return true;
+      }
+      if (*lub != type) {
+        value = Literal::makeNull(*lub);
+        return true;
+      }
+      return false;
+    }
+
     return false;
   }
 
@@ -130,6 +139,10 @@ public:
 
   bool isConstantGlobal() const { return std::get_if<Name>(&value); }
 
+  bool isNull() const {
+    return isConstantLiteral() && getConstantLiteral().isNull();
+  }
+
   // Returns the single constant value.
   Literal getConstantLiteral() const {
     assert(isConstant());
@@ -155,7 +168,7 @@ public:
   // Returns whether we have ever noted a value.
   bool hasNoted() const { return !std::get_if<None>(&value); }
 
-  void dump(std::ostream& o) {
+  void dump(std::ostream& o) const {
     o << '[';
     if (!hasNoted()) {
       o << "unwritten";
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
new file mode 100644
index 0000000..0284101
--- /dev/null
+++ b/src/ir/possible-contents.cpp
@@ -0,0 +1,2142 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <optional>
+#include <variant>
+
+#include "ir/branch-utils.h"
+#include "ir/eh-utils.h"
+#include "ir/local-graph.h"
+#include "ir/module-utils.h"
+#include "ir/possible-contents.h"
+#include "support/insert_ordered.h"
+#include "wasm.h"
+
+namespace std {
+
+std::ostream& operator<<(std::ostream& stream,
+                         const wasm::PossibleContents& contents) {
+  contents.dump(stream);
+  return stream;
+}
+
+} // namespace std
+
+namespace wasm {
+
+PossibleContents PossibleContents::combine(const PossibleContents& a,
+                                           const PossibleContents& b) {
+  auto aType = a.getType();
+  auto bType = b.getType();
+  // First handle the trivial cases of them being equal, or one of them is
+  // None or Many.
+  if (a == b) {
+    return a;
+  }
+  if (b.isNone()) {
+    return a;
+  }
+  if (a.isNone()) {
+    return b;
+  }
+  if (a.isMany()) {
+    return a;
+  }
+  if (b.isMany()) {
+    return b;
+  }
+
+  if (!aType.isRef() || !bType.isRef()) {
+    // At least one is not a reference. The only possibility left for a useful
+    // combination here is if they have the same type (since we've already ruled
+    // out the case of them being equal). If they have the same type then
+    // neither is a reference and we can emit an exact type (since subtyping is
+    // not relevant for non-references).
+    if (aType == bType) {
+      return ExactType(aType);
+    } else {
+      return Many();
+    }
+  }
+
+  // Special handling for references from here.
+
+  if (a.isNull() && b.isNull()) {
+    // These must be nulls in different hierarchies, otherwise a would have
+    // been handled by the `a == b` case above.
+    assert(aType != bType);
+    return Many();
+  }
+
+  auto lub = Type::getLeastUpperBound(aType, bType);
+  if (lub == Type::none) {
+    // The types are not in the same hierarchy.
+    return Many();
+  }
+
+  // From here we can assume there is a useful LUB.
+
+  // Nulls can be combined in by just adding nullability to a type.
+  if (a.isNull() || b.isNull()) {
+    // Only one of them can be null here, since we already handled the case
+    // where they were both null.
+    assert(!a.isNull() || !b.isNull());
+    // If only one is a null then we can use the type info from the b, and
+    // just add in nullability. For example, a literal of type T and a null
+    // becomes an exact type of T that allows nulls, and so forth.
+    auto mixInNull = [](ConeType cone) {
+      cone.type = Type(cone.type.getHeapType(), Nullable);
+      return cone;
+    };
+    if (!a.isNull()) {
+      return mixInNull(a.getCone());
+    } else if (!b.isNull()) {
+      return mixInNull(b.getCone());
+    }
+  }
+
+  // Find a ConeType that describes both inputs, using the shared ancestor which
+  // is the LUB. We need to find how big a cone we need: the cone must be big
+  // enough to contain both the inputs.
+  auto aDepth = a.getCone().depth;
+  auto bDepth = b.getCone().depth;
+  Index newDepth;
+  if (aDepth == FullDepth || bDepth == FullDepth) {
+    // At least one has full (infinite) depth, so we know the new depth must
+    // be the same.
+    newDepth = FullDepth;
+  } else {
+    // The depth we need under the lub is how far from the lub we are, plus
+    // the depth of our cone.
+    // TODO: we could make a single loop that also does the LUB, at the same
+    // time, and also avoids calling getDepth() which loops once more?
+    auto aDepthFromRoot = aType.getHeapType().getDepth();
+    auto bDepthFromRoot = bType.getHeapType().getDepth();
+    auto lubDepthFromRoot = lub.getHeapType().getDepth();
+    assert(lubDepthFromRoot <= aDepthFromRoot);
+    assert(lubDepthFromRoot <= bDepthFromRoot);
+    Index aDepthUnderLub = aDepthFromRoot - lubDepthFromRoot + aDepth;
+    Index bDepthUnderLub = bDepthFromRoot - lubDepthFromRoot + bDepth;
+
+    // The total cone must be big enough to contain all the above.
+    newDepth = std::max(aDepthUnderLub, bDepthUnderLub);
+  }
+
+  return ConeType{lub, newDepth};
+}
+
+void PossibleContents::intersectWithFullCone(const PossibleContents& other) {
+  assert(other.isFullConeType());
+
+  if (isSubContents(other, *this)) {
+    // The intersection is just |other|.
+    // Note that this code path handles |this| being Many.
+    value = other.value;
+    return;
+  }
+
+  if (!haveIntersection(*this, other)) {
+    // There is no intersection at all.
+    // Note that this code path handles |this| being None.
+    value = None();
+    return;
+  }
+
+  // There is an intersection here. Note that this implies |this| is a reference
+  // type, as it has an intersection with |other| which is a full cone type
+  // (which must be a reference type).
+  auto type = getType();
+  auto otherType = other.getType();
+  auto heapType = type.getHeapType();
+  auto otherHeapType = otherType.getHeapType();
+
+  // If both inputs are nullable then the intersection is nullable as well.
+  auto nullability =
+    type.isNullable() && otherType.isNullable() ? Nullable : NonNullable;
+
+  auto setNoneOrNull = [&]() {
+    if (nullability == Nullable) {
+      value = Literal::makeNull(otherHeapType);
+    } else {
+      value = None();
+    }
+  };
+
+  if (isNull()) {
+    // The intersection is either this null itself, or nothing if a null is not
+    // allowed.
+    setNoneOrNull();
+    return;
+  }
+
+  // If the heap types are not compatible then they are in separate hierarchies
+  // and there is no intersection, aside from possibly a null of the bottom
+  // type.
+  auto isSubType = HeapType::isSubType(heapType, otherHeapType);
+  auto otherIsSubType = HeapType::isSubType(otherHeapType, heapType);
+  if (!isSubType && !otherIsSubType) {
+    if (nullability == Nullable &&
+        heapType.getBottom() == otherHeapType.getBottom()) {
+      value = Literal::makeNull(heapType.getBottom());
+    } else {
+      value = None();
+    }
+    return;
+  }
+
+  if (isLiteral() || isGlobal()) {
+    // The information about the value being identical to a particular literal
+    // or immutable global is not removed by intersection, if the type is in the
+    // cone we are intersecting with.
+    if (isSubType) {
+      return;
+    }
+
+    // The type must change, so continue down to the generic code path.
+    // TODO: for globals we could perhaps refine the type here, but then the
+    //       type on GlobalInfo would not match the module, so that needs some
+    //       refactoring.
+  }
+
+  // Intersect the cones, as there is no more specific information we can use.
+  auto depthFromRoot = heapType.getDepth();
+  auto otherDepthFromRoot = otherHeapType.getDepth();
+
+  // To compute the new cone, find the new heap type for it, and to compute its
+  // depth, consider the adjustments to the existing depths that stem from the
+  // choice of new heap type.
+  HeapType newHeapType;
+
+  if (depthFromRoot < otherDepthFromRoot) {
+    newHeapType = otherHeapType;
+  } else {
+    newHeapType = heapType;
+  }
+
+  auto newType = Type(newHeapType, nullability);
+
+  // By assumption |other| has full depth. Consider the other cone in |this|.
+  if (hasFullCone()) {
+    // Both are full cones, so the result is as well.
+    value = FullConeType(newType);
+  } else {
+    // The result is a partial cone. If the cone starts in |otherHeapType| then
+    // we need to adjust the depth down, since it will be smaller than the
+    // original cone:
+    /*
+    //                             ..
+    //                            /
+    //              otherHeapType
+    //            /               \
+    //   heapType                  ..
+    //            \
+    */
+    // E.g. if |this| is a cone of depth 10, and |otherHeapType| is an immediate
+    // subtype of |this|, then the new cone must be of depth 9.
+    auto newDepth = getCone().depth;
+    if (newHeapType == otherHeapType) {
+      assert(depthFromRoot <= otherDepthFromRoot);
+      auto reduction = otherDepthFromRoot - depthFromRoot;
+      if (reduction > newDepth) {
+        // The cone on heapType does not even reach the cone on otherHeapType,
+        // so the result is not a cone.
+        setNoneOrNull();
+        return;
+      }
+      newDepth -= reduction;
+    }
+
+    value = ConeType{newType, newDepth};
+  }
+}
+
+bool PossibleContents::haveIntersection(const PossibleContents& a,
+                                        const PossibleContents& b) {
+  if (a.isNone() || b.isNone()) {
+    // One is the empty set, so nothing can intersect here.
+    return false;
+  }
+
+  if (a.isMany() || b.isMany()) {
+    // One is the set of all things, so definitely something can intersect since
+    // we've ruled out an empty set for both.
+    return true;
+  }
+
+  auto aType = a.getType();
+  auto bType = b.getType();
+
+  if (!aType.isRef() || !bType.isRef()) {
+    // At least one is not a reference. The only way they can intersect is if
+    // the type is identical.
+    return aType == bType;
+  }
+
+  // From here on we focus on references.
+
+  auto aHeapType = aType.getHeapType();
+  auto bHeapType = bType.getHeapType();
+
+  if (aType.isNullable() && bType.isNullable() &&
+      aHeapType.getBottom() == bHeapType.getBottom()) {
+    // A compatible null is possible on both sides.
+    return true;
+  }
+
+  // We ruled out having a compatible null on both sides. If one is simply a
+  // null then no chance for an intersection remains.
+  if (a.isNull() || b.isNull()) {
+    return false;
+  }
+
+  auto aSubB = HeapType::isSubType(aHeapType, bHeapType);
+  auto bSubA = HeapType::isSubType(bHeapType, aHeapType);
+  if (!aSubB && !bSubA) {
+    // No type can appear in both a and b, so the types differ, so the values
+    // do not overlap.
+    return false;
+  }
+
+  // From here on we focus on references and can ignore the case of null - any
+  // intersection must be of a non-null value, so we can focus on the heap
+  // types.
+
+  auto aDepthFromRoot = aHeapType.getDepth();
+  auto bDepthFromRoot = bHeapType.getDepth();
+
+  if (aSubB) {
+    // A is a subtype of B. For there to be an intersection we need their cones
+    // to intersect, that is, to rule out the case where the cone from B is not
+    // deep enough to reach A.
+    assert(aDepthFromRoot >= bDepthFromRoot);
+    return aDepthFromRoot - bDepthFromRoot <= b.getCone().depth;
+  } else if (bSubA) {
+    assert(bDepthFromRoot >= aDepthFromRoot);
+    return bDepthFromRoot - aDepthFromRoot <= a.getCone().depth;
+  } else {
+    WASM_UNREACHABLE("we ruled out no subtyping before");
+  }
+
+  // TODO: we can also optimize things like different Literals, but existing
+  //       passes do such things already so it is low priority.
+}
+
+bool PossibleContents::isSubContents(const PossibleContents& a,
+                                     const PossibleContents& b) {
+  // TODO: Everything else. For now we only call this when |a| or |b| is a full
+  //       cone type.
+  if (b.isFullConeType()) {
+    if (a.isNone()) {
+      return true;
+    }
+    if (a.isMany()) {
+      return false;
+    }
+    if (a.isNull()) {
+      return b.getType().isNullable();
+    }
+    return Type::isSubType(a.getType(), b.getType());
+  }
+
+  if (a.isFullConeType()) {
+    // We've already ruled out b being a full cone type before, so the only way
+    // |a| can be contained in |b| is if |b| is everything.
+    return b.isMany();
+  }
+
+  WASM_UNREACHABLE("a or b must be a full cone");
+}
+
+namespace {
+
+// We are going to do a very large flow operation, potentially, as we create
+// a Location for every interesting part in the entire wasm, and some of those
+// places will have lots of links (like a struct field may link out to every
+// single struct.get of that type), so we must make the data structures here
+// as efficient as possible. Towards that goal, we work with location
+// *indexes* where possible, which are small (32 bits) and do not require any
+// complex hashing when we use them in sets or maps.
+//
+// Note that we do not use indexes everywhere, since the initial analysis is
+// done in parallel, and we do not have a fixed indexing of locations yet. When
+// we merge the parallel data we create that indexing, and use indexes from then
+// on.
+using LocationIndex = uint32_t;
+
+#ifndef NDEBUG
+// Assert on not having duplicates in a vector.
+template<typename T> void disallowDuplicates(const T& targets) {
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  std::unordered_set<LocationIndex> uniqueTargets;
+  for (const auto& target : targets) {
+    uniqueTargets.insert(target);
+  }
+  assert(uniqueTargets.size() == targets.size());
+#endif
+}
+#endif
+
+// A link indicates a flow of content from one location to another. For
+// example, if we do a local.get and return that value from a function, then
+// we have a link from the ExpressionLocation of that local.get to a
+// ResultLocation.
+template<typename T> struct Link {
+  T from;
+  T to;
+
+  bool operator==(const Link<T>& other) const {
+    return from == other.from && to == other.to;
+  }
+};
+
+using LocationLink = Link<Location>;
+using IndexLink = Link<LocationIndex>;
+
+} // anonymous namespace
+
+} // namespace wasm
+
+namespace std {
+
+template<> struct hash<wasm::LocationLink> {
+  size_t operator()(const wasm::LocationLink& loc) const {
+    return std::hash<std::pair<wasm::Location, wasm::Location>>{}(
+      {loc.from, loc.to});
+  }
+};
+
+template<> struct hash<wasm::IndexLink> {
+  size_t operator()(const wasm::IndexLink& loc) const {
+    return std::hash<std::pair<wasm::LocationIndex, wasm::LocationIndex>>{}(
+      {loc.from, loc.to});
+  }
+};
+
+} // namespace std
+
+namespace wasm {
+
+namespace {
+
+// The data we gather from each function, as we process them in parallel. Later
+// this will be merged into a single big graph.
+struct CollectedFuncInfo {
+  // All the links we found in this function. Rarely are there duplicates
+  // in this list (say when writing to the same global location from another
+  // global location), and we do not try to deduplicate here, just store them in
+  // a plain array for now, which is faster (later, when we merge all the info
+  // from the functions, we need to deduplicate anyhow).
+  std::vector<LocationLink> links;
+
+  // All the roots of the graph, that is, places that begin by containing some
+  // particular content. That includes i32.const, ref.func, struct.new, etc. All
+  // possible contents in the rest of the graph flow from such places.
+  //
+  // The vector here is of the location of the root and then its contents.
+  std::vector<std::pair<Location, PossibleContents>> roots;
+
+  // In some cases we need to know the parent of the expression. Consider this:
+  //
+  //  (struct.set $A k
+  //    (local.get $ref)
+  //    (local.get $value)
+  //  )
+  //
+  // Imagine that the first local.get, for $ref, receives a new value. That can
+  // affect where the struct.set sends values: if previously that local.get had
+  // no possible contents, and now it does, then we have DataLocations to
+  // update. Likewise, when the second local.get is updated we must do the same,
+  // but again which DataLocations we update depends on the ref passed to the
+  // struct.set. To handle such things, we set add a childParent link, and then
+  // when we update the child we can find the parent and handle any special
+  // behavior we need there.
+  std::unordered_map<Expression*, Expression*> childParents;
+};
+
+// Walk the wasm and find all the links we need to care about, and the locations
+// and roots related to them. This builds up a CollectedFuncInfo data structure.
+// After all InfoCollectors run, those data structures will be merged and the
+// main flow will begin.
+struct InfoCollector
+  : public PostWalker<InfoCollector, OverriddenVisitor<InfoCollector>> {
+  CollectedFuncInfo& info;
+
+  InfoCollector(CollectedFuncInfo& info) : info(info) {}
+
+  // Check if a type is relevant for us. If not, we can ignore it entirely.
+  bool isRelevant(Type type) {
+    if (type == Type::unreachable || type == Type::none) {
+      return false;
+    }
+    if (type.isTuple()) {
+      for (auto t : type) {
+        if (isRelevant(t)) {
+          return true;
+        }
+      }
+    }
+    if (type.isRef() && getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      // We need explicit supers in the SubTyping helper class. Without that,
+      // cannot handle refs, and consider them irrelevant.
+      return false;
+    }
+    return true;
+  }
+
+  bool isRelevant(Signature sig) {
+    return isRelevant(sig.params) || isRelevant(sig.results);
+  }
+
+  bool isRelevant(Expression* curr) { return curr && isRelevant(curr->type); }
+
+  template<typename T> bool isRelevant(const T& vec) {
+    for (auto* expr : vec) {
+      if (isRelevant(expr->type)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Each visit*() call is responsible for connecting the children of a node to
+  // that node. Responsibility for connecting the node's output to anywhere
+  // else (another expression or the function itself, if we are at the top
+  // level) is the responsibility of the outside.
+
+  void visitBlock(Block* curr) {
+    if (curr->list.empty()) {
+      return;
+    }
+
+    // Values sent to breaks to this block must be received here.
+    handleBreakTarget(curr);
+
+    // The final item in the block can flow a value to here as well.
+    receiveChildValue(curr->list.back(), curr);
+  }
+  void visitIf(If* curr) {
+    // Each arm may flow out a value.
+    receiveChildValue(curr->ifTrue, curr);
+    receiveChildValue(curr->ifFalse, curr);
+  }
+  void visitLoop(Loop* curr) { receiveChildValue(curr->body, curr); }
+  void visitBreak(Break* curr) {
+    // Connect the value (if present) to the break target.
+    handleBreakValue(curr);
+
+    // The value may also flow through in a br_if (the type will indicate that,
+    // which receiveChildValue will notice).
+    receiveChildValue(curr->value, curr);
+  }
+  void visitSwitch(Switch* curr) { handleBreakValue(curr); }
+  void visitLoad(Load* curr) {
+    // We could infer the exact type here, but as no subtyping is possible, it
+    // would have no benefit, so just add a generic root (which will be "Many").
+    // See the comment on the ContentOracle class.
+    addRoot(curr);
+  }
+  void visitStore(Store* curr) {}
+  void visitAtomicRMW(AtomicRMW* curr) { addRoot(curr); }
+  void visitAtomicCmpxchg(AtomicCmpxchg* curr) { addRoot(curr); }
+  void visitAtomicWait(AtomicWait* curr) { addRoot(curr); }
+  void visitAtomicNotify(AtomicNotify* curr) { addRoot(curr); }
+  void visitAtomicFence(AtomicFence* curr) {}
+  void visitSIMDExtract(SIMDExtract* curr) { addRoot(curr); }
+  void visitSIMDReplace(SIMDReplace* curr) { addRoot(curr); }
+  void visitSIMDShuffle(SIMDShuffle* curr) { addRoot(curr); }
+  void visitSIMDTernary(SIMDTernary* curr) { addRoot(curr); }
+  void visitSIMDShift(SIMDShift* curr) { addRoot(curr); }
+  void visitSIMDLoad(SIMDLoad* curr) { addRoot(curr); }
+  void visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { addRoot(curr); }
+  void visitMemoryInit(MemoryInit* curr) {}
+  void visitDataDrop(DataDrop* curr) {}
+  void visitMemoryCopy(MemoryCopy* curr) {}
+  void visitMemoryFill(MemoryFill* curr) {}
+  void visitConst(Const* curr) {
+    addRoot(curr, PossibleContents::literal(curr->value));
+  }
+  void visitUnary(Unary* curr) {
+    // We could optimize cases like this using interpreter integration: if the
+    // input is a Literal, we could interpret the Literal result. However, if
+    // the input is a literal then the GUFA pass will emit a Const there, and
+    // the Precompute pass can use that later to interpret a result. That is,
+    // the input we need here, a constant, is already something GUFA can emit as
+    // an output. As a result, integrating the interpreter here would perhaps
+    // make compilation require fewer steps, but it wouldn't let us optimize
+    // more than we could before.
+    addRoot(curr);
+  }
+  void visitBinary(Binary* curr) { addRoot(curr); }
+  void visitSelect(Select* curr) {
+    receiveChildValue(curr->ifTrue, curr);
+    receiveChildValue(curr->ifFalse, curr);
+  }
+  void visitDrop(Drop* curr) {}
+  void visitMemorySize(MemorySize* curr) { addRoot(curr); }
+  void visitMemoryGrow(MemoryGrow* curr) { addRoot(curr); }
+  void visitRefNull(RefNull* curr) {
+    addRoot(
+      curr,
+      PossibleContents::literal(Literal::makeNull(curr->type.getHeapType())));
+  }
+  void visitRefIs(RefIs* curr) {
+    // TODO: Optimize when possible. For example, if we can infer an exact type
+    //       here which allows us to know the result then we should do so. This
+    //       is unlike the case in visitUnary, above: the information that lets
+    //       us optimize *cannot* be written into Binaryen IR (unlike a Literal)
+    //       so using it during this pass allows us to optimize new things.
+    addRoot(curr);
+  }
+  void visitRefFunc(RefFunc* curr) {
+    addRoot(
+      curr,
+      PossibleContents::literal(Literal(curr->func, curr->type.getHeapType())));
+
+    // The presence of a RefFunc indicates the function may be called
+    // indirectly, so add the relevant connections for this particular function.
+    // We do so here in the RefFunc so that we only do it for functions that
+    // actually have a RefFunc.
+    auto* func = getModule()->getFunction(curr->func);
+    for (Index i = 0; i < func->getParams().size(); i++) {
+      info.links.push_back(
+        {SignatureParamLocation{func->type, i}, ParamLocation{func, i}});
+    }
+    for (Index i = 0; i < func->getResults().size(); i++) {
+      info.links.push_back(
+        {ResultLocation{func, i}, SignatureResultLocation{func->type, i}});
+    }
+  }
+  void visitRefEq(RefEq* curr) {
+    addRoot(curr);
+  }
+  void visitTableGet(TableGet* curr) {
+    addRoot(curr);
+  }
+  void visitTableSet(TableSet* curr) {}
+  void visitTableSize(TableSize* curr) { addRoot(curr); }
+  void visitTableGrow(TableGrow* curr) { addRoot(curr); }
+
+  void visitNop(Nop* curr) {}
+  void visitUnreachable(Unreachable* curr) {}
+
+#ifndef NDEBUG
+  // For now we only handle pops in a catch body, see visitTry(). To check for
+  // errors, use counter of the pops we handled and all the pops; those sums
+  // must agree at the end, or else we've seen something we can't handle.
+  Index totalPops = 0;
+  Index handledPops = 0;
+#endif
+
+  void visitPop(Pop* curr) {
+#ifndef NDEBUG
+    totalPops++;
+#endif
+  }
+  void visitI31New(I31New* curr) {
+    // TODO: optimize like struct references
+    addRoot(curr);
+  }
+  void visitI31Get(I31Get* curr) {
+    // TODO: optimize like struct references
+    addRoot(curr);
+  }
+
+  void visitRefCast(RefCast* curr) { receiveChildValue(curr->ref, curr); }
+  void visitRefTest(RefTest* curr) { addRoot(curr); }
+  void visitBrOn(BrOn* curr) {
+    // TODO: optimize when possible
+    handleBreakValue(curr);
+    receiveChildValue(curr->ref, curr);
+  }
+  void visitRefAs(RefAs* curr) {
+    if (curr->op == ExternExternalize || curr->op == ExternInternalize) {
+      // The external conversion ops emit something of a completely different
+      // type, which we must mark as a root.
+      addRoot(curr);
+      return;
+    }
+
+    // All other RefAs operations flow values through while refining them (the
+    // filterExpressionContents method will handle the refinement
+    // automatically).
+    receiveChildValue(curr->value, curr);
+  }
+
+  void visitLocalSet(LocalSet* curr) {
+    if (!isRelevant(curr->value->type)) {
+      return;
+    }
+
+    // Tees flow out the value (receiveChildValue will see if this is a tee
+    // based on the type, automatically).
+    receiveChildValue(curr->value, curr);
+
+    // We handle connecting local.gets to local.sets below, in visitFunction.
+  }
+  void visitLocalGet(LocalGet* curr) {
+    // We handle connecting local.gets to local.sets below, in visitFunction.
+  }
+
+  // Globals read and write from their location.
+  void visitGlobalGet(GlobalGet* curr) {
+    if (isRelevant(curr->type)) {
+      // FIXME: we allow tuples in globals, so GlobalLocation needs a tupleIndex
+      //        and we should loop here.
+      assert(!curr->type.isTuple());
+      info.links.push_back(
+        {GlobalLocation{curr->name}, ExpressionLocation{curr, 0}});
+    }
+  }
+  void visitGlobalSet(GlobalSet* curr) {
+    if (isRelevant(curr->value->type)) {
+      info.links.push_back(
+        {ExpressionLocation{curr->value, 0}, GlobalLocation{curr->name}});
+    }
+  }
+
+  // Iterates over a list of children and adds links to parameters and results
+  // as needed. The param/result functions receive the index and create the
+  // proper location for it.
+  template<typename T>
+  void handleCall(T* curr,
+                  std::function<Location(Index)> makeParamLocation,
+                  std::function<Location(Index)> makeResultLocation) {
+    Index i = 0;
+    for (auto* operand : curr->operands) {
+      if (isRelevant(operand->type)) {
+        info.links.push_back(
+          {ExpressionLocation{operand, 0}, makeParamLocation(i)});
+      }
+      i++;
+    }
+
+    // Add results, if anything flows out.
+    for (Index i = 0; i < curr->type.size(); i++) {
+      if (isRelevant(curr->type[i])) {
+        info.links.push_back(
+          {makeResultLocation(i), ExpressionLocation{curr, i}});
+      }
+    }
+
+    // If this is a return call then send the result to the function return as
+    // well.
+    if (curr->isReturn) {
+      auto results = getFunction()->getResults();
+      for (Index i = 0; i < results.size(); i++) {
+        auto result = results[i];
+        if (isRelevant(result)) {
+          info.links.push_back(
+            {makeResultLocation(i), ResultLocation{getFunction(), i}});
+        }
+      }
+    }
+  }
+
+  // Calls send values to params in their possible targets, and receive
+  // results.
+
+  template<typename T> void handleDirectCall(T* curr, Name targetName) {
+    auto* target = getModule()->getFunction(targetName);
+    handleCall(
+      curr,
+      [&](Index i) {
+        assert(i <= target->getParams().size());
+        return ParamLocation{target, i};
+      },
+      [&](Index i) {
+        assert(i <= target->getResults().size());
+        return ResultLocation{target, i};
+      });
+  }
+  template<typename T> void handleIndirectCall(T* curr, HeapType targetType) {
+    // If the heap type is not a signature, which is the case for a bottom type
+    // (null) then nothing can be called.
+    if (!targetType.isSignature()) {
+      assert(targetType.isBottom());
+      return;
+    }
+    handleCall(
+      curr,
+      [&](Index i) {
+        assert(i <= targetType.getSignature().params.size());
+        return SignatureParamLocation{targetType, i};
+      },
+      [&](Index i) {
+        assert(i <= targetType.getSignature().results.size());
+        return SignatureResultLocation{targetType, i};
+      });
+  }
+  template<typename T> void handleIndirectCall(T* curr, Type targetType) {
+    // If the type is unreachable, nothing can be called (and there is no heap
+    // type to get).
+    if (targetType != Type::unreachable) {
+      handleIndirectCall(curr, targetType.getHeapType());
+    }
+  }
+
+  void visitCall(Call* curr) {
+    Name targetName;
+    if (!Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
+      // This is just a normal call.
+      handleDirectCall(curr, curr->target);
+      return;
+    }
+    // A call-without-effects receives a function reference and calls it, the
+    // same as a CallRef. When we have a flag for non-closed-world, we should
+    // handle this automatically by the reference flowing out to an import,
+    // which is what binaryen intrinsics look like. For now, to support use
+    // cases of a closed world but that also use this intrinsic, handle the
+    // intrinsic specifically here. (Without that, the closed world assumption
+    // makes us ignore the function ref that flows to an import, so we are not
+    // aware that it is actually called.)
+    auto* target = curr->operands.back();
+
+    // We must ignore the last element when handling the call - the target is
+    // used to perform the call, and not sent during the call.
+    curr->operands.pop_back();
+
+    if (auto* refFunc = target->dynCast<RefFunc>()) {
+      // We can see exactly where this goes.
+      handleDirectCall(curr, refFunc->func);
+    } else {
+      // We can't see where this goes. We must be pessimistic and assume it
+      // can call anything of the proper type, the same as a CallRef. (We could
+      // look at the possible contents of |target| during the flow, but that
+      // would require special logic like we have for StructGet etc., and the
+      // intrinsics will be lowered away anyhow, so just running after that is
+      // a workaround.)
+      handleIndirectCall(curr, target->type);
+    }
+
+    // Restore the target.
+    curr->operands.push_back(target);
+  }
+  void visitCallIndirect(CallIndirect* curr) {
+    // TODO: the table identity could also be used here
+    // TODO: optimize the call target like CallRef
+    handleIndirectCall(curr, curr->heapType);
+  }
+  void visitCallRef(CallRef* curr) {
+    handleIndirectCall(curr, curr->target->type);
+  }
+
+  // Creates a location for a null of a particular type and adds a root for it.
+  // Such roots are where the default value of an i32 local comes from, or the
+  // value in a ref.null.
+  Location getNullLocation(Type type) {
+    auto location = NullLocation{type};
+    addRoot(location, PossibleContents::literal(Literal::makeZero(type)));
+    return location;
+  }
+
+  // Iterates over a list of children and adds links from them. The target of
+  // those link is created using a function that is passed in, which receives
+  // the index of the child.
+  void linkChildList(ExpressionList& operands,
+                     std::function<Location(Index)> makeTarget) {
+    Index i = 0;
+    for (auto* operand : operands) {
+      // This helper is not used from places that allow a tuple (hence we can
+      // hardcode the index 0 a few lines down).
+      assert(!operand->type.isTuple());
+
+      if (isRelevant(operand->type)) {
+        info.links.push_back({ExpressionLocation{operand, 0}, makeTarget(i)});
+      }
+      i++;
+    }
+  }
+
+  void visitStructNew(StructNew* curr) {
+    if (curr->type == Type::unreachable) {
+      return;
+    }
+    auto type = curr->type.getHeapType();
+    if (curr->isWithDefault()) {
+      // Link the default values to the struct's fields.
+      auto& fields = type.getStruct().fields;
+      for (Index i = 0; i < fields.size(); i++) {
+        info.links.push_back(
+          {getNullLocation(fields[i].type), DataLocation{type, i}});
+      }
+    } else {
+      // Link the operands to the struct's fields.
+      linkChildList(curr->operands, [&](Index i) {
+        return DataLocation{type, i};
+      });
+    }
+    addRoot(curr, PossibleContents::exactType(curr->type));
+  }
+  void visitArrayNew(ArrayNew* curr) {
+    if (curr->type == Type::unreachable) {
+      return;
+    }
+    auto type = curr->type.getHeapType();
+    if (curr->init) {
+      info.links.push_back(
+        {ExpressionLocation{curr->init, 0}, DataLocation{type, 0}});
+    } else {
+      info.links.push_back(
+        {getNullLocation(type.getArray().element.type), DataLocation{type, 0}});
+    }
+    addRoot(curr, PossibleContents::exactType(curr->type));
+  }
+  void visitArrayNewSeg(ArrayNewSeg* curr) {
+    if (curr->type == Type::unreachable) {
+      return;
+    }
+    addRoot(curr, PossibleContents::exactType(curr->type));
+    auto heapType = curr->type.getHeapType();
+    switch (curr->op) {
+      case NewData: {
+        Type elemType = heapType.getArray().element.type;
+        addRoot(DataLocation{heapType, 0},
+                PossibleContents::fromType(elemType));
+        return;
+      }
+      case NewElem: {
+        Type segType = getModule()->elementSegments[curr->segment]->type;
+        addRoot(DataLocation{heapType, 0}, PossibleContents::fromType(segType));
+        return;
+      }
+    }
+    WASM_UNREACHABLE("unexpected op");
+  }
+  void visitArrayInit(ArrayInit* curr) {
+    if (curr->type == Type::unreachable) {
+      return;
+    }
+    if (!curr->values.empty()) {
+      auto type = curr->type.getHeapType();
+      linkChildList(curr->values, [&](Index i) {
+        // The index i is ignored, as we do not track indexes in Arrays -
+        // everything is modeled as if at index 0.
+        return DataLocation{type, 0};
+      });
+    }
+    addRoot(curr, PossibleContents::exactType(curr->type));
+  }
+
+  // Struct operations access the struct fields' locations.
+  void visitStructGet(StructGet* curr) {
+    if (!isRelevant(curr->ref)) {
+      // If references are irrelevant then we will ignore them, and we won't
+      // have information about this struct.get's reference, which means we
+      // won't have information to compute relevant values for this struct.get.
+      // Instead, just mark this as an unknown value (root).
+      addRoot(curr);
+      return;
+    }
+    // The struct.get will receive different values depending on the contents
+    // in the reference, so mark us as the parent of the ref, and we will
+    // handle all of this in a special way during the flow. Note that we do
+    // not even create a DataLocation here; anything that we need will be
+    // added during the flow.
+    addChildParentLink(curr->ref, curr);
+  }
+  void visitStructSet(StructSet* curr) {
+    if (curr->ref->type == Type::unreachable) {
+      return;
+    }
+    // See comment on visitStructGet. Here we also connect the value.
+    addChildParentLink(curr->ref, curr);
+    addChildParentLink(curr->value, curr);
+  }
+  // Array operations access the array's location, parallel to how structs work.
+  void visitArrayGet(ArrayGet* curr) {
+    if (!isRelevant(curr->ref)) {
+      addRoot(curr);
+      return;
+    }
+    addChildParentLink(curr->ref, curr);
+  }
+  void visitArraySet(ArraySet* curr) {
+    if (curr->ref->type == Type::unreachable) {
+      return;
+    }
+    addChildParentLink(curr->ref, curr);
+    addChildParentLink(curr->value, curr);
+  }
+
+  void visitArrayLen(ArrayLen* curr) {
+    // TODO: optimize when possible (perhaps we can infer a Literal for the
+    //       length)
+    addRoot(curr);
+  }
+  void visitArrayCopy(ArrayCopy* curr) {
+    if (curr->type == Type::unreachable) {
+      return;
+    }
+    // Our flow handling of GC data is not simple: we have special code for each
+    // read and write instruction. Therefore, to avoid adding special code for
+    // ArrayCopy, model it as a combination of an ArrayRead and ArrayWrite, by
+    // just emitting fake expressions for those. The fake expressions are not
+    // part of the main IR, which is potentially confusing during debugging,
+    // however, which is a downside.
+    Builder builder(*getModule());
+    auto* get =
+      builder.makeArrayGet(curr->srcRef, curr->srcIndex, curr->srcRef->type);
+    visitArrayGet(get);
+    auto* set = builder.makeArraySet(curr->destRef, curr->destIndex, get);
+    visitArraySet(set);
+  }
+
+  void visitStringNew(StringNew* curr) {
+    if (curr->type == Type::unreachable) {
+      return;
+    }
+    addRoot(curr, PossibleContents::exactType(curr->type));
+  }
+  void visitStringConst(StringConst* curr) {
+    addRoot(curr, PossibleContents::exactType(curr->type));
+  }
+  void visitStringMeasure(StringMeasure* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringEncode(StringEncode* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringConcat(StringConcat* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringEq(StringEq* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringAs(StringAs* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringWTF8Advance(StringWTF8Advance* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringWTF16Get(StringWTF16Get* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringIterNext(StringIterNext* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringIterMove(StringIterMove* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringSliceWTF(StringSliceWTF* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+  void visitStringSliceIter(StringSliceIter* curr) {
+    // TODO: optimize when possible
+    addRoot(curr);
+  }
+
+  // TODO: Model which throws can go to which catches. For now, anything thrown
+  //       is sent to the location of that tag, and any catch of that tag can
+  //       read them.
+  void visitTry(Try* curr) {
+    receiveChildValue(curr->body, curr);
+    for (auto* catchBody : curr->catchBodies) {
+      receiveChildValue(catchBody, curr);
+    }
+
+    auto numTags = curr->catchTags.size();
+    for (Index tagIndex = 0; tagIndex < numTags; tagIndex++) {
+      auto tag = curr->catchTags[tagIndex];
+      auto* body = curr->catchBodies[tagIndex];
+
+      auto params = getModule()->getTag(tag)->sig.params;
+      if (params.size() == 0) {
+        continue;
+      }
+
+      // Find the pop of the tag's contents. The body must start with such a
+      // pop, which might be of a tuple.
+      auto* pop = EHUtils::findPop(body);
+      // There must be a pop since we checked earlier if it was an empty tag,
+      // and would not reach here.
+      assert(pop);
+      assert(pop->type.size() == params.size());
+      for (Index i = 0; i < params.size(); i++) {
+        if (isRelevant(params[i])) {
+          info.links.push_back(
+            {TagLocation{tag, i}, ExpressionLocation{pop, i}});
+        }
+      }
+
+#ifndef NDEBUG
+      // This pop was in the position we can handle, note that (see visitPop
+      // for details).
+      handledPops++;
+#endif
+    }
+  }
+  void visitThrow(Throw* curr) {
+    auto& operands = curr->operands;
+    if (!isRelevant(operands)) {
+      return;
+    }
+
+    auto tag = curr->tag;
+    for (Index i = 0; i < curr->operands.size(); i++) {
+      info.links.push_back(
+        {ExpressionLocation{operands[i], 0}, TagLocation{tag, i}});
+    }
+  }
+  void visitRethrow(Rethrow* curr) {}
+
+  void visitTupleMake(TupleMake* curr) {
+    if (isRelevant(curr->type)) {
+      for (Index i = 0; i < curr->operands.size(); i++) {
+        info.links.push_back({ExpressionLocation{curr->operands[i], 0},
+                              ExpressionLocation{curr, i}});
+      }
+    }
+  }
+  void visitTupleExtract(TupleExtract* curr) {
+    if (isRelevant(curr->type)) {
+      info.links.push_back({ExpressionLocation{curr->tuple, curr->index},
+                            ExpressionLocation{curr, 0}});
+    }
+  }
+
+  // Adds a result to the current function, such as from a return or the value
+  // that flows out.
+  void addResult(Expression* value) {
+    if (value && isRelevant(value->type)) {
+      for (Index i = 0; i < value->type.size(); i++) {
+        info.links.push_back(
+          {ExpressionLocation{value, i}, ResultLocation{getFunction(), i}});
+      }
+    }
+  }
+
+  void visitReturn(Return* curr) { addResult(curr->value); }
+
+  void visitFunction(Function* func) {
+    // Functions with a result can flow a value out from their body.
+    addResult(func->body);
+
+    // See visitPop().
+    assert(handledPops == totalPops);
+
+    // Handle local.get/sets: each set must write to the proper gets.
+    LocalGraph localGraph(func);
+
+    for (auto& [get, setsForGet] : localGraph.getSetses) {
+      auto index = get->index;
+      auto type = func->getLocalType(index);
+      if (!isRelevant(type)) {
+        continue;
+      }
+
+      // Each get reads from its relevant sets.
+      for (auto* set : setsForGet) {
+        for (Index i = 0; i < type.size(); i++) {
+          Location source;
+          if (set) {
+            // This is a normal local.set.
+            source = ExpressionLocation{set->value, i};
+          } else if (getFunction()->isParam(index)) {
+            // This is a parameter.
+            source = ParamLocation{getFunction(), index};
+          } else {
+            // This is the default value from the function entry, a null.
+            source = getNullLocation(type[i]);
+          }
+          info.links.push_back({source, ExpressionLocation{get, i}});
+        }
+      }
+    }
+  }
+
+  // Helpers
+
+  // Handles the value sent in a break instruction. Does not handle anything
+  // else like the condition etc.
+  void handleBreakValue(Expression* curr) {
+    BranchUtils::operateOnScopeNameUsesAndSentValues(
+      curr, [&](Name target, Expression* value) {
+        if (value && isRelevant(value->type)) {
+          for (Index i = 0; i < value->type.size(); i++) {
+            // Breaks send the contents of the break value to the branch target
+            // that the break goes to.
+            info.links.push_back(
+              {ExpressionLocation{value, i},
+               BreakTargetLocation{getFunction(), target, i}});
+          }
+        }
+      });
+  }
+
+  // Handles receiving values from breaks at the target (as in a block).
+  void handleBreakTarget(Expression* curr) {
+    if (isRelevant(curr->type)) {
+      BranchUtils::operateOnScopeNameDefs(curr, [&](Name target) {
+        for (Index i = 0; i < curr->type.size(); i++) {
+          info.links.push_back({BreakTargetLocation{getFunction(), target, i},
+                                ExpressionLocation{curr, i}});
+        }
+      });
+    }
+  }
+
+  // Connect a child's value to the parent, that is, all content in the child is
+  // now considered possible in the parent as well.
+  void receiveChildValue(Expression* child, Expression* parent) {
+    if (isRelevant(parent) && isRelevant(child)) {
+      // The tuple sizes must match (or, if not a tuple, the size should be 1 in
+      // both cases).
+      assert(child->type.size() == parent->type.size());
+      for (Index i = 0; i < child->type.size(); i++) {
+        info.links.push_back(
+          {ExpressionLocation{child, i}, ExpressionLocation{parent, i}});
+      }
+    }
+  }
+
+  // See the comment on CollectedFuncInfo::childParents.
+  void addChildParentLink(Expression* child, Expression* parent) {
+    if (isRelevant(child->type)) {
+      info.childParents[child] = parent;
+    }
+  }
+
+  // Adds a root, if the expression is relevant. If the value is not specified,
+  // mark the root as containing Many (which is the common case, so avoid
+  // verbose code).
+  void addRoot(Expression* curr,
+               PossibleContents contents = PossibleContents::many()) {
+    // TODO Use a cone type here when relevant
+    if (isRelevant(curr)) {
+      if (contents.isMany()) {
+        contents = PossibleContents::fromType(curr->type);
+      }
+      addRoot(ExpressionLocation{curr, 0}, contents);
+    }
+  }
+
+  // As above, but given an arbitrary location and not just an expression.
+  void addRoot(Location loc,
+               PossibleContents contents = PossibleContents::many()) {
+    info.roots.emplace_back(loc, contents);
+  }
+};
+
+// Main logic for building data for the flow analysis and then performing that
+// analysis.
+struct Flower {
+  Module& wasm;
+
+  Flower(Module& wasm);
+
+  // Each LocationIndex will have one LocationInfo that contains the relevant
+  // information we need for each location.
+  struct LocationInfo {
+    // The location at this index.
+    Location location;
+
+    // The possible contents in that location.
+    PossibleContents contents;
+
+    // A list of the target locations to which this location sends content.
+    // TODO: benchmark SmallVector<1> here, as commonly there may be a single
+    //       target (an expression has one parent)
+    std::vector<LocationIndex> targets;
+
+    LocationInfo(Location location) : location(location) {}
+  };
+
+  // Maps location indexes to the info stored there, as just described above.
+  std::vector<LocationInfo> locations;
+
+  // Reverse mapping of locations to their indexes.
+  std::unordered_map<Location, LocationIndex> locationIndexes;
+
+  const Location& getLocation(LocationIndex index) {
+    assert(index < locations.size());
+    return locations[index].location;
+  }
+
+  PossibleContents& getContents(LocationIndex index) {
+    assert(index < locations.size());
+    return locations[index].contents;
+  }
+
+private:
+  std::vector<LocationIndex>& getTargets(LocationIndex index) {
+    assert(index < locations.size());
+    return locations[index].targets;
+  }
+
+  // Convert the data into the efficient LocationIndex form we will use during
+  // the flow analysis. This method returns the index of a location, allocating
+  // one if this is the first time we see it.
+  LocationIndex getIndex(const Location& location) {
+    auto iter = locationIndexes.find(location);
+    if (iter != locationIndexes.end()) {
+      return iter->second;
+    }
+
+    // Allocate a new index here.
+    size_t index = locations.size();
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+    std::cout << "  new index " << index << " for ";
+    dump(location);
+#endif
+    if (index >= std::numeric_limits<LocationIndex>::max()) {
+      // 32 bits should be enough since each location takes at least one byte
+      // in the binary, and we don't have 4GB wasm binaries yet... do we?
+      Fatal() << "Too many locations for 32 bits";
+    }
+    locations.emplace_back(location);
+    locationIndexes[location] = index;
+
+    return index;
+  }
+
+  bool hasIndex(const Location& location) {
+    return locationIndexes.find(location) != locationIndexes.end();
+  }
+
+  IndexLink getIndexes(const LocationLink& link) {
+    return {getIndex(link.from), getIndex(link.to)};
+  }
+
+  // See the comment on CollectedFuncInfo::childParents. This is the merged info
+  // from all the functions and the global scope.
+  std::unordered_map<LocationIndex, LocationIndex> childParents;
+
+  // The work remaining to do during the flow: locations that we need to flow
+  // content from, after new content reached them.
+  //
+  // Using a set here is efficient as multiple updates may arrive to a location
+  // before we get to processing it.
+  //
+  // The items here could be {location, newContents}, but it is more efficient
+  // to have already written the new contents to the main data structure. That
+  // avoids larger data here, and also, updating the contents as early as
+  // possible is helpful as anything reading them meanwhile (before we get to
+  // their work item in the queue) will see the newer value, possibly avoiding
+  // flowing an old value that would later be overwritten.
+  //
+  // This must be ordered to avoid nondeterminism. The problem is that our
+  // operations are imprecise and so the transitive property does not hold:
+  // (AvB)vC may differ from Av(BvC). Likewise (AvB)^C may differ from
+  // (A^C)v(B^C). An example of the latter is if a location is sent a null func
+  // and an i31, and the location can only contain funcref. If the null func
+  // arrives first, then later we'd merge null func + i31 which ends up as Many,
+  // and then we filter that to funcref and get funcref. But if the i31 arrived
+  // first, we'd filter it into nothing, and then the null func that arrives
+  // later would be the final result. This would not happen if our operations
+  // were precise, but we only make approximations here to avoid unacceptable
+  // overhead, such as cone types but not arbitrary unions, etc.
+  InsertOrderedSet<LocationIndex> workQueue;
+
+  // All existing links in the graph. We keep this to know when a link we want
+  // to add is new or not.
+  std::unordered_set<IndexLink> links;
+
+  // Update a location with new contents that are added to everything already
+  // present there. If the update changes the contents at that location (if
+  // there was anything new) then we also need to flow from there, which we will
+  // do by adding the location to the work queue, and eventually flowAfterUpdate
+  // will be called on this location.
+  //
+  // Returns whether it is worth sending new contents to this location in the
+  // future. If we return false, the sending location never needs to do that
+  // ever again.
+  bool updateContents(LocationIndex locationIndex,
+                      PossibleContents newContents);
+
+  // Slow helper that converts a Location to a LocationIndex. This should be
+  // avoided. TODO: remove the remaining uses of this.
+  bool updateContents(const Location& location,
+                      const PossibleContents& newContents) {
+    return updateContents(getIndex(location), newContents);
+  }
+
+  // Flow contents from a location where a change occurred. This sends the new
+  // contents to all the normal targets of this location (using
+  // flowToTargetsAfterUpdate), and also handles special cases of flow after.
+  void flowAfterUpdate(LocationIndex locationIndex);
+
+  // Internal part of flowAfterUpdate that handles sending new values to the
+  // given location index's normal targets (that is, the ones listed in the
+  // |targets| vector).
+  void flowToTargetsAfterUpdate(LocationIndex locationIndex,
+                                const PossibleContents& contents);
+
+  // Add a new connection while the flow is happening. If the link already
+  // exists it is not added.
+  void connectDuringFlow(Location from, Location to);
+
+  // Contents sent to certain locations can be filtered in a special way during
+  // the flow, which is handled in these helpers. These may update
+  // |worthSendingMore| which is whether it is worth sending any more content to
+  // this location in the future.
+  void filterExpressionContents(PossibleContents& contents,
+                                const ExpressionLocation& exprLoc,
+                                bool& worthSendingMore);
+  void filterGlobalContents(PossibleContents& contents,
+                            const GlobalLocation& globalLoc);
+
+  // Reads from GC data: a struct.get or array.get. This is given the type of
+  // the read operation, the field that is read on that type, the known contents
+  // in the reference the read receives, and the read instruction itself. We
+  // compute where we need to read from based on the type and the ref contents
+  // and get that data, adding new links in the graph as needed.
+  void readFromData(Type declaredType,
+                    Index fieldIndex,
+                    const PossibleContents& refContents,
+                    Expression* read);
+
+  // Similar to readFromData, but does a write for a struct.set or array.set.
+  void writeToData(Expression* ref, Expression* value, Index fieldIndex);
+
+  // We will need subtypes during the flow, so compute them once ahead of time.
+  std::unique_ptr<SubTypes> subTypes;
+
+  // The depth of children for each type. This is 0 if the type has no
+  // subtypes, 1 if it has subtypes but none of those have subtypes themselves,
+  // and so forth.
+  std::unordered_map<HeapType, Index> maxDepths;
+
+  // Given a ConeType, return the normalized depth, that is, the canonical depth
+  // given the actual children it has. If this is a full cone, then we can
+  // always pick the actual maximal depth and use that instead of FullDepth==-1.
+  // For a non-full cone, we also reduce the depth as much as possible, so it is
+  // equal to the maximum depth of an existing subtype.
+  Index getNormalizedConeDepth(Type type, Index depth) {
+    return std::min(depth, maxDepths[type.getHeapType()]);
+  }
+
+  void normalizeConeType(PossibleContents& cone) {
+    assert(cone.isConeType());
+    auto type = cone.getType();
+    auto before = cone.getCone().depth;
+    auto normalized = getNormalizedConeDepth(type, before);
+    if (normalized != before) {
+      cone = PossibleContents::coneType(type, normalized);
+    }
+  }
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  // Dump out a location for debug purposes.
+  void dump(Location location);
+#endif
+};
+
+Flower::Flower(Module& wasm) : wasm(wasm) {
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "parallel phase\n";
+#endif
+
+  // First, collect information from each function.
+  ModuleUtils::ParallelFunctionAnalysis<CollectedFuncInfo> analysis(
+    wasm, [&](Function* func, CollectedFuncInfo& info) {
+      InfoCollector finder(info);
+
+      if (func->imported()) {
+        // Imports return unknown values.
+        auto results = func->getResults();
+        for (Index i = 0; i < results.size(); i++) {
+          finder.addRoot(ResultLocation{func, i},
+                         PossibleContents::fromType(results[i]));
+        }
+        return;
+      }
+
+      finder.walkFunctionInModule(func, &wasm);
+    });
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "single phase\n";
+#endif
+
+  // Also walk the global module code (for simplicity, also add it to the
+  // function map, using a "function" key of nullptr).
+  auto& globalInfo = analysis.map[nullptr];
+  InfoCollector finder(globalInfo);
+  finder.walkModuleCode(&wasm);
+
+  // Connect global init values (which we've just processed, as part of the
+  // module code) to the globals they initialize.
+  for (auto& global : wasm.globals) {
+    if (global->imported()) {
+      // Imports are unknown values.
+      finder.addRoot(GlobalLocation{global->name},
+                     PossibleContents::fromType(global->type));
+      continue;
+    }
+    auto* init = global->init;
+    if (finder.isRelevant(init->type)) {
+      globalInfo.links.push_back(
+        {ExpressionLocation{init, 0}, GlobalLocation{global->name}});
+    }
+  }
+
+  // Merge the function information into a single large graph that represents
+  // the entire program all at once, indexing and deduplicating everything as we
+  // go.
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "merging+indexing phase\n";
+#endif
+
+  // The merged roots. (Note that all other forms of merged data are declared at
+  // the class level, since we need them during the flow, but the roots are only
+  // needed to start the flow, so we can declare them here.)
+  std::unordered_map<Location, PossibleContents> roots;
+
+  for (auto& [func, info] : analysis.map) {
+    for (auto& link : info.links) {
+      links.insert(getIndexes(link));
+    }
+    for (auto& [root, value] : info.roots) {
+      roots[root] = value;
+
+      // Ensure an index even for a root with no links to it - everything needs
+      // an index.
+      getIndex(root);
+    }
+    for (auto [child, parent] : info.childParents) {
+      // In practice we do not have any childParent connections with a tuple;
+      // assert on that just to be safe.
+      assert(!child->type.isTuple());
+      childParents[getIndex(ExpressionLocation{child, 0})] =
+        getIndex(ExpressionLocation{parent, 0});
+    }
+  }
+
+  // We no longer need the function-level info.
+  analysis.map.clear();
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "external phase\n";
+#endif
+
+  // Parameters of exported functions are roots, since exports can have callers
+  // that we can't see, so anything might arrive there.
+  auto calledFromOutside = [&](Name funcName) {
+    auto* func = wasm.getFunction(funcName);
+    auto params = func->getParams();
+    for (Index i = 0; i < func->getParams().size(); i++) {
+      roots[ParamLocation{func, i}] = PossibleContents::fromType(params[i]);
+    }
+  };
+
+  for (auto& ex : wasm.exports) {
+    if (ex->kind == ExternalKind::Function) {
+      calledFromOutside(ex->value);
+    } else if (ex->kind == ExternalKind::Table) {
+      // If any table is exported, assume any function in any table (including
+      // other tables) can be called from the outside.
+      // TODO: This could be more precise about which tables are exported and
+      //       which are not: perhaps one table is exported but we can optimize
+      //       the functions in another table, which is not exported. However,
+      //       it is simpler to treat them all the same, and this handles the
+      //       common case of no tables being exported at all.
+      // TODO: This does not handle table.get/table.set, or call_ref, for which
+      //       we'd need to see which references are used and which escape etc.
+      //       For now, assume a closed world for such such advanced use cases /
+      //       assume this pass won't be run in them anyhow.
+      // TODO: do this only once if multiple tables are exported
+      for (auto& elementSegment : wasm.elementSegments) {
+        for (auto* curr : elementSegment->data) {
+          if (auto* refFunc = curr->dynCast<RefFunc>()) {
+            calledFromOutside(refFunc->func);
+          }
+        }
+      }
+    } else if (ex->kind == ExternalKind::Global) {
+      // Exported mutable globals are roots, since the outside may write any
+      // value to them.
+      auto name = ex->value;
+      auto* global = wasm.getGlobal(name);
+      if (global->mutable_) {
+        roots[GlobalLocation{name}] = PossibleContents::fromType(global->type);
+      }
+    }
+  }
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "struct phase\n";
+#endif
+
+  if (getTypeSystem() == TypeSystem::Nominal ||
+      getTypeSystem() == TypeSystem::Isorecursive) {
+    subTypes = std::make_unique<SubTypes>(wasm);
+    maxDepths = subTypes->getMaxDepths();
+  }
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "Link-targets phase\n";
+#endif
+
+  // Add all links to the targets vectors of the source locations, which we will
+  // use during the flow.
+  for (auto& link : links) {
+    getTargets(link.from).push_back(link.to);
+  }
+
+#ifndef NDEBUG
+  // Each vector of targets (which is a vector for efficiency) must have no
+  // duplicates.
+  for (auto& info : locations) {
+    disallowDuplicates(info.targets);
+  }
+#endif
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "roots phase\n";
+#endif
+
+  // Set up the roots, which are the starting state for the flow analysis: send
+  // their initial content to them to start the flow.
+  for (const auto& [location, value] : roots) {
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+    std::cout << "  init root\n";
+    dump(location);
+    value.dump(std::cout, &wasm);
+    std::cout << '\n';
+#endif
+
+    updateContents(location, value);
+  }
+
+#ifdef POSSIBLE_CONTENTS_DEBUG
+  std::cout << "flow phase\n";
+  size_t iters = 0;
+#endif
+
+  // Flow the data while there is still stuff flowing.
+  while (!workQueue.empty()) {
+#ifdef POSSIBLE_CONTENTS_DEBUG
+    iters++;
+    if ((iters & 255) == 0) {
+      std::cout << iters++ << " iters, work left: " << workQueue.size() << '\n';
+    }
+#endif
+
+    auto iter = workQueue.begin();
+    auto locationIndex = *iter;
+    workQueue.erase(iter);
+
+    flowAfterUpdate(locationIndex);
+  }
+
+  // TODO: Add analysis and retrieval logic for fields of immutable globals,
+  //       including multiple levels of depth (necessary for itables in j2wasm).
+}
+
+bool Flower::updateContents(LocationIndex locationIndex,
+                            PossibleContents newContents) {
+  auto& contents = getContents(locationIndex);
+  auto oldContents = contents;
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  std::cout << "updateContents\n";
+  dump(getLocation(locationIndex));
+  contents.dump(std::cout, &wasm);
+  std::cout << "\n with new contents \n";
+  newContents.dump(std::cout, &wasm);
+  std::cout << '\n';
+#endif
+
+  contents.combine(newContents);
+
+  if (contents.isNone()) {
+    // There is still nothing here. There is nothing more to do here but to
+    // return that it is worth sending more.
+    return true;
+  }
+
+  // It is not worth sending any more to this location if we are now in the
+  // worst possible case, as no future value could cause any change.
+  bool worthSendingMore = true;
+  if (contents.isConeType()) {
+    if (!contents.getType().isRef()) {
+      // A cone type of a non-reference is the worst case, since subtyping is
+      // not relevant there, and so if we only know something about the type
+      // then we already know nothing beyond what the type in the wasm tells us
+      // (and from there we can only go to Many).
+      worthSendingMore = false;
+    } else {
+      // Normalize all reference cones. There is never a point to flow around
+      // anything non-normalized, which might lead to extra work. For example,
+      // if A has no subtypes, then a full cone for A is really the same as one
+      // with depth 0 (an exact type). And we don't want to see the full cone
+      // arrive and think it was an improvement over the one with depth 0 and do
+      // more flowing based on that.
+      normalizeConeType(contents);
+    }
+  }
+
+  // Check if anything changed.
+  if (contents == oldContents) {
+    // Nothing actually changed, so just return.
+    return worthSendingMore;
+  }
+
+  // Handle special cases: Some locations can only contain certain contents, so
+  // filter accordingly.
+  auto location = getLocation(locationIndex);
+  bool filtered = false;
+  if (auto* exprLoc = std::get_if<ExpressionLocation>(&location)) {
+    // TODO: Replace this with specific filterFoo or flowBar methods like we
+    //       have for filterGlobalContents. That could save a little wasted work
+    //       here. Might be best to do that after the spec is fully stable.
+    filterExpressionContents(contents, *exprLoc, worthSendingMore);
+    filtered = true;
+  } else if (auto* globalLoc = std::get_if<GlobalLocation>(&location)) {
+    filterGlobalContents(contents, *globalLoc);
+    filtered = true;
+  }
+
+  // Check if anything changed after filtering, if we did so.
+  if (filtered) {
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+    std::cout << "  filtered contents:\n";
+    contents.dump(std::cout, &wasm);
+    std::cout << '\n';
+#endif
+
+    if (contents == oldContents) {
+      return worthSendingMore;
+    }
+  }
+
+  // After filtering we should always have more precise information than "many"
+  // - in the worst case, we can have the type declared in the wasm.
+  assert(!contents.isMany());
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  std::cout << "  updateContents has something new\n";
+  contents.dump(std::cout, &wasm);
+  std::cout << '\n';
+#endif
+
+  // Add a work item if there isn't already.
+  workQueue.insert(locationIndex);
+
+  return worthSendingMore;
+}
+
+void Flower::flowAfterUpdate(LocationIndex locationIndex) {
+  const auto location = getLocation(locationIndex);
+  auto& contents = getContents(locationIndex);
+
+  // We are called after a change at a location. A change means that some
+  // content has arrived, since we never send empty values around. Assert on
+  // that.
+  assert(!contents.isNone());
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  std::cout << "\nflowAfterUpdate to:\n";
+  dump(location);
+  std::cout << "  arriving:\n";
+  contents.dump(std::cout, &wasm);
+  std::cout << '\n';
+#endif
+
+  // Flow the contents to the normal targets of this location.
+  flowToTargetsAfterUpdate(locationIndex, contents);
+
+  // We are mostly done, except for handling interesting/special cases in the
+  // flow, additional operations that we need to do aside from sending the new
+  // contents to the normal (statically linked) targets.
+
+  if (auto* exprLoc = std::get_if<ExpressionLocation>(&location)) {
+    auto iter = childParents.find(locationIndex);
+    if (iter == childParents.end()) {
+      return;
+    }
+
+    // This is indeed one of the special cases where it is the child of a
+    // parent, and we need to do some special handling because of that child-
+    // parent connection.
+    auto* child = exprLoc->expr;
+    WASM_UNUSED(child);
+    auto parentIndex = iter->second;
+    auto* parent = std::get<ExpressionLocation>(getLocation(parentIndex)).expr;
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+    std::cout << "  special, parent:\n" << *parent << '\n';
+#endif
+
+    if (auto* get = parent->dynCast<StructGet>()) {
+      // |child| is the reference child of a struct.get.
+      assert(get->ref == child);
+      readFromData(get->ref->type, get->index, contents, get);
+    } else if (auto* set = parent->dynCast<StructSet>()) {
+      // |child| is either the reference or the value child of a struct.set.
+      assert(set->ref == child || set->value == child);
+      writeToData(set->ref, set->value, set->index);
+    } else if (auto* get = parent->dynCast<ArrayGet>()) {
+      assert(get->ref == child);
+      readFromData(get->ref->type, 0, contents, get);
+    } else if (auto* set = parent->dynCast<ArraySet>()) {
+      assert(set->ref == child || set->value == child);
+      writeToData(set->ref, set->value, 0);
+    } else {
+      // TODO: ref.test and all other casts can be optimized (see the cast
+      //       helper code used in OptimizeInstructions and RemoveUnusedBrs)
+      WASM_UNREACHABLE("bad childParents content");
+    }
+  }
+}
+
+void Flower::flowToTargetsAfterUpdate(LocationIndex locationIndex,
+                                      const PossibleContents& contents) {
+  // Send the new contents to all the targets of this location. As we do so,
+  // prune any targets that we do not need to bother sending content to in the
+  // future, to save space and work later.
+  auto& targets = getTargets(locationIndex);
+  targets.erase(std::remove_if(targets.begin(),
+                               targets.end(),
+                               [&](LocationIndex targetIndex) {
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+                                 std::cout << "  send to target\n";
+                                 dump(getLocation(targetIndex));
+#endif
+                                 return !updateContents(targetIndex, contents);
+                               }),
+                targets.end());
+
+  if (contents.isMany()) {
+    // We contain Many, and just called updateContents on our targets to send
+    // that value to them. We'll never need to send anything from here ever
+    // again, since we sent the worst case possible already, so we can just
+    // clear our targets vector. But we should have already removed all the
+    // targets in the above remove_if operation, since they should have all
+    // notified us that we do not need to send them any more updates.
+    assert(targets.empty());
+  }
+}
+
+void Flower::connectDuringFlow(Location from, Location to) {
+  auto newLink = LocationLink{from, to};
+  auto newIndexLink = getIndexes(newLink);
+  if (links.count(newIndexLink) == 0) {
+    // This is a new link. Add it to the known links.
+    links.insert(newIndexLink);
+
+    // Add it to the |targets| vector.
+    auto& targets = getTargets(newIndexLink.from);
+    targets.push_back(newIndexLink.to);
+#ifndef NDEBUG
+    disallowDuplicates(targets);
+#endif
+
+    // In addition to adding the link, which will ensure new contents appearing
+    // later will be sent along, we also update with the current contents.
+    updateContents(to, getContents(getIndex(from)));
+  }
+}
+
+void Flower::filterExpressionContents(PossibleContents& contents,
+                                      const ExpressionLocation& exprLoc,
+                                      bool& worthSendingMore) {
+  auto type = exprLoc.expr->type;
+  if (!type.isRef()) {
+    return;
+  }
+
+  // The caller cannot know of a situation where it might not be worth sending
+  // more to a reference - all that logic is in here. That is, the rest of this
+  // function is the only place we can mark |worthSendingMore| as false for a
+  // reference.
+  assert(worthSendingMore);
+
+  // The maximal contents here are the declared type and all subtypes. Nothing
+  // else can pass through, so filter such things out.
+  auto maximalContents = PossibleContents::fullConeType(type);
+  contents.intersectWithFullCone(maximalContents);
+  if (contents.isNone()) {
+    // Nothing was left here at all.
+    return;
+  }
+
+  // Normalize the intersection. We want to check later if any more content can
+  // arrive here, and also we want to avoid flowing around anything non-
+  // normalized, as explained earlier.
+  //
+  // Note that this normalization is necessary even though |contents| was
+  // normalized before the intersection, e.g.:
+  /*
+  //      A
+  //     / \
+  //    B   C
+  //        |
+  //        D
+  */
+  // Consider the case where |maximalContents| is Cone(B, Infinity) and the
+  // original |contents| was Cone(A, 2) (which is normalized). The naive
+  // intersection is Cone(B, 1), since the core intersection logic makes no
+  // assumptions about the rest of the types. That is then normalized to
+  // Cone(B, 0) since there happens to be no subtypes for B.
+  //
+  // Note that the intersection may also not be a cone type, if it is a global
+  // or literal. In that case we don't have anything more to do here.
+  if (!contents.isConeType()) {
+    return;
+  }
+
+  normalizeConeType(contents);
+
+  // There is a chance that the intersection is equal to the maximal contents,
+  // which would mean nothing more can arrive here. (Note that we can't
+  // normalize |maximalContents| before the intersection as
+  // intersectWithFullCone assumes a full/infinite cone.)
+  normalizeConeType(maximalContents);
+
+  if (contents == maximalContents) {
+    // We already contain everything possible, so this is the worst case.
+    worthSendingMore = false;
+  }
+}
+
+void Flower::filterGlobalContents(PossibleContents& contents,
+                                  const GlobalLocation& globalLoc) {
+  auto* global = wasm.getGlobal(globalLoc.name);
+  if (global->mutable_ == Immutable) {
+    // This is an immutable global. We never need to consider this value as
+    // "Many", since in the worst case we can just use the immutable value. That
+    // is, we can always replace this value with (global.get $name) which will
+    // get the right value. Likewise, using the immutable global value is often
+    // better than a cone type (even an exact one), but TODO: we could note both
+    // a cone/exact type *and* that something is equal to a global, in some
+    // cases. See https://github.com/WebAssembly/binaryen/pull/5083
+    if (contents.isMany() || contents.isConeType()) {
+      contents = PossibleContents::global(global->name, global->type);
+
+      // TODO: We could do better here, to set global->init->type instead of
+      //       global->type, or even the contents.getType() - either of those
+      //       may be more refined. But other passes will handle that in
+      //       general (by refining the global's type).
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+      std::cout << "  setting immglobal to ImmutableGlobal\n";
+      contents.dump(std::cout, &wasm);
+      std::cout << '\n';
+#endif
+    }
+  }
+}
+
+void Flower::readFromData(Type declaredType,
+                          Index fieldIndex,
+                          const PossibleContents& refContents,
+                          Expression* read) {
+#ifndef NDEBUG
+  // We must not have anything in the reference that is invalid for the wasm
+  // type there.
+  auto maximalContents = PossibleContents::fullConeType(declaredType);
+  assert(PossibleContents::isSubContents(refContents, maximalContents));
+#endif
+
+  // The data that a struct.get reads depends on two things: the reference that
+  // we read from, and the relevant DataLocations. The reference determines
+  // which DataLocations are relevant: if it is an exact type then we have a
+  // single DataLocation to read from, the one type that can be read from there.
+  // Otherwise, we might read from any subtype, and so all their DataLocations
+  // are relevant.
+  //
+  // What can be confusing is that the information about the reference is also
+  // inferred during the flow. That is, we use our current information about the
+  // reference to decide what to do here. But the flow is not finished yet!
+  // To keep things valid, we must therefore react to changes in either the
+  // reference - when we see that more types might be read from here - or the
+  // DataLocations - when new things are written to the data we can read from.
+  // Specifically, at every point in time we want to preserve the property that
+  // we've read from all relevant types based on the current reference, and
+  // we've read the very latest possible contents from those types. And then
+  // since we preserve that property til the end of the flow, it is also valid
+  // then. At the end of the flow, the current reference's contents are the
+  // final and correct contents for that location, which means we've ended up
+  // with the proper result: the struct.get reads everything it should.
+  //
+  // To implement what was just described, we call this function when the
+  // reference is updated. This function will then set up connections in the
+  // graph so that updates to the relevant DataLocations will reach us in the
+  // future.
+
+  if (refContents.isNull() || refContents.isNone()) {
+    // Nothing is read here as this is either a null or unreachable code. (Note
+    // that the contents must be a subtype of the wasm type, which rules out
+    // other possibilities like a non-null literal such as an integer or a
+    // function reference.)
+    return;
+  }
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  std::cout << "    add special reads\n";
+#endif
+
+  // The only possibilities left are a cone type (the worst case is where the
+  // cone matches the wasm type), or a global.
+  //
+  // TODO: The Global case may have a different cone type than the heapType,
+  //       which we could use here.
+  // TODO: A Global may refer to an immutable global, which we can read the
+  //       field from potentially (reading it from the struct.new/array.new
+  //       in the definition of it, if it is not imported; or, we could track
+  //       the contents of immutable fields of allocated objects, and not just
+  //       represent them as an exact type).
+  //       See the test TODO with text "We optimize some of this, but stop at
+  //       reading from the immutable global"
+  assert(refContents.isGlobal() || refContents.isConeType());
+
+  // Just look at the cone here, discarding information about this being a
+  // global, if it was one. All that matters from now is the cone. We also
+  // normalize the cone to avoid wasted work later.
+  auto cone = refContents.getCone();
+  auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth);
+
+  // We create a ConeReadLocation for the canonical cone of this type, to
+  // avoid bloating the graph, see comment on ConeReadLocation().
+  auto coneReadLocation =
+    ConeReadLocation{cone.type.getHeapType(), normalizedDepth, fieldIndex};
+  if (!hasIndex(coneReadLocation)) {
+    // This is the first time we use this location, so create the links for it
+    // in the graph.
+    subTypes->iterSubTypes(
+      cone.type.getHeapType(),
+      normalizedDepth,
+      [&](HeapType type, Index depth) {
+        connectDuringFlow(DataLocation{type, fieldIndex}, coneReadLocation);
+      });
+
+    // TODO: we can end up with redundant links here if we see one cone first
+    //       and then a larger one later. But removing links is not efficient,
+    //       so for now just leave that.
+  }
+
+  // Link to the canonical location.
+  connectDuringFlow(coneReadLocation, ExpressionLocation{read, 0});
+}
+
+void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) {
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+  std::cout << "    add special writes\n";
+#endif
+
+  auto refContents = getContents(getIndex(ExpressionLocation{ref, 0}));
+
+#ifndef NDEBUG
+  // We must not have anything in the reference that is invalid for the wasm
+  // type there.
+  auto maximalContents = PossibleContents::fullConeType(ref->type);
+  assert(PossibleContents::isSubContents(refContents, maximalContents));
+#endif
+
+  // We could set up links here as we do for reads, but as we get to this code
+  // in any case, we can just flow the values forward directly. This avoids
+  // adding any links (edges) to the graph (and edges are what we want to avoid
+  // adding, as there can be a quadratic number of them). In other words, we'll
+  // loop over the places we need to send info to, which we can figure out in a
+  // simple way, and by doing so we avoid materializing edges into the graph.
+  //
+  // Note that this is different from readFromData, above, which does add edges
+  // to the graph (and works hard to add as few as possible, see the "canonical
+  // cone reads" logic). The difference is because readFromData must "subscribe"
+  // to get notifications from the relevant DataLocations. But when writing that
+  // is not a problem: whenever a change happens in the reference or the value
+  // of a struct.set then this function will get called, and those are the only
+  // things we care about. And we can then just compute the values we are
+  // sending (based on the current contents of the reference and the value), and
+  // where we should send them to, and do that right here. (And as commented in
+  // readFromData, that is guaranteed to give us the right result in the end: at
+  // every point in time we send the right data, so when the flow is finished
+  // we've sent information based on the final and correct information about our
+  // reference and value.)
+
+  auto valueContents = getContents(getIndex(ExpressionLocation{value, 0}));
+
+  // See the related comment in readFromData() as to why these are the only
+  // things we need to check, and why the assertion afterwards contains the only
+  // things possible.
+  if (refContents.isNone() || refContents.isNull()) {
+    return;
+  }
+  assert(refContents.isGlobal() || refContents.isConeType());
+
+  // As in readFromData, normalize to the proper cone.
+  auto cone = refContents.getCone();
+  auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth);
+
+  subTypes->iterSubTypes(
+    cone.type.getHeapType(), normalizedDepth, [&](HeapType type, Index depth) {
+      auto heapLoc = DataLocation{type, fieldIndex};
+      updateContents(heapLoc, valueContents);
+    });
+}
+
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+void Flower::dump(Location location) {
+  if (auto* loc = std::get_if<ExpressionLocation>(&location)) {
+    std::cout << "  exprloc \n" << *loc->expr << '\n';
+  } else if (auto* loc = std::get_if<DataLocation>(&location)) {
+    std::cout << "  dataloc ";
+    if (wasm.typeNames.count(loc->type)) {
+      std::cout << '$' << wasm.typeNames[loc->type].name;
+    } else {
+      std::cout << loc->type << '\n';
+    }
+    std::cout << " : " << loc->index << '\n';
+  } else if (auto* loc = std::get_if<TagLocation>(&location)) {
+    std::cout << "  tagloc " << loc->tag << '\n';
+  } else if (auto* loc = std::get_if<ParamLocation>(&location)) {
+    std::cout << "  paramloc " << loc->func->name << " : " << loc->index
+              << '\n';
+  } else if (auto* loc = std::get_if<ResultLocation>(&location)) {
+    std::cout << "  resultloc $" << loc->func->name << " : " << loc->index
+              << '\n';
+  } else if (auto* loc = std::get_if<GlobalLocation>(&location)) {
+    std::cout << "  globalloc " << loc->name << '\n';
+  } else if (auto* loc = std::get_if<BreakTargetLocation>(&location)) {
+    std::cout << "  branchloc " << loc->func->name << " : " << loc->target
+              << " tupleIndex " << loc->tupleIndex << '\n';
+  } else if (auto* loc = std::get_if<SignatureParamLocation>(&location)) {
+    WASM_UNUSED(loc);
+    std::cout << "  sigparamloc " << '\n';
+  } else if (auto* loc = std::get_if<SignatureResultLocation>(&location)) {
+    WASM_UNUSED(loc);
+    std::cout << "  sigresultloc " << '\n';
+  } else if (auto* loc = std::get_if<NullLocation>(&location)) {
+    std::cout << "  Nullloc " << loc->type << '\n';
+  } else {
+    std::cout << "  (other)\n";
+  }
+}
+#endif
+
+} // anonymous namespace
+
+void ContentOracle::analyze() {
+  Flower flower(wasm);
+  for (LocationIndex i = 0; i < flower.locations.size(); i++) {
+    locationContents[flower.getLocation(i)] = flower.getContents(i);
+  }
+}
+
+} // namespace wasm
diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h
new file mode 100644
index 0000000..b7c9bfa
--- /dev/null
+++ b/src/ir/possible-contents.h
@@ -0,0 +1,659 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_possible_contents_h
+#define wasm_ir_possible_contents_h
+
+#include <variant>
+
+#include "ir/possible-constant.h"
+#include "ir/subtypes.h"
+#include "support/hash.h"
+#include "support/small_vector.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+//
+// PossibleContents represents the possible contents at a particular location
+// (such as in a local or in a function parameter). This is a little similar to
+// PossibleConstantValues, but considers more types of contents than constant
+// values - in particular, it can track types to some extent.
+//
+// The specific contents this can vary over are:
+//
+//  * None:            No possible value.
+//
+//  * Literal:         One possible constant value like an i32 of 42.
+//
+//  * Global:          The name of a global whose value is here. We do not know
+//                     the actual value at compile time, but we know it is equal
+//                     to that global. Typically we can only infer this for
+//                     immutable globals.
+//
+//  * ConeType:        Any possible value of a particular type, and a possible
+//                     "cone" of a certain depth below it. If the depth is 0
+//                     then only the exact type is possible; if the depth is 1
+//                     then either that type or its immediate subtypes, and so
+//                     forth.
+//                     A depth of -1 means unlimited: all subtypes are allowed.
+//                     If the type here is nullable then null is also allowed.
+//                     TODO: Add ConeTypePlusContents or such, which would be
+//                           used on e.g. a struct.new with an immutable field
+//                           to which we assign a constant: not only do we know
+//                           the type, but also certain field's values.
+//
+//  * Many:            Anything else. Many things are possible here, and we do
+//                     not track what they might be, so we must assume the worst
+//                     in the calling code.
+//
+class PossibleContents {
+  struct None : public std::monostate {};
+
+  struct GlobalInfo {
+    Name name;
+    // The type of the global in the module. We stash this here so that we do
+    // not need to pass around a module all the time.
+    // TODO: could we save size in this variant if we did pass around the
+    //       module?
+    Type type;
+    bool operator==(const GlobalInfo& other) const {
+      return name == other.name && type == other.type;
+    }
+  };
+
+  struct ConeType {
+    Type type;
+    Index depth;
+    bool operator==(const ConeType& other) const {
+      return type == other.type && depth == other.depth;
+    }
+  };
+
+  struct Many : public std::monostate {};
+
+  // TODO: This is similar to the variant in PossibleConstantValues, and perhaps
+  //       we could share code, but extending a variant using template magic may
+  //       not be worthwhile. Another option might be to make PCV inherit from
+  //       this and disallow ConeType etc., but PCV might get slower.
+  using Variant = std::variant<None, Literal, GlobalInfo, ConeType, Many>;
+  Variant value;
+
+  // Internal convenience for creating a cone type with depth 0, i.e,, an exact
+  // type.
+  static ConeType ExactType(Type type) { return ConeType{type, 0}; }
+
+  static constexpr Index FullDepth = -1;
+
+  // Internal convenience for creating a cone type of unbounded depth, i.e., the
+  // full cone of all subtypes for that type.
+  static ConeType FullConeType(Type type) { return ConeType{type, FullDepth}; }
+
+  template<typename T> PossibleContents(T value) : value(value) {}
+
+public:
+  PossibleContents() : value(None()) {}
+  PossibleContents(const PossibleContents& other) = default;
+
+  // Most users will use one of the following static functions to construct a
+  // new instance:
+
+  static PossibleContents none() { return PossibleContents{None()}; }
+  static PossibleContents literal(Literal c) { return PossibleContents{c}; }
+  static PossibleContents global(Name name, Type type) {
+    return PossibleContents{GlobalInfo{name, type}};
+  }
+  // Helper for a cone type with depth 0, i.e., an exact type.
+  static PossibleContents exactType(Type type) {
+    return PossibleContents{ExactType(type)};
+  }
+  // Helper for a cone with unbounded depth, i.e., the full cone of all subtypes
+  // for that type.
+  static PossibleContents fullConeType(Type type) {
+    return PossibleContents{FullConeType(type)};
+  }
+  static PossibleContents coneType(Type type, Index depth) {
+    return PossibleContents{ConeType{type, depth}};
+  }
+  static PossibleContents many() { return PossibleContents{Many()}; }
+
+  // Helper for creating a PossibleContents based on a wasm type, that is, where
+  // all we know is the wasm type.
+  static PossibleContents fromType(Type type) {
+    assert(type != Type::none);
+
+    if (type.isRef()) {
+      // For a reference, subtyping matters.
+      return fullConeType(type);
+    }
+
+    if (type == Type::unreachable) {
+      // Nothing is possible here.
+      return none();
+    }
+
+    // Otherwise, this is a concrete MVP type.
+    assert(type.isConcrete());
+    return exactType(type);
+  }
+
+  PossibleContents& operator=(const PossibleContents& other) = default;
+
+  bool operator==(const PossibleContents& other) const {
+    return value == other.value;
+  }
+
+  bool operator!=(const PossibleContents& other) const {
+    return !(*this == other);
+  }
+
+  // Combine the information in a given PossibleContents to this one. The
+  // contents here will then include whatever content was possible in |other|.
+  [[nodiscard]] static PossibleContents combine(const PossibleContents& a,
+                                                const PossibleContents& b);
+
+  void combine(const PossibleContents& other) {
+    *this = PossibleContents::combine(*this, other);
+  }
+
+  // Removes anything not in |other| from this object, so that it ends up with
+  // only their intersection. Currently this only handles an intersection with a
+  // full cone.
+  void intersectWithFullCone(const PossibleContents& other);
+
+  bool isNone() const { return std::get_if<None>(&value); }
+  bool isLiteral() const { return std::get_if<Literal>(&value); }
+  bool isGlobal() const { return std::get_if<GlobalInfo>(&value); }
+  bool isConeType() const { return std::get_if<ConeType>(&value); }
+  bool isMany() const { return std::get_if<Many>(&value); }
+
+  Literal getLiteral() const {
+    assert(isLiteral());
+    return std::get<Literal>(value);
+  }
+
+  Name getGlobal() const {
+    assert(isGlobal());
+    return std::get<GlobalInfo>(value).name;
+  }
+
+  bool isNull() const { return isLiteral() && getLiteral().isNull(); }
+
+  // Return the relevant type here. Note that the *meaning* of the type varies
+  // by the contents: type $foo of a global means that type or any subtype, as a
+  // subtype might be written to it, while type $foo of a Literal or a ConeType
+  // with depth zero means that type and nothing else, etc. (see also
+  // hasExactType).
+  //
+  // If no type is possible, return unreachable; if many types are, return none.
+  Type getType() const {
+    if (auto* literal = std::get_if<Literal>(&value)) {
+      return literal->type;
+    } else if (auto* global = std::get_if<GlobalInfo>(&value)) {
+      return global->type;
+    } else if (auto* coneType = std::get_if<ConeType>(&value)) {
+      return coneType->type;
+    } else if (std::get_if<None>(&value)) {
+      return Type::unreachable;
+    } else if (std::get_if<Many>(&value)) {
+      return Type::none;
+    } else {
+      WASM_UNREACHABLE("bad value");
+    }
+  }
+
+  // Returns cone type info. This can be called on non-cone types as well, and
+  // it returns a cone that best describes them. That is, this is like getType()
+  // but it also provides an indication about the depth, if relevant. (If cone
+  // info is not relevant, like when getType() returns none or unreachable, the
+  // depth is set to 0.)
+  ConeType getCone() const {
+    if (auto* literal = std::get_if<Literal>(&value)) {
+      return ExactType(literal->type);
+    } else if (auto* global = std::get_if<GlobalInfo>(&value)) {
+      return FullConeType(global->type);
+    } else if (auto* coneType = std::get_if<ConeType>(&value)) {
+      return *coneType;
+    } else if (std::get_if<None>(&value)) {
+      return ExactType(Type::unreachable);
+    } else if (std::get_if<Many>(&value)) {
+      return ExactType(Type::none);
+    } else {
+      WASM_UNREACHABLE("bad value");
+    }
+  }
+
+  // Returns whether the relevant cone for this, as computed by getCone(), is of
+  // full size, that is, includes all subtypes.
+  bool hasFullCone() const { return getCone().depth == FullDepth; }
+
+  // Returns whether this is a cone type and also is of full size. This differs
+  // from hasFullCone() in that the former can return true for a global, for
+  // example, while this cannot (a global is not a cone type, but the
+  // information we have about its cone is that it is full).
+  bool isFullConeType() const { return isConeType() && hasFullCone(); }
+
+  // Returns whether the type we can report here is exact, that is, nothing of a
+  // strict subtype might show up - the contents here have an exact type.
+  //
+  // This returns false for None and Many, for whom it is not well-defined.
+  bool hasExactType() const {
+    if (isLiteral()) {
+      return true;
+    }
+
+    if (auto* coneType = std::get_if<ConeType>(&value)) {
+      return coneType->depth == 0;
+    }
+
+    return false;
+  }
+
+  // Returns whether the given contents have any intersection, that is, whether
+  // some value exists that can appear in both |a| and |b|. For example, if
+  // either is None, or if they are different literals, then they have no
+  // intersection.
+  static bool haveIntersection(const PossibleContents& a,
+                               const PossibleContents& b);
+
+  // Returns whether |a| is a subset of |b|, that is, all possible contents of
+  // |a| are also possible in |b|.
+  static bool isSubContents(const PossibleContents& a,
+                            const PossibleContents& b);
+
+  // Whether we can make an Expression* for this containing the proper contents.
+  // We can do that for a Literal (emitting a Const or RefFunc etc.) or a
+  // Global (emitting a GlobalGet), but not for anything else yet.
+  bool canMakeExpression() const { return isLiteral() || isGlobal(); }
+
+  Expression* makeExpression(Module& wasm) {
+    assert(canMakeExpression());
+    Builder builder(wasm);
+    if (isLiteral()) {
+      return builder.makeConstantExpression(getLiteral());
+    } else {
+      auto name = getGlobal();
+      return builder.makeGlobalGet(name, wasm.getGlobal(name)->type);
+    }
+  }
+
+  size_t hash() const {
+    // First hash the index of the variant, then add the internals for each.
+    size_t ret = std::hash<size_t>()(value.index());
+    if (isNone() || isMany()) {
+      // Nothing to add.
+    } else if (isLiteral()) {
+      rehash(ret, getLiteral());
+    } else if (isGlobal()) {
+      rehash(ret, getGlobal());
+    } else if (auto* coneType = std::get_if<ConeType>(&value)) {
+      rehash(ret, coneType->type);
+      rehash(ret, coneType->depth);
+    } else {
+      WASM_UNREACHABLE("bad variant");
+    }
+    return ret;
+  }
+
+  void dump(std::ostream& o, Module* wasm = nullptr) const {
+    o << '[';
+    if (isNone()) {
+      o << "None";
+    } else if (isLiteral()) {
+      o << "Literal " << getLiteral();
+      auto t = getType();
+      if (t.isRef()) {
+        auto h = t.getHeapType();
+        o << " HT: " << h;
+      }
+    } else if (isGlobal()) {
+      o << "GlobalInfo $" << getGlobal();
+    } else if (auto* coneType = std::get_if<ConeType>(&value)) {
+      auto t = coneType->type;
+      o << "ConeType " << t;
+      if (coneType->depth == 0) {
+        o << " exact";
+      } else {
+        o << " depth=" << coneType->depth;
+      }
+      if (t.isRef()) {
+        auto h = t.getHeapType();
+        o << " HT: " << h;
+        if (wasm && wasm->typeNames.count(h)) {
+          o << " $" << wasm->typeNames[h].name;
+        }
+        if (t.isNullable()) {
+          o << " null";
+        }
+      }
+    } else if (isMany()) {
+      o << "Many";
+    } else {
+      WASM_UNREACHABLE("bad variant");
+    }
+    o << ']';
+  }
+};
+
+// The various *Location structs (ExpressionLocation, ResultLocation, etc.)
+// describe particular locations where content can appear.
+
+// The location of a specific IR expression.
+struct ExpressionLocation {
+  Expression* expr;
+  // If this expression contains a tuple then each index in the tuple will have
+  // its own location with a corresponding tupleIndex. If this is not a tuple
+  // then we only use tupleIndex 0.
+  Index tupleIndex;
+  bool operator==(const ExpressionLocation& other) const {
+    return expr == other.expr && tupleIndex == other.tupleIndex;
+  }
+};
+
+// The location of one of the parameters of a function.
+struct ParamLocation {
+  Function* func;
+  Index index;
+  bool operator==(const ParamLocation& other) const {
+    return func == other.func && index == other.index;
+  }
+};
+
+// The location of one of the results of a function.
+struct ResultLocation {
+  Function* func;
+  Index index;
+  bool operator==(const ResultLocation& other) const {
+    return func == other.func && index == other.index;
+  }
+};
+
+// The location of a break target in a function, identified by its name.
+struct BreakTargetLocation {
+  Function* func;
+  Name target;
+  // As in ExpressionLocation, the index inside the tuple, or 0 if not a tuple.
+  // That is, if the branch target has a tuple type, then each branch to that
+  // location sends a tuple, and we'll have a separate BreakTargetLocation for
+  // each, indexed by the index in the tuple that the branch sends.
+  Index tupleIndex;
+  bool operator==(const BreakTargetLocation& other) const {
+    return func == other.func && target == other.target &&
+           tupleIndex == other.tupleIndex;
+  }
+};
+
+// The location of a global in the module.
+struct GlobalLocation {
+  Name name;
+  bool operator==(const GlobalLocation& other) const {
+    return name == other.name;
+  }
+};
+
+// The location of one of the parameters in a function signature.
+struct SignatureParamLocation {
+  HeapType type;
+  Index index;
+  bool operator==(const SignatureParamLocation& other) const {
+    return type == other.type && index == other.index;
+  }
+};
+
+// The location of one of the results in a function signature.
+struct SignatureResultLocation {
+  HeapType type;
+  Index index;
+  bool operator==(const SignatureResultLocation& other) const {
+    return type == other.type && index == other.index;
+  }
+};
+
+// The location of contents in a struct or array (i.e., things that can fit in a
+// dataref). Note that this is specific to this type - it does not include data
+// about subtypes or supertypes.
+struct DataLocation {
+  HeapType type;
+  // The index of the field in a struct, or 0 for an array (where we do not
+  // attempt to differentiate by index).
+  Index index;
+  bool operator==(const DataLocation& other) const {
+    return type == other.type && index == other.index;
+  }
+};
+
+// The location of anything written to a particular tag.
+struct TagLocation {
+  Name tag;
+  // If the tag has more than one element, we'll have a separate TagLocation for
+  // each, with corresponding indexes. If the tag has just one element we'll
+  // only have one TagLocation with index 0.
+  Index tupleIndex;
+  bool operator==(const TagLocation& other) const {
+    return tag == other.tag && tupleIndex == other.tupleIndex;
+  }
+};
+
+// A null value. This is used as the location of the default value of a var in a
+// function, a null written to a struct field in struct.new_with_default, etc.
+struct NullLocation {
+  Type type;
+  bool operator==(const NullLocation& other) const {
+    return type == other.type;
+  }
+};
+
+// A special type of location that does not refer to something concrete in the
+// wasm, but is used to optimize the graph. A "cone read" is a struct.get or
+// array.get of a type that is not exact, so it can read from either that type
+// of some of the subtypes (up to a particular subtype depth).
+//
+// In general a read of a cone type + depth (as opposed to an exact type) will
+// require N incoming links, from each of the N subtypes - and we need that
+// for each struct.get of a cone. If there are M such gets then we have N * M
+// edges for this. Instead, we make a single canonical "cone read" location, and
+// add a single link to it from each get, which is only N + M (plus the cost
+// of adding "latency" in requiring an additional step along the way for the
+// data to flow along).
+struct ConeReadLocation {
+  HeapType type;
+  // As in PossibleContents, this represents the how deep we go with subtypes.
+  // 0 means an exact type, 1 means immediate subtypes, etc. (Note that 0 is not
+  // needed since that is what DataLocation already is.)
+  Index depth;
+  // The index of the field in a struct, or 0 for an array (where we do not
+  // attempt to differentiate by index).
+  Index index;
+  bool operator==(const ConeReadLocation& other) const {
+    return type == other.type && depth == other.depth && index == other.index;
+  }
+};
+
+// A location is a variant over all the possible flavors of locations that we
+// have.
+using Location = std::variant<ExpressionLocation,
+                              ParamLocation,
+                              ResultLocation,
+                              BreakTargetLocation,
+                              GlobalLocation,
+                              SignatureParamLocation,
+                              SignatureResultLocation,
+                              DataLocation,
+                              TagLocation,
+                              NullLocation,
+                              ConeReadLocation>;
+
+} // namespace wasm
+
+namespace std {
+
+std::ostream& operator<<(std::ostream& stream,
+                         const wasm::PossibleContents& contents);
+
+template<> struct hash<wasm::PossibleContents> {
+  size_t operator()(const wasm::PossibleContents& contents) const {
+    return contents.hash();
+  }
+};
+
+// Define hashes of all the *Location flavors so that Location itself is
+// hashable and we can use it in unordered maps and sets.
+
+template<> struct hash<wasm::ExpressionLocation> {
+  size_t operator()(const wasm::ExpressionLocation& loc) const {
+    return std::hash<std::pair<size_t, wasm::Index>>{}(
+      {size_t(loc.expr), loc.tupleIndex});
+  }
+};
+
+template<> struct hash<wasm::ParamLocation> {
+  size_t operator()(const wasm::ParamLocation& loc) const {
+    return std::hash<std::pair<size_t, wasm::Index>>{}(
+      {size_t(loc.func), loc.index});
+  }
+};
+
+template<> struct hash<wasm::ResultLocation> {
+  size_t operator()(const wasm::ResultLocation& loc) const {
+    return std::hash<std::pair<size_t, wasm::Index>>{}(
+      {size_t(loc.func), loc.index});
+  }
+};
+
+template<> struct hash<wasm::BreakTargetLocation> {
+  size_t operator()(const wasm::BreakTargetLocation& loc) const {
+    return std::hash<std::tuple<size_t, wasm::Name, wasm::Index>>{}(
+      {size_t(loc.func), loc.target, loc.tupleIndex});
+  }
+};
+
+template<> struct hash<wasm::GlobalLocation> {
+  size_t operator()(const wasm::GlobalLocation& loc) const {
+    return std::hash<wasm::Name>{}(loc.name);
+  }
+};
+
+template<> struct hash<wasm::SignatureParamLocation> {
+  size_t operator()(const wasm::SignatureParamLocation& loc) const {
+    return std::hash<std::pair<wasm::HeapType, wasm::Index>>{}(
+      {loc.type, loc.index});
+  }
+};
+
+template<> struct hash<wasm::SignatureResultLocation> {
+  size_t operator()(const wasm::SignatureResultLocation& loc) const {
+    return std::hash<std::pair<wasm::HeapType, wasm::Index>>{}(
+      {loc.type, loc.index});
+  }
+};
+
+template<> struct hash<wasm::DataLocation> {
+  size_t operator()(const wasm::DataLocation& loc) const {
+    return std::hash<std::pair<wasm::HeapType, wasm::Index>>{}(
+      {loc.type, loc.index});
+  }
+};
+
+template<> struct hash<wasm::TagLocation> {
+  size_t operator()(const wasm::TagLocation& loc) const {
+    return std::hash<std::pair<wasm::Name, wasm::Index>>{}(
+      {loc.tag, loc.tupleIndex});
+  }
+};
+
+template<> struct hash<wasm::NullLocation> {
+  size_t operator()(const wasm::NullLocation& loc) const {
+    return std::hash<wasm::Type>{}(loc.type);
+  }
+};
+
+template<> struct hash<wasm::ConeReadLocation> {
+  size_t operator()(const wasm::ConeReadLocation& loc) const {
+    return std::hash<std::tuple<wasm::HeapType, wasm::Index, wasm::Index>>{}(
+      {loc.type, loc.depth, loc.index});
+  }
+};
+
+} // namespace std
+
+namespace wasm {
+
+// Analyze the entire wasm file to find which contents are possible in which
+// locations. This assumes a closed world and starts from roots - newly created
+// values - and propagates them to the locations they reach. After the
+// analysis the user of this class can ask which contents are possible at any
+// location.
+//
+// This focuses on useful information for the typical user of this API.
+// Specifically, we find out:
+//
+//  1. What locations have no content reaching them at all. That means the code
+//     is unreachable. (Other passes may handle this, but ContentOracle does it
+//     for all things, so it might catch situations other passes do not cover;
+//     and, it takes no effort to support this here).
+//  2. For all locations, we try to find when they must contain a constant value
+//     like i32(42) or ref.func(foo).
+//  3. For locations that contain references, information about the subtypes
+//     possible there. For example, if something has wasm type anyref in the IR,
+//     we might find it must contain an exact type of something specific.
+//
+// Note that there is not much use in providing type info for locations that are
+// *not* references. If a local is i32, for example, then it cannot contain any
+// subtype anyhow, since i32 is not a reference and has no subtypes. And we know
+// the type i32 from the wasm anyhow, that is, the caller will know it.
+// Therefore the only useful information we can provide on top of the info
+// already in the wasm is either that nothing can be there (1, above), or that a
+// constant must be there (2, above), and so we do not make an effort to track
+// non-reference types here. This makes the internals of ContentOracle simpler
+// and faster. A noticeable outcome of that is that querying the contents of an
+// i32 local will return Many and not ConeType{i32, 0} (assuming we could not
+// infer either that there must be nothing there, or a constant). Again, the
+// caller is assumed to know the wasm IR type anyhow, and also other
+// optimization passes work on the types in the IR, so we do not focus on that
+// here.
+class ContentOracle {
+  Module& wasm;
+
+  void analyze();
+
+public:
+  ContentOracle(Module& wasm) : wasm(wasm) { analyze(); }
+
+  // Get the contents possible at a location.
+  PossibleContents getContents(Location location) {
+    auto iter = locationContents.find(location);
+    if (iter == locationContents.end()) {
+      // We know of no possible contents here.
+      return PossibleContents::none();
+    }
+    return iter->second;
+  }
+
+  // Helper for the common case of an expression location that is not a
+  // multivalue.
+  PossibleContents getContents(Expression* curr) {
+    assert(curr->type.size() == 1);
+    return getContents(ExpressionLocation{curr, 0});
+  }
+
+private:
+  std::unordered_map<Location, PossibleContents> locationContents;
+};
+
+} // namespace wasm
+
+#endif // wasm_ir_possible_contents_h
diff --git a/src/ir/properties.h b/src/ir/properties.h
index 0789816..bd0487d 100644
--- a/src/ir/properties.h
+++ b/src/ir/properties.h
@@ -24,15 +24,6 @@
 
 namespace wasm::Properties {
 
-inline bool emitsBoolean(Expression* curr) {
-  if (auto* unary = curr->dynCast<Unary>()) {
-    return unary->isRelational();
-  } else if (auto* binary = curr->dynCast<Binary>()) {
-    return binary->isRelational();
-  }
-  return false;
-}
-
 inline bool isSymmetric(Binary* binary) {
   switch (binary->op) {
     case AddInt32:
@@ -51,8 +42,12 @@ inline bool isSymmetric(Binary* binary) {
     case EqInt64:
     case NeInt64:
 
+    case MinFloat32:
+    case MaxFloat32:
     case EqFloat32:
     case NeFloat32:
+    case MinFloat64:
+    case MaxFloat64:
     case EqFloat64:
     case NeFloat64:
       return true;
@@ -84,8 +79,8 @@ inline bool isNamedControlFlow(Expression* curr) {
 // at compile time, and passes that propagate constants can try to propagate it.
 // Constant expressions are also allowed in global initializers in wasm. Also
 // when two constant expressions compare equal at compile time, their values at
-// runtime will be equal as well.
-// TODO: look into adding more things here like RttCanon.
+// runtime will be equal as well. TODO: combine this with
+// isValidInConstantExpression or find better names(#4845)
 inline bool isSingleConstantExpression(const Expression* curr) {
   return curr->is<Const>() || curr->is<RefNull>() || curr->is<RefFunc>();
 }
@@ -115,7 +110,7 @@ inline Literal getLiteral(const Expression* curr) {
   } else if (auto* n = curr->dynCast<RefNull>()) {
     return Literal(n->type);
   } else if (auto* r = curr->dynCast<RefFunc>()) {
-    return Literal(r->func, r->type);
+    return Literal(r->func, r->type.getHeapType());
   } else if (auto* i = curr->dynCast<I31New>()) {
     if (auto* c = i->value->dynCast<Const>()) {
       return Literal::makeI31(c->value.geti32());
@@ -249,17 +244,29 @@ inline Index getZeroExtBits(Expression* curr) {
 // way to the final value falling through, potentially through multiple
 // intermediate expressions.
 //
+// Behavior wrt tee/br_if is customizable, since in some cases we do not want to
+// look through them (for example, the type of a tee is related to the local,
+// not the value, so if we returned the fallthrough of the tee we'd have a
+// possible difference between the type in the IR and the type of the value,
+// which some cases care about; the same for a br_if, whose type is related to
+// the branch target).
+//
 // TODO: Receive a Module instead of FeatureSet, to pass to EffectAnalyzer?
-inline Expression* getImmediateFallthrough(Expression* curr,
-                                           const PassOptions& passOptions,
-                                           Module& module) {
+
+enum class FallthroughBehavior { AllowTeeBrIf, NoTeeBrIf };
+
+inline Expression* getImmediateFallthrough(
+  Expression* curr,
+  const PassOptions& passOptions,
+  Module& module,
+  FallthroughBehavior behavior = FallthroughBehavior::AllowTeeBrIf) {
   // If the current node is unreachable, there is no value
   // falling through.
   if (curr->type == Type::unreachable) {
     return curr;
   }
   if (auto* set = curr->dynCast<LocalSet>()) {
-    if (set->isTee()) {
+    if (set->isTee() && behavior == FallthroughBehavior::AllowTeeBrIf) {
       return set->value;
     }
   } else if (auto* block = curr->dynCast<Block>()) {
@@ -279,7 +286,22 @@ inline Expression* getImmediateFallthrough(Expression* curr,
       }
     }
   } else if (auto* br = curr->dynCast<Break>()) {
-    if (br->condition && br->value) {
+    // Note that we must check for the ability to reorder the condition and the
+    // value, as the value is first, which would be a problem here:
+    //
+    //  (br_if ..
+    //    (local.get $x)    ;; value
+    //    (tee_local $x ..) ;; condition
+    //  )
+    //
+    // We must not say that the fallthrough value is $x, since it is the
+    // *earlier* value of $x before the tee that is passed out. But, if we can
+    // reorder then that means that the value could have been last and so we do
+    // know the fallthrough in that case.
+    if (br->condition && br->value &&
+        behavior == FallthroughBehavior::AllowTeeBrIf &&
+        EffectAnalyzer::canReorder(
+          passOptions, module, br->condition, br->value)) {
       return br->value;
     }
   } else if (auto* tryy = curr->dynCast<Try>()) {
@@ -298,11 +320,13 @@ inline Expression* getImmediateFallthrough(Expression* curr,
 
 // Similar to getImmediateFallthrough, but looks through multiple children to
 // find the final value that falls through.
-inline Expression* getFallthrough(Expression* curr,
-                                  const PassOptions& passOptions,
-                                  Module& module) {
+inline Expression* getFallthrough(
+  Expression* curr,
+  const PassOptions& passOptions,
+  Module& module,
+  FallthroughBehavior behavior = FallthroughBehavior::AllowTeeBrIf) {
   while (1) {
-    auto* next = getImmediateFallthrough(curr, passOptions, module);
+    auto* next = getImmediateFallthrough(curr, passOptions, module, behavior);
     if (next == curr) {
       return curr;
     }
@@ -416,8 +440,8 @@ bool isGenerative(Expression* curr, FeatureSet features);
 
 inline bool isValidInConstantExpression(Expression* expr, FeatureSet features) {
   if (isSingleConstantExpression(expr) || expr->is<GlobalGet>() ||
-      expr->is<RttCanon>() || expr->is<RttSub>() || expr->is<StructNew>() ||
-      expr->is<ArrayNew>() || expr->is<ArrayInit>() || expr->is<I31New>()) {
+      expr->is<StructNew>() || expr->is<ArrayNew>() || expr->is<ArrayInit>() ||
+      expr->is<I31New>() || expr->is<StringConst>()) {
     return true;
   }
 
diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h
index bc2eb54..9f88098 100644
--- a/src/ir/struct-utils.h
+++ b/src/ir/struct-utils.h
@@ -50,6 +50,7 @@ struct StructValuesMap : public std::unordered_map<HeapType, StructValues<T>> {
   // When we access an item, if it does not already exist, create it with a
   // vector of the right length for that type.
   StructValues<T>& operator[](HeapType type) {
+    assert(type.isStruct());
     auto inserted = this->insert({type, {}});
     auto& values = inserted.first->second;
     if (inserted.second) {
@@ -130,6 +131,8 @@ struct StructScanner
   : public WalkerPass<PostWalker<StructScanner<T, SubType>>> {
   bool isFunctionParallel() override { return true; }
 
+  bool modifiesBinaryenIR() override { return false; }
+
   StructScanner(FunctionStructValuesMap<T>& functionNewInfos,
                 FunctionStructValuesMap<T>& functionSetGetInfos)
     : functionNewInfos(functionNewInfos),
@@ -157,7 +160,7 @@ struct StructScanner
 
   void visitStructSet(StructSet* curr) {
     auto type = curr->ref->type;
-    if (type == Type::unreachable) {
+    if (type == Type::unreachable || type.isNull()) {
       return;
     }
 
@@ -171,7 +174,7 @@ struct StructScanner
 
   void visitStructGet(StructGet* curr) {
     auto type = curr->ref->type;
-    if (type == Type::unreachable) {
+    if (type == Type::unreachable || type.isNull()) {
       return;
     }
 
@@ -189,7 +192,10 @@ struct StructScanner
     // (otherwise, we'd need to consider both the type actually written and the
     // type of the fallthrough, somehow).
     auto* fallthrough = Properties::getFallthrough(
-      expr, this->getPassOptions(), *this->getModule());
+      expr,
+      this->getPassOptions(),
+      *this->getModule(),
+      static_cast<SubType*>(this)->getFallthroughBehavior());
     if (fallthrough->type == expr->type) {
       expr = fallthrough;
     }
@@ -203,6 +209,11 @@ struct StructScanner
     static_cast<SubType*>(this)->noteExpression(expr, type, index, info);
   }
 
+  Properties::FallthroughBehavior getFallthroughBehavior() {
+    // By default, look at and use tee&br_if fallthrough values.
+    return Properties::FallthroughBehavior::AllowTeeBrIf;
+  }
+
   FunctionStructValuesMap<T>& functionNewInfos;
   FunctionStructValuesMap<T>& functionSetGetInfos;
 };
@@ -260,7 +271,7 @@ private:
       if (toSubTypes) {
         // Propagate shared fields to the subtypes.
         auto numFields = type.getStruct().fields.size();
-        for (auto subType : subTypes.getSubTypes(type)) {
+        for (auto subType : subTypes.getStrictSubTypes(type)) {
           auto& subInfos = combinedInfos[subType];
           for (Index i = 0; i < numFields; i++) {
             if (subInfos[i].combine(infos[i])) {
diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h
index 2315934..198a69b 100644
--- a/src/ir/subtypes.h
+++ b/src/ir/subtypes.h
@@ -18,32 +18,48 @@
 #define wasm_ir_subtypes_h
 
 #include "ir/module-utils.h"
+#include "support/topological_sort.h"
 #include "wasm.h"
 
 namespace wasm {
 
 // Analyze subtyping relationships and provide useful interfaces to discover
 // them.
+//
+// This only scans user types, and not basic types like HeapType::eq.
 struct SubTypes {
-  SubTypes(Module& wasm) {
-    types = ModuleUtils::collectHeapTypes(wasm);
+  SubTypes(const std::vector<HeapType>& types) : types(types) {
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "SubTypes requires explicit supers";
+    }
     for (auto type : types) {
       note(type);
     }
   }
 
-  const std::vector<HeapType>& getSubTypes(HeapType type) {
-    return typeSubTypes[type];
+  SubTypes(Module& wasm) : SubTypes(ModuleUtils::collectHeapTypes(wasm)) {}
+
+  const std::vector<HeapType>& getStrictSubTypes(HeapType type) const {
+    assert(!type.isBasic());
+    if (auto iter = typeSubTypes.find(type); iter != typeSubTypes.end()) {
+      return iter->second;
+    }
+
+    // No entry exists. Return a canonical constant empty vec, to avoid
+    // allocation.
+    static const std::vector<HeapType> empty;
+    return empty;
   }
 
   // Get all subtypes of a type, and their subtypes and so forth, recursively.
-  std::vector<HeapType> getAllSubTypes(HeapType type) {
+  std::vector<HeapType> getAllStrictSubTypes(HeapType type) {
     std::vector<HeapType> ret, work;
     work.push_back(type);
     while (!work.empty()) {
       auto curr = work.back();
       work.pop_back();
-      for (auto sub : getSubTypes(curr)) {
+      for (auto sub : getStrictSubTypes(curr)) {
         ret.push_back(sub);
         work.push_back(sub);
       }
@@ -51,20 +67,119 @@ struct SubTypes {
     return ret;
   }
 
-  // Get all supertypes of a type. The order in the output vector is with the
-  // immediate supertype first, then its supertype, and so forth.
-  std::vector<HeapType> getAllSuperTypes(HeapType type) {
-    std::vector<HeapType> ret;
-    while (1) {
-      auto super = type.getSuperType();
-      if (!super) {
-        return ret;
+  // Like getAllStrictSubTypes, but also includes the type itself.
+  std::vector<HeapType> getAllSubTypes(HeapType type) {
+    auto ret = getAllStrictSubTypes(type);
+    ret.push_back(type);
+    return ret;
+  }
+
+  // Computes the depth of children for each type. This is 0 if the type has no
+  // subtypes, 1 if it has subtypes but none of those have subtypes themselves,
+  // and so forth.
+  //
+  // This depth ignores bottom types.
+  std::unordered_map<HeapType, Index> getMaxDepths() {
+    struct DepthSort : TopologicalSort<HeapType, DepthSort> {
+      const SubTypes& parent;
+
+      DepthSort(const SubTypes& parent) : parent(parent) {
+        for (auto type : parent.types) {
+          // The roots are types with no supertype.
+          if (!type.getSuperType()) {
+            push(type);
+          }
+        }
+      }
+
+      void pushPredecessors(HeapType type) {
+        // Things we need to process before each type are its subtypes. Once we
+        // know their depth, we can easily compute our own.
+        for (auto pred : parent.getStrictSubTypes(type)) {
+          push(pred);
+        }
+      }
+    };
+
+    std::unordered_map<HeapType, Index> depths;
+
+    for (auto type : DepthSort(*this)) {
+      // Begin with depth 0, then take into account the subtype depths.
+      Index depth = 0;
+      for (auto subType : getStrictSubTypes(type)) {
+        depth = std::max(depth, depths[subType] + 1);
+      }
+      depths[type] = depth;
+    }
+
+    // Add the max depths of basic types.
+    // TODO: update when we get structtype
+    for (auto type : types) {
+      HeapType basic;
+      if (type.isStruct()) {
+        basic = HeapType::data;
+      } else if (type.isArray()) {
+        basic = HeapType::array;
+      } else {
+        assert(type.isSignature());
+        basic = HeapType::func;
+      }
+      depths[basic] = std::max(depths[basic], depths[type] + 1);
+    }
+
+    depths[HeapType::data] =
+      std::max(depths[HeapType::data], depths[HeapType::array] + 1);
+    depths[HeapType::eq] = std::max(Index(1), depths[HeapType::data] + 1);
+    depths[HeapType::any] = depths[HeapType::eq] + 1;
+
+    return depths;
+  }
+
+  // Efficiently iterate on subtypes of a type, up to a particular depth (depth
+  // 0 means not to traverse subtypes, etc.). The callback function receives
+  // (type, depth).
+  template<typename F> void iterSubTypes(HeapType type, Index depth, F func) {
+    // Start by traversing the type itself.
+    func(type, 0);
+
+    if (depth == 0) {
+      // Nothing else to scan.
+      return;
+    }
+
+    // getStrictSubTypes() returns vectors of subtypes, so for efficiency store
+    // pointers to those in our work queue to avoid allocations. See the note
+    // below on typeSubTypes for why this is safe.
+    struct Item {
+      const std::vector<HeapType>* vec;
+      Index depth;
+    };
+
+    // Real-world type hierarchies tend to have a limited depth, so try to avoid
+    // allocations in our work queue with a SmallVector.
+    SmallVector<Item, 10> work;
+
+    // Start with the subtypes of the base type. Those have depth 1.
+    work.push_back({&getStrictSubTypes(type), 1});
+
+    while (!work.empty()) {
+      auto& item = work.back();
+      work.pop_back();
+      auto currDepth = item.depth;
+      auto& currVec = *item.vec;
+      assert(currDepth <= depth);
+      for (auto type : currVec) {
+        func(type, currDepth);
+        auto* subVec = &getStrictSubTypes(type);
+        if (currDepth + 1 <= depth && !subVec->empty()) {
+          work.push_back({subVec, currDepth + 1});
+        }
       }
-      ret.push_back(*super);
-      type = *super;
     }
   }
 
+  // All the types in the program. This is computed here anyhow, and can be
+  // useful for callers to iterate on, so it is public.
   std::vector<HeapType> types;
 
 private:
@@ -76,6 +191,9 @@ private:
   }
 
   // Maps a type to its subtypes.
+  //
+  // After our constructor we never modify this data structure, so we can take
+  // references to the vectors here safely.
   std::unordered_map<HeapType, std::vector<HeapType>> typeSubTypes;
 };
 
diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp
index 0d47f15..fb92853 100644
--- a/src/ir/table-utils.cpp
+++ b/src/ir/table-utils.cpp
@@ -76,7 +76,8 @@ bool usesExpressions(ElementSegment* curr, Module* module) {
   // declare a type that is a subtype of that, so it must use the post-MVP form
   // of using expressions.
   bool hasTableOfSpecializedType =
-    curr->table.is() && module->getTable(curr->table)->type != Type::funcref;
+    curr->table.is() &&
+    module->getTable(curr->table)->type != Type(HeapType::func, Nullable);
 
   return !allElementsRefFunc || hasTableOfSpecializedType;
 }
diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp
index ff154dd..2d75f09 100644
--- a/src/ir/type-updating.cpp
+++ b/src/ir/type-updating.cpp
@@ -16,6 +16,7 @@
 
 #include "type-updating.h"
 #include "find_all.h"
+#include "ir/local-structural-dominance.h"
 #include "ir/module-utils.h"
 #include "ir/utils.h"
 #include "wasm-type.h"
@@ -32,6 +33,15 @@ void GlobalTypeRewriter::update() {
   }
   typeBuilder.grow(indexedTypes.types.size());
 
+  // All the input types are distinct, so we need to make sure the output types
+  // are distinct as well. Further, the new types may have more recursions than
+  // the original types, so the old recursion groups may not be sufficient any
+  // more. Both of these problems are solved by putting all the new types into a
+  // single large recursion group.
+  // TODO: When we properly analyze which types are external and which are
+  // internal to the module, only optimize internal types.
+  typeBuilder.createRecGroup(0, typeBuilder.size());
+
   // Create the temporary heap types.
   for (Index i = 0; i < indexedTypes.types.size(); i++) {
     auto type = indexedTypes.types[i];
@@ -70,7 +80,8 @@ void GlobalTypeRewriter::update() {
 
     // Apply a super, if there is one
     if (auto super = type.getSuperType()) {
-      typeBuilder.setSubType(i, indexedTypes.indices[*super]);
+      typeBuilder.setSubType(
+        i, typeBuilder.getTempHeapType(indexedTypes.indices[*super]));
     }
   }
 
@@ -102,15 +113,14 @@ void GlobalTypeRewriter::update() {
 
     CodeUpdater(OldToNewTypes& oldToNewTypes) : oldToNewTypes(oldToNewTypes) {}
 
-    CodeUpdater* create() override { return new CodeUpdater(oldToNewTypes); }
+    std::unique_ptr<Pass> create() override {
+      return std::make_unique<CodeUpdater>(oldToNewTypes);
+    }
 
     Type getNew(Type type) {
       if (type.isRef()) {
         return Type(getNew(type.getHeapType()), type.getNullability());
       }
-      if (type.isRtt()) {
-        return Type(Rtt(type.getRtt().depth, getNew(type.getHeapType())));
-      }
       if (type.isTuple()) {
         auto tuple = type.getTuple();
         for (auto& t : tuple.types) {
@@ -137,6 +147,26 @@ void GlobalTypeRewriter::update() {
     }
 
     void visitExpression(Expression* curr) {
+      // local.get and local.tee are special in that their type is tied to the
+      // type of the local in the function, which is tied to the signature. That
+      // means we must update it based on the signature, and not on the old type
+      // in the local.
+      //
+      // We have already updated function signatures by the time we get here,
+      // which means we can just apply the current local type that we see (there
+      // is no need to call getNew(), which we already did on the function's
+      // signature itself).
+      if (auto* get = curr->dynCast<LocalGet>()) {
+        curr->type = getFunction()->getLocalType(get->index);
+        return;
+      } else if (auto* tee = curr->dynCast<LocalSet>()) {
+        // Rule out a local.set and unreachable code.
+        if (tee->type != Type::none && tee->type != Type::unreachable) {
+          curr->type = getFunction()->getLocalType(tee->index);
+        }
+        return;
+      }
+
       // Update the type to the new one.
       curr->type = getNew(curr->type);
 
@@ -172,6 +202,16 @@ void GlobalTypeRewriter::update() {
 
   CodeUpdater updater(oldToNewTypes);
   PassRunner runner(&wasm);
+
+  // Update functions first, so that we see the updated types for locals (which
+  // can change if the function signature changes).
+  for (auto& func : wasm.functions) {
+    func->type = updater.getNew(func->type);
+    for (auto& var : func->vars) {
+      var = updater.getNew(var);
+    }
+  }
+
   updater.run(&runner, &wasm);
   updater.walkModuleCode(&wasm);
 
@@ -185,12 +225,6 @@ void GlobalTypeRewriter::update() {
   for (auto& global : wasm.globals) {
     global->type = updater.getNew(global->type);
   }
-  for (auto& func : wasm.functions) {
-    func->type = updater.getNew(func->type);
-    for (auto& var : func->vars) {
-      var = updater.getNew(var);
-    }
-  }
   for (auto& tag : wasm.tags) {
     tag->sig = updater.getNew(tag->sig);
   }
@@ -218,18 +252,6 @@ Type GlobalTypeRewriter::getTempType(Type type) {
       typeBuilder.getTempHeapType(indexedTypes.indices[heapType]),
       type.getNullability());
   }
-  if (type.isRtt()) {
-    auto rtt = type.getRtt();
-    auto newRtt = rtt;
-    auto heapType = type.getHeapType();
-    if (!indexedTypes.indices.count(heapType)) {
-      // See above with references.
-      return type;
-    }
-    newRtt.heapType =
-      typeBuilder.getTempHeapType(indexedTypes.indices[heapType]);
-    return typeBuilder.getTempRttType(newRtt);
-  }
   if (type.isTuple()) {
     auto& tuple = type.getTuple();
     auto newTuple = tuple;
@@ -250,8 +272,12 @@ bool canHandleAsLocal(Type type) {
 }
 
 void handleNonDefaultableLocals(Function* func, Module& wasm) {
-  // Check if this is an issue.
   if (wasm.features.hasGCNNLocals()) {
+    // We have nothing to fix up: all locals are allowed.
+    return;
+  }
+  if (!wasm.features.hasReferenceTypes()) {
+    // No references, so no non-nullable ones at all.
     return;
   }
   bool hasNonNullable = false;
@@ -262,6 +288,26 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
     }
   }
   if (!hasNonNullable) {
+    // No non-nullable types exist in practice.
+    return;
+  }
+
+  // Non-nullable locals exist, which we may need to fix up. See if they
+  // validate as they are, that is, if they fall within the validation rules of
+  // the wasm spec. We do not need to modify such locals.
+  LocalStructuralDominance info(
+    func, wasm, LocalStructuralDominance::NonNullableOnly);
+  std::unordered_set<Index> badIndexes;
+  for (auto index : info.nonDominatingIndices) {
+    badIndexes.insert(index);
+
+    // LocalStructuralDominance should have only looked at non-nullable indexes
+    // since we told it to ignore nullable ones. Also, params always dominate
+    // and should not appear here.
+    assert(func->getLocalType(index).isNonNullable());
+    assert(!func->isParam(index));
+  }
+  if (badIndexes.empty()) {
     return;
   }
 
@@ -269,11 +315,9 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
   Builder builder(wasm);
   for (auto** getp : FindAllPointers<LocalGet>(func->body).list) {
     auto* get = (*getp)->cast<LocalGet>();
-    if (!func->isVar(get->index)) {
-      // We do not need to process params, which can legally be non-nullable.
-      continue;
+    if (badIndexes.count(get->index)) {
+      *getp = fixLocalGet(get, wasm);
     }
-    *getp = fixLocalGet(get, wasm);
   }
 
   // Update tees, whose type must match the local (if the wasm spec changes for
@@ -289,8 +333,8 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
     if (!set->isTee() || set->type == Type::unreachable) {
       continue;
     }
-    auto type = func->getLocalType(set->index);
-    if (type.isNonNullable()) {
+    if (badIndexes.count(set->index)) {
+      auto type = func->getLocalType(set->index);
       set->type = Type(type.getHeapType(), Nullable);
       *setp = builder.makeRefAs(RefAsNonNull, set);
     }
@@ -298,12 +342,14 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
 
   // Rewrite the types of the function's vars (which we can do now, after we
   // are done using them to know which local.gets etc to fix).
-  for (auto& type : func->vars) {
-    type = getValidLocalType(type, wasm.features);
+  for (auto index : badIndexes) {
+    func->vars[index - func->getNumParams()] =
+      getValidLocalType(func->getLocalType(index), wasm.features);
   }
 }
 
 Type getValidLocalType(Type type, FeatureSet features) {
+  // TODO: this should handle tuples with a non-nullable item
   assert(canHandleAsLocal(type));
   if (type.isNonNullable() && !features.hasGCNNLocals()) {
     type = Type(type.getHeapType(), Nullable);
@@ -323,7 +369,8 @@ Expression* fixLocalGet(LocalGet* get, Module& wasm) {
 
 void updateParamTypes(Function* func,
                       const std::vector<Type>& newParamTypes,
-                      Module& wasm) {
+                      Module& wasm,
+                      LocalUpdatingMode localUpdating) {
   // Before making this update, we must be careful if the param was "reused",
   // specifically, if it is assigned a less-specific type in the body then
   // we'd get a validation error when we refine it. To handle that, if a less-
@@ -369,7 +416,11 @@ void updateParamTypes(Function* func,
       if (iter != paramFixups.end()) {
         auto fixup = iter->second;
         contents.push_back(builder.makeLocalSet(
-          fixup, builder.makeLocalGet(index, newParamTypes[index])));
+          fixup,
+          builder.makeLocalGet(index,
+                               localUpdating == Update
+                                 ? newParamTypes[index]
+                                 : func->getLocalType(index))));
       }
     }
     contents.push_back(func->body);
@@ -391,17 +442,19 @@ void updateParamTypes(Function* func,
   }
 
   // Update local.get/local.tee operations that use the modified param type.
-  for (auto* get : gets.list) {
-    auto index = get->index;
-    if (func->isParam(index)) {
-      get->type = newParamTypes[index];
+  if (localUpdating == Update) {
+    for (auto* get : gets.list) {
+      auto index = get->index;
+      if (func->isParam(index)) {
+        get->type = newParamTypes[index];
+      }
     }
-  }
-  for (auto* set : sets.list) {
-    auto index = set->index;
-    if (func->isParam(index) && set->isTee()) {
-      set->type = newParamTypes[index];
-      set->finalize();
+    for (auto* set : sets.list) {
+      auto index = set->index;
+      if (func->isParam(index) && set->isTee()) {
+        set->type = newParamTypes[index];
+        set->finalize();
+      }
     }
   }
 
diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h
index d1c4af6..50ba9ef 100644
--- a/src/ir/type-updating.h
+++ b/src/ir/type-updating.h
@@ -409,15 +409,22 @@ Expression* fixLocalGet(LocalGet* get, Module& wasm);
 
 // Applies new types of parameters to a function. This does all the necessary
 // changes aside from altering the function type, which the caller is expected
-// to do (the caller might simply change the type, but in other cases the caller
-// might be rewriting the types and need to preserve their identity in terms of
-// nominal typing, so we don't change the type here). The specific things this
-// function does are to update the types of local.get/tee operations,
-// refinalize, etc., basically all operations necessary to ensure validation
-// with the new types.
+// to do after we run (the caller might simply change the type, but in other
+// cases the caller  might be rewriting the types and need to preserve their
+// identity in terms of nominal typing, so we don't change the type here). The
+// specific things this function does are to update the types of local.get/tee
+// operations, refinalize, etc., basically all operations necessary to ensure
+// validation with the new types.
+//
+// While doing so, we can either update or not update the types of local.get and
+// local.tee operations. (We do not update them here if we'll be doing an update
+// later in the caller, which is the case if we are rewriting function types).
+enum LocalUpdatingMode { Update, DoNotUpdate };
+
 void updateParamTypes(Function* func,
                       const std::vector<Type>& newParamTypes,
-                      Module& wasm);
+                      Module& wasm,
+                      LocalUpdatingMode localUpdating = Update);
 
 } // namespace TypeUpdating
 
diff --git a/src/ir/utils.h b/src/ir/utils.h
index 7abc87b..72aa701 100644
--- a/src/ir/utils.h
+++ b/src/ir/utils.h
@@ -119,7 +119,13 @@ struct ReFinalize
   : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<ReFinalize>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new ReFinalize; }
+  // Re-running finalize() does not change the types of locals, so validation is
+  // preserved.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<ReFinalize>();
+  }
 
   ReFinalize() { name = "refinalize"; }
 
@@ -132,13 +138,12 @@ struct ReFinalize
 
 #include "wasm-delegations.def"
 
-  void visitFunction(Function* curr);
-
   void visitExport(Export* curr);
   void visitGlobal(Global* curr);
   void visitTable(Table* curr);
   void visitElementSegment(ElementSegment* curr);
   void visitMemory(Memory* curr);
+  void visitDataSegment(DataSegment* curr);
   void visitTag(Tag* curr);
   void visitModule(Module* curr);
 
@@ -163,6 +168,7 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
   void visitTable(Table* curr) { WASM_UNREACHABLE("unimp"); }
   void visitElementSegment(ElementSegment* curr) { WASM_UNREACHABLE("unimp"); }
   void visitMemory(Memory* curr) { WASM_UNREACHABLE("unimp"); }
+  void visitDataSegment(DataSegment* curr) { WASM_UNREACHABLE("unimp"); }
   void visitTag(Tag* curr) { WASM_UNREACHABLE("unimp"); }
   void visitModule(Module* curr) { WASM_UNREACHABLE("unimp"); }
 
@@ -183,7 +189,9 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
 struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new AutoDrop; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<AutoDrop>();
+  }
 
   AutoDrop() { name = "autodrop"; }
 
diff --git a/src/js/binaryen.js-extern-pre.js b/src/js/binaryen.js-extern-pre.js
index 50c38c3..6c1ff4c 100644
--- a/src/js/binaryen.js-extern-pre.js
+++ b/src/js/binaryen.js-extern-pre.js
@@ -2,3 +2,10 @@
 // we are building to ES6 (where it doesn't exist) and the .wasm blob is inlined
 // see: https://github.com/emscripten-core/emscripten/issues/11792
 var __dirname = "";
+
+// FIXME: The Emscripten shell requires this function to be present, even though
+// we are building to ESM (where it doesn't exist) and the result is not used
+// see: https://github.com/emscripten-core/emscripten/pull/17851
+function require() {
+  return {};
+}
diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js
index 28f5b62..f252dae 100644
--- a/src/js/binaryen.js-post.js
+++ b/src/js/binaryen.js-post.js
@@ -9,7 +9,7 @@ function preserveStack(func) {
 }
 
 function strToStack(str) {
-  return str ? allocate(intArrayFromString(str), ALLOC_STACK) : 0;
+  return str ? allocateUTF8OnStack(str) : 0;
 }
 
 function i32sToStack(i32s) {
@@ -39,6 +39,10 @@ function initializeConstants() {
     ['eqref', 'Eqref'],
     ['i31ref', 'I31ref'],
     ['dataref', 'Dataref'],
+    ['stringref', 'Stringref'],
+    ['stringview_wtf8', 'StringviewWTF8'],
+    ['stringview_wtf16', 'StringviewWTF16'],
+    ['stringview_iter', 'StringviewIter'],
     ['unreachable', 'Unreachable'],
     ['auto', 'Auto']
   ].forEach(entry => {
@@ -107,8 +111,6 @@ function initializeConstants() {
     'RefTest',
     'RefCast',
     'BrOn',
-    'RttCanon',
-    'RttSub',
     'StructNew',
     'StructGet',
     'StructSet',
@@ -116,7 +118,22 @@ function initializeConstants() {
     'ArrayInit',
     'ArrayGet',
     'ArraySet',
-    'ArrayLen'
+    'ArrayLen',
+    'ArrayCopy',
+    'RefAs',
+    'StringNew',
+    'StringConst',
+    'StringMeasure',
+    'StringEncode',
+    'StringConcat',
+    'StringEq',
+    'StringAs',
+    'StringWTF8Advance',
+    'StringWTF16Get',
+    'StringIterNext',
+    'StringIterMove',
+    'StringSliceWTF',
+    'StringSliceIter'
   ].forEach(name => {
     Module['ExpressionIds'][name] = Module[name + 'Id'] = Module['_Binaryen' + name + 'Id']();
   });
@@ -147,9 +164,10 @@ function initializeConstants() {
     'Multivalue',
     'GC',
     'Memory64',
-    'TypedFunctionReferences',
     'RelaxedSIMD',
     'ExtendedConst',
+    'Strings',
+    'MultiMemories',
     'All'
   ].forEach(name => {
     Module['Features'][name] = Module['_BinaryenFeature' + name]();
@@ -539,6 +557,44 @@ function initializeConstants() {
     'RefAsFunc',
     'RefAsData',
     'RefAsI31',
+    'RefAsExternInternalize',
+    'RefAsExternExternalize',
+    'BrOnNull',
+    'BrOnNonNull',
+    'BrOnCast',
+    'BrOnCastFail',
+    'BrOnFunc',
+    'BrOnNonFunc',
+    'BrOnData',
+    'BrOnNonData',
+    'BrOnI31',
+    'BrOnNonI31',
+    'StringNewUTF8',
+    'StringNewWTF8',
+    'StringNewReplace',
+    'StringNewWTF16',
+    'StringNewUTF8Array',
+    'StringNewWTF8Array',
+    'StringNewReplaceArray',
+    'StringNewWTF16Array',
+    'StringMeasureUTF8',
+    'StringMeasureWTF8',
+    'StringMeasureWTF16',
+    'StringMeasureIsUSV',
+    'StringMeasureWTF16View',
+    'StringEncodeUTF8',
+    'StringEncodeWTF8',
+    'StringEncodeWTF16',
+    'StringEncodeUTF8Array',
+    'StringEncodeWTF8Array',
+    'StringEncodeWTF16Array',
+    'StringAsWTF8',
+    'StringAsWTF16',
+    'StringAsIter',
+    'StringIterMoveAdvance',
+    'StringIterMoveRewind',
+    'StringSliceWTF8',
+    'StringSliceWTF16'
   ].forEach(name => {
     Module['Operations'][name] = Module[name] = Module['_Binaryen' + name]();
   });
@@ -684,30 +740,31 @@ function wrapModule(module, self = {}) {
   }
 
   self['memory'] = {
-    'size'() {
-      return Module['_BinaryenMemorySize'](module);
+    // memory64 defaults to undefined/false.
+    'size'(name, memory64) {
+      return Module['_BinaryenMemorySize'](module, strToStack(name), memory64);
     },
-    'grow'(value) {
-      return Module['_BinaryenMemoryGrow'](module, value);
+    'grow'(value, name, memory64) {
+      return Module['_BinaryenMemoryGrow'](module, value, strToStack(name), memory64);
     },
-    'init'(segment, dest, offset, size) {
-      return Module['_BinaryenMemoryInit'](module, segment, dest, offset, size);
+    'init'(segment, dest, offset, size, name) {
+      return Module['_BinaryenMemoryInit'](module, segment, dest, offset, size, strToStack(name));
     },
-    'copy'(dest, source, size) {
-      return Module['_BinaryenMemoryCopy'](module, dest, source, size);
+    'copy'(dest, source, size, destMemory, sourceMemory) {
+      return Module['_BinaryenMemoryCopy'](module, dest, source, size, strToStack(destMemory), strToStack(sourceMemory));
     },
-    'fill'(dest, value, size) {
-      return Module['_BinaryenMemoryFill'](module, dest, value, size);
+    'fill'(dest, value, size, name) {
+      return Module['_BinaryenMemoryFill'](module, dest, value, size, strToStack(name));
     },
     'atomic': {
-      'notify'(ptr, notifyCount) {
-        return Module['_BinaryenAtomicNotify'](module, ptr, notifyCount);
+      'notify'(ptr, notifyCount, name) {
+        return Module['_BinaryenAtomicNotify'](module, ptr, notifyCount, strToStack(name));
       },
-      'wait32'(ptr, expected, timeout) {
-        return Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i32']);
+      'wait32'(ptr, expected, timeout, name) {
+        return Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i32'], strToStack(name));
       },
-      'wait64'(ptr, expected, timeout) {
-        return Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i64']);
+      'wait64'(ptr, expected, timeout, name) {
+        return Module['_BinaryenAtomicWait'](module, ptr, expected, timeout, Module['i64'], strToStack(name));
       }
     }
   }
@@ -719,29 +776,29 @@ function wrapModule(module, self = {}) {
   }
 
   self['i32'] = {
-    'load'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i32'], ptr);
+    'load'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i32'], ptr, strToStack(name));
     },
-    'load8_s'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i32'], ptr);
+    'load8_s'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i32'], ptr, strToStack(name));
     },
-    'load8_u'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i32'], ptr);
+    'load8_u'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i32'], ptr, strToStack(name));
     },
-    'load16_s'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i32'], ptr);
+    'load16_s'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i32'], ptr, strToStack(name));
     },
-    'load16_u'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i32'], ptr);
+    'load16_u'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i32'], ptr, strToStack(name));
     },
-    'store'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i32']);
+    'store'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i32'], strToStack(name));
     },
-    'store8'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i32']);
+    'store8'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i32'], strToStack(name));
     },
-    'store16'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i32']);
+    'store16'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i32'], strToStack(name));
     },
     'const'(x) {
       return preserveStack(() => {
@@ -882,91 +939,91 @@ function wrapModule(module, self = {}) {
       return Module['_BinaryenBinary'](module, Module['GeUInt32'], left, right);
     },
     'atomic': {
-      'load'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i32'], ptr);
+      'load'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i32'], ptr, strToStack(name));
       },
-      'load8_u'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i32'], ptr);
+      'load8_u'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i32'], ptr, strToStack(name));
       },
-      'load16_u'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i32'], ptr);
+      'load16_u'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i32'], ptr, strToStack(name));
       },
-      'store'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i32']);
+      'store'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i32'], strToStack(name));
       },
-      'store8'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i32']);
+      'store8'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i32'], strToStack(name));
       },
-      'store16'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i32']);
+      'store16'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i32'], strToStack(name));
       },
       'rmw': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i32']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i32']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i32']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i32']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i32']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i32']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i32'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i32'], strToStack(name))
         },
       },
       'rmw8_u': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i32']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i32']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i32']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i32']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i32']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i32']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i32'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i32'], strToStack(name))
         },
       },
       'rmw16_u': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i32']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i32']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i32']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i32']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i32']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i32']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i32'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i32'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i32'], strToStack(name))
         },
       },
     },
@@ -976,38 +1033,38 @@ function wrapModule(module, self = {}) {
   };
 
   self['i64'] = {
-    'load'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['i64'], ptr);
+    'load'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'load8_s'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i64'], ptr);
+    'load8_s'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 1, true, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'load8_u'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i64'], ptr);
+    'load8_u'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 1, false, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'load16_s'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i64'], ptr);
+    'load16_s'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 2, true, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'load16_u'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i64'], ptr);
+    'load16_u'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 2, false, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'load32_s'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i64'], ptr);
+    'load32_s'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'load32_u'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 4, false, offset, align, Module['i64'], ptr);
+    'load32_u'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 4, false, offset, align, Module['i64'], ptr, strToStack(name));
     },
-    'store'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['i64']);
+    'store'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['i64'], strToStack(name));
     },
-    'store8'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i64']);
+    'store8'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 1, offset, align, ptr, value, Module['i64'], strToStack(name));
     },
-    'store16'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i64']);
+    'store16'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i64'], strToStack(name));
     },
-    'store32'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i64']);
+    'store32'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i64'], strToStack(name));
     },
     'const'(x, y) {
       return preserveStack(() => {
@@ -1154,120 +1211,120 @@ function wrapModule(module, self = {}) {
       return Module['_BinaryenBinary'](module, Module['GeUInt64'], left, right);
     },
     'atomic': {
-      'load'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 8, offset, Module['i64'], ptr);
+      'load'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 8, offset, Module['i64'], ptr, strToStack(name));
       },
-      'load8_u'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i64'], ptr);
+      'load8_u'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 1, offset, Module['i64'], ptr, strToStack(name));
       },
-      'load16_u'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i64'], ptr);
+      'load16_u'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 2, offset, Module['i64'], ptr, strToStack(name));
       },
-      'load32_u'(offset, ptr) {
-        return Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i64'], ptr);
+      'load32_u'(offset, ptr, name) {
+        return Module['_BinaryenAtomicLoad'](module, 4, offset, Module['i64'], ptr, strToStack(name));
       },
-      'store'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 8, offset, ptr, value, Module['i64']);
+      'store'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 8, offset, ptr, value, Module['i64'], strToStack(name));
       },
-      'store8'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i64']);
+      'store8'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 1, offset, ptr, value, Module['i64'], strToStack(name));
       },
-      'store16'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i64']);
+      'store16'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 2, offset, ptr, value, Module['i64'], strToStack(name));
       },
-      'store32'(offset, ptr, value) {
-        return Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i64']);
+      'store32'(offset, ptr, value, name) {
+        return Module['_BinaryenAtomicStore'](module, 4, offset, ptr, value, Module['i64'], strToStack(name));
       },
       'rmw': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 8, offset, ptr, value, Module['i64']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 8, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 8, offset, ptr, value, Module['i64']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 8, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 8, offset, ptr, value, Module['i64']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 8, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 8, offset, ptr, value, Module['i64']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 8, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 8, offset, ptr, value, Module['i64']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 8, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 8, offset, ptr, value, Module['i64']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 8, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 8, offset, ptr, expected, replacement, Module['i64'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 8, offset, ptr, expected, replacement, Module['i64'], strToStack(name))
         },
       },
       'rmw8_u': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i64']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 1, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i64']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 1, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i64']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 1, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i64']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 1, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i64']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 1, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i64']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 1, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i64'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 1, offset, ptr, expected, replacement, Module['i64'], strToStack(name))
         },
       },
       'rmw16_u': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i64']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 2, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i64']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 2, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i64']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 2, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i64']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 2, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i64']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 2, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i64']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 2, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i64'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 2, offset, ptr, expected, replacement, Module['i64'], strToStack(name))
         },
       },
       'rmw32_u': {
-        'add'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i64']);
+        'add'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAdd'], 4, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'sub'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i64']);
+        'sub'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWSub'], 4, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'and'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i64']);
+        'and'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWAnd'], 4, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'or'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i64']);
+        'or'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWOr'], 4, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xor'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i64']);
+        'xor'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXor'], 4, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'xchg'(offset, ptr, value) {
-          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i64']);
+        'xchg'(offset, ptr, value, name) {
+          return Module['_BinaryenAtomicRMW'](module, Module['AtomicRMWXchg'], 4, offset, ptr, value, Module['i64'], strToStack(name));
         },
-        'cmpxchg'(offset, ptr, expected, replacement) {
-          return Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i64'])
+        'cmpxchg'(offset, ptr, expected, replacement, name) {
+          return Module['_BinaryenAtomicCmpxchg'](module, 4, offset, ptr, expected, replacement, Module['i64'], strToStack(name))
         },
       },
     },
@@ -1277,11 +1334,11 @@ function wrapModule(module, self = {}) {
   };
 
   self['f32'] = {
-    'load'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['f32'], ptr);
+    'load'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['f32'], ptr, strToStack(name));
     },
-    'store'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['f32']);
+    'store'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['f32'], strToStack(name));
     },
     'const'(x) {
       return preserveStack(() => {
@@ -1385,11 +1442,11 @@ function wrapModule(module, self = {}) {
   };
 
   self['f64'] = {
-    'load'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['f64'], ptr);
+    'load'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['f64'], ptr, strToStack(name));
     },
-    'store'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['f64']);
+    'store'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['f64'], strToStack(name));
     },
     'const'(x) {
       return preserveStack(() => {
@@ -1493,71 +1550,71 @@ function wrapModule(module, self = {}) {
   };
 
   self['v128'] = {
-    'load'(offset, align, ptr) {
-      return Module['_BinaryenLoad'](module, 16, false, offset, align, Module['v128'], ptr);
+    'load'(offset, align, ptr, name) {
+      return Module['_BinaryenLoad'](module, 16, false, offset, align, Module['v128'], ptr, strToStack(name));
     },
-    'load8_splat'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load8SplatVec128'], offset, align, ptr);
+    'load8_splat'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load8SplatVec128'], offset, align, ptr, strToStack(name));
     },
-    'load16_splat'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load16SplatVec128'], offset, align, ptr);
+    'load16_splat'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load16SplatVec128'], offset, align, ptr, strToStack(name));
     },
-    'load32_splat'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load32SplatVec128'], offset, align, ptr);
+    'load32_splat'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load32SplatVec128'], offset, align, ptr, strToStack(name));
     },
-    'load64_splat'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load64SplatVec128'], offset, align, ptr);
+    'load64_splat'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load64SplatVec128'], offset, align, ptr, strToStack(name));
     },
-    'load8x8_s'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load8x8SVec128'], offset, align, ptr);
+    'load8x8_s'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load8x8SVec128'], offset, align, ptr, strToStack(name));
     },
-    'load8x8_u'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load8x8UVec128'], offset, align, ptr);
+    'load8x8_u'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load8x8UVec128'], offset, align, ptr, strToStack(name));
     },
-    'load16x4_s'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load16x4SVec128'], offset, align, ptr);
+    'load16x4_s'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load16x4SVec128'], offset, align, ptr, strToStack(name));
     },
-    'load16x4_u'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load16x4UVec128'], offset, align, ptr);
+    'load16x4_u'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load16x4UVec128'], offset, align, ptr, strToStack(name));
     },
-    'load32x2_s'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load32x2SVec128'], offset, align, ptr);
+    'load32x2_s'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load32x2SVec128'], offset, align, ptr, strToStack(name));
     },
-    'load32x2_u'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load32x2UVec128'], offset, align, ptr);
+    'load32x2_u'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load32x2UVec128'], offset, align, ptr, strToStack(name));
     },
-    'load32_zero'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load32ZeroVec128'], offset, align, ptr);
+    'load32_zero'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load32ZeroVec128'], offset, align, ptr, strToStack(name));
     },
-    'load64_zero'(offset, align, ptr) {
-      return Module['_BinaryenSIMDLoad'](module, Module['Load64ZeroVec128'], offset, align, ptr);
+    'load64_zero'(offset, align, ptr, name) {
+      return Module['_BinaryenSIMDLoad'](module, Module['Load64ZeroVec128'], offset, align, ptr, strToStack(name));
     },
-    'load8_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load8LaneVec128'], offset, align, index, ptr, vec);
+    'load8_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load8LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'load16_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load16LaneVec128'], offset, align, index, ptr, vec);
+    'load16_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load16LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'load32_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load32LaneVec128'], offset, align, index, ptr, vec);
+    'load32_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load32LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'load64_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load64LaneVec128'], offset, align, index, ptr, vec);
+    'load64_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Load64LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'store8_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store8LaneVec128'], offset, align, index, ptr, vec);
+    'store8_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store8LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'store16_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store16LaneVec128'], offset, align, index, ptr, vec);
+    'store16_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store16LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'store32_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store32LaneVec128'], offset, align, index, ptr, vec);
+    'store32_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store32LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'store64_lane'(offset, align, index, ptr, vec) {
-      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store64LaneVec128'], offset, align, index, ptr, vec);
+    'store64_lane'(offset, align, index, ptr, vec, name) {
+      return Module['_BinaryenSIMDLoadStoreLane'](module, Module['Store64LaneVec128'], offset, align, index, ptr, vec, strToStack(name));
     },
-    'store'(offset, align, ptr, value) {
-      return Module['_BinaryenStore'](module, 16, offset, align, ptr, value, Module['v128']);
+    'store'(offset, align, ptr, value, name) {
+      return Module['_BinaryenStore'](module, 16, offset, align, ptr, value, Module['v128'], strToStack(name));
     },
     'const'(i8s) {
       return preserveStack(() => {
@@ -2264,6 +2321,30 @@ function wrapModule(module, self = {}) {
     }
   };
 
+  self['stringref'] = {
+    'pop'() {
+      return Module['_BinaryenPop'](module, Module['stringref']);
+    }
+  };
+
+  self['stringview_wtf8'] = {
+    'pop'() {
+      return Module['_BinaryenPop'](module, Module['stringview_wtf8']);
+    }
+  };
+
+  self['stringview_wtf16'] = {
+    'pop'() {
+      return Module['_BinaryenPop'](module, Module['stringview_wtf16']);
+    }
+  };
+
+  self['stringview_iter'] = {
+    'pop'() {
+      return Module['_BinaryenPop'](module, Module['stringview_iter']);
+    }
+  };
+
   self['ref'] = {
     'null'(type) {
       return Module['_BinaryenRefNull'](module, type);
@@ -2354,6 +2435,18 @@ function wrapModule(module, self = {}) {
     }
   };
 
+  // TODO: extern.internalize
+  // TODO: extern.externalize
+  // TODO: ref.test
+  // TODO: ref.cast
+  // TODO: br_on_*
+  // TODO: struct.*
+  // TODO: array.*
+  // TODO: string.*
+  // TODO: stringview_wtf8.*
+  // TODO: stringview_wtf16.*
+  // TODO: stringview_iter.*
+
   // 'Module' operations
   self['addFunction'] = function(name, params, results, varTypes, body) {
     return preserveStack(() =>
@@ -2474,7 +2567,7 @@ function wrapModule(module, self = {}) {
   self['removeExport'] = function(externalName) {
     return preserveStack(() => Module['_BinaryenRemoveExport'](module, strToStack(externalName)));
   };
-  self['setMemory'] = function(initial, maximum, exportName, segments = [], shared = false) {
+  self['setMemory'] = function(initial, maximum, exportName, segments = [], shared = false, memory64 = false, internalName = null) {
     // segments are assumed to be { passive: bool, offset: expression ref, data: array of 8-bit data }
     return preserveStack(() => {
       const segmentsLen = segments.length;
@@ -2484,7 +2577,8 @@ function wrapModule(module, self = {}) {
       const segmentOffset = new Array(segmentsLen);
       for (let i = 0; i < segmentsLen; i++) {
         const { data, offset, passive } = segments[i];
-        segmentData[i] = allocate(data, ALLOC_STACK);
+        segmentData[i] = stackAlloc(data.length);
+        HEAP8.set(data, segmentData[i]);
         segmentDataLen[i] = data.length;
         segmentPassive[i] = passive;
         segmentOffset[i] = offset;
@@ -2496,16 +2590,39 @@ function wrapModule(module, self = {}) {
         i32sToStack(segmentOffset),
         i32sToStack(segmentDataLen),
         segmentsLen,
-        shared
+        shared,
+        memory64,
+        strToStack(internalName)
       );
     });
   };
+  self['hasMemory'] = function() {
+    return Boolean(Module['_BinaryenHasMemory'](module));
+  };
+  self['getMemoryInfo'] = function(name) {
+    var memoryInfo = {
+      'module': UTF8ToString(Module['_BinaryenMemoryImportGetModule'](module, strToStack(name))),
+      'base': UTF8ToString(Module['_BinaryenMemoryImportGetBase'](module, strToStack(name))),
+      'initial': Module['_BinaryenMemoryGetInitial'](module, strToStack(name)),
+      'shared': Boolean(Module['_BinaryenMemoryIsShared'](module, strToStack(name))),
+      'is64': Boolean(Module['_BinaryenMemoryIs64'](module, strToStack(name))),
+    };
+    if (Module['_BinaryenMemoryHasMax'](module, strToStack(name))) {
+      memoryInfo['max'] = Module['_BinaryenMemoryGetMax'](module, strToStack(name));
+    }
+    return memoryInfo;
+  };
   self['getNumMemorySegments'] = function() {
     return Module['_BinaryenGetNumMemorySegments'](module);
-  }
+  };
   self['getMemorySegmentInfoByIndex'] = function(id) {
+    const passive = Boolean(Module['_BinaryenGetMemorySegmentPassive'](module, id));
+    let offset = null;
+    if (!passive) {
+      offset = Module['_BinaryenGetMemorySegmentByteOffset'](module, id);
+    }
     return {
-      'offset': Module['_BinaryenGetMemorySegmentByteOffset'](module, id),
+      'offset': offset,
       'data': (function(){
         const size = Module['_BinaryenGetMemorySegmentByteLength'](module, id);
         const ptr = _malloc(size);
@@ -2515,9 +2632,9 @@ function wrapModule(module, self = {}) {
         _free(ptr);
         return res.buffer;
       })(),
-      'passive': Boolean(Module['_BinaryenGetMemorySegmentPassive'](module, id))
+      'passive': passive
     };
-  }
+  };
   self['setStart'] = function(start) {
     return Module['_BinaryenSetStart'](module, start);
   };
@@ -2566,22 +2683,16 @@ function wrapModule(module, self = {}) {
     return Module['_BinaryenGetElementSegmentByIndex'](module, index);
   };
   self['emitText'] = function() {
-    const old = out;
-    let ret = '';
-    out = x => { ret += x + '\n' };
-    Module['_BinaryenModulePrint'](module);
-    out = old;
-    return ret;
+    let textPtr = Module['_BinaryenModuleAllocateAndWriteText'](module);
+    let text = UTF8ToString(textPtr);
+    if (textPtr) _free(textPtr);
+    return text;
   };
   self['emitStackIR'] = function(optimize) {
-    self['runPasses'](['generate-stack-ir']);
-    if (optimize) self['runPasses'](['optimize-stack-ir']);
-    const old = out;
-    let ret = '';
-    out = x => { ret += x + '\n' };
-    self['runPasses'](['print-stack-ir']);
-    out = old;
-    return ret;
+    let textPtr = Module['_BinaryenModuleAllocateAndWriteStackIR'](module, optimize);
+    let text = UTF8ToString(textPtr);
+    if (textPtr) _free(textPtr);
+    return text;
   };
   self['emitAsmjs'] = function() {
     const old = out;
@@ -2658,6 +2769,7 @@ function wrapModule(module, self = {}) {
 Module['wrapModule'] = wrapModule;
 
 // 'Relooper' interface
+/** @constructor */
 Module['Relooper'] = function(module) {
   assert(module && typeof module === 'object' && module['ptr'] && module['block'] && module['if']); // guard against incorrect old API usage
   const relooper = Module['_RelooperCreate'](module['ptr']);
@@ -2681,6 +2793,7 @@ Module['Relooper'] = function(module) {
 };
 
 // 'ExpressionRunner' interface
+/** @constructor */
 Module['ExpressionRunner'] = function(module, flags, maxDepth, maxLoopIterations) {
   const runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth, maxLoopIterations);
   this['ptr'] = runner;
@@ -3303,7 +3416,8 @@ Module['emitText'] = function(expr) {
 Object.defineProperty(Module, 'readBinary', { writable: true });
 
 Module['readBinary'] = function(data) {
-  const buffer = allocate(data, ALLOC_NORMAL);
+  const buffer = _malloc(data.length);
+  HEAP8.set(data, buffer);
   const ptr = Module['_BinaryenModuleRead'](buffer, data.length);
   _free(buffer);
   return wrapModule(ptr);
@@ -3448,6 +3562,10 @@ const thisPtr = Symbol();
 // Makes a specific expression wrapper class with the specified static members
 // while automatically deriving instance methods and accessors.
 function makeExpressionWrapper(ownStaticMembers) {
+  /**
+   * @constructor
+   * @extends Expression
+   */
   function SpecificExpression(expr) {
     // can call the constructor without `new`
     if (!(this instanceof SpecificExpression)) {
@@ -3479,6 +3597,7 @@ function deriveWrapperInstanceMembers(prototype, staticMembers) {
     const member = staticMembers[memberName];
     if (typeof member === "function") {
       // Instance method calls the respective static method with `this` bound.
+      /** @this {Expression} */
       prototype[memberName] = function(...args) {
         return this.constructor[memberName](this[thisPtr], ...args);
       };
@@ -3492,9 +3611,11 @@ function deriveWrapperInstanceMembers(prototype, staticMembers) {
         const propertyName = memberName.charAt(index).toLowerCase() + memberName.substring(index + 1);
         const setterIfAny = staticMembers["set" + memberName.substring(index)];
         Object.defineProperty(prototype, propertyName, {
+          /** @this {Expression} */
           get() {
             return member(this[thisPtr]);
           },
+          /** @this {Expression} */
           set(value) {
             if (setterIfAny) setterIfAny(this[thisPtr], value);
             else throw Error("property '" + propertyName + "' has no setter");
@@ -3506,6 +3627,7 @@ function deriveWrapperInstanceMembers(prototype, staticMembers) {
 }
 
 // Base class of all expression wrappers
+/** @constructor */
 function Expression(expr) {
   if (!expr) throw Error("expression reference must not be null");
   this[thisPtr] = expr;
@@ -4752,6 +4874,7 @@ Module['I31Get'] = makeExpressionWrapper({
 
 Module['Function'] = (() => {
   // Closure compiler doesn't allow multiple `Function`s at top-level, so:
+  /** @constructor */
   function Function(func) {
     if (!(this instanceof Function)) {
       if (!func) return null;
diff --git a/src/literal.h b/src/literal.h
index 555f89e..213713a 100644
--- a/src/literal.h
+++ b/src/literal.h
@@ -32,12 +32,14 @@ namespace wasm {
 
 class Literals;
 struct GCData;
-struct RttSupers;
 
 class Literal {
   // store only integers, whose bits are deterministic. floats
   // can have their signalling bit set, for example.
   union {
+    // Note: i31 is stored in the |i32| field, with the lower 31 bits containing
+    // the value if there is one, and the highest bit containing whether there
+    // is a value. Thus, a null is |i32 === 0|.
     int32_t i32;
     int64_t i64;
     uint8_t v128[16];
@@ -48,21 +50,6 @@ class Literal {
     // Array, and for a Struct, is just the fields in order). The type is used
     // to indicate whether this is a Struct or an Array, and of what type.
     std::shared_ptr<GCData> gcData;
-    // RTT values are "structural" in that the MVP doc says that multiple
-    // invocations of ref.canon return things that are observably identical, and
-    // the same is true for ref.sub. That is, what matters is the types; there
-    // is no unique identifier created in each ref.canon/sub. To track the
-    // types, we maintain a simple vector of the supertypes. Thus, an rtt.canon
-    // of type A will have an empty vector; an rtt.sub of type B of that initial
-    // canon would have a vector of size 1 containing A; a subsequent rtt.sub
-    // would have A, B, and so forth.
-    // (This encoding is very inefficient and not at all what a production VM
-    // would do, but it is simple.)
-    // The unique_ptr here is to avoid increasing the size of the union as well
-    // as the Literal class itself.
-    // To support the experimental RttFreshSub instruction, we not only store
-    // the type, but also a reference to an allocation.
-    std::unique_ptr<RttSupers> rttSupers;
     // TODO: Literals of type `anyref` can only be `null` currently but we
     // will need to represent external values eventually, to
     // 1) run the spec tests and fuzzer with reference types enabled and
@@ -91,9 +78,11 @@ public:
   explicit Literal(const std::array<Literal, 8>&);
   explicit Literal(const std::array<Literal, 4>&);
   explicit Literal(const std::array<Literal, 2>&);
-  explicit Literal(Name func, Type type) : func(func), type(type) {}
-  explicit Literal(std::shared_ptr<GCData> gcData, Type type);
-  explicit Literal(std::unique_ptr<RttSupers>&& rttSupers, Type type);
+  explicit Literal(Name func, HeapType type)
+    : func(func), type(type, NonNullable) {
+    assert(type.isSignature());
+  }
+  explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);
   Literal(const Literal& other);
   Literal& operator=(const Literal& other);
   ~Literal();
@@ -103,18 +92,8 @@ public:
   bool isFunction() const { return type.isFunction(); }
   bool isData() const { return type.isData(); }
 
-  bool isNull() const {
-    if (type.isNullable()) {
-      if (type.isFunction()) {
-        return func.isNull();
-      }
-      if (isData()) {
-        return !gcData;
-      }
-      return true;
-    }
-    return false;
-  }
+  bool isNull() const { return type.isNull(); }
+
   bool isZero() const {
     switch (type.getBasic()) {
       case Type::i32:
@@ -218,6 +197,10 @@ public:
         WASM_UNREACHABLE("unexpected type");
     }
   }
+
+  static Literal makeFromMemory(void* p, Type type);
+  static Literal makeFromMemory(void* p, const Field& field);
+
   static Literal makeSignedMin(Type type) {
     switch (type.getBasic()) {
       case Type::i32:
@@ -248,22 +231,39 @@ public:
         WASM_UNREACHABLE("unexpected type");
     }
   }
-  static Literal makeNull(Type type) {
-    assert(type.isNullable());
-    return Literal(type);
+  static Literal makeNull(HeapType type) {
+    return Literal(Type(type.getBottom(), Nullable));
   }
-  static Literal makeFunc(Name func, Type type = Type::funcref) {
+  static Literal makeFunc(Name func, HeapType type) {
     return Literal(func, type);
   }
   static Literal makeI31(int32_t value) {
-    auto lit = Literal(Type::i31ref);
-    lit.i32 = value & 0x7fffffff;
+    auto lit = Literal(Type(HeapType::i31, NonNullable));
+    lit.i32 = value | 0x80000000;
     return lit;
   }
-
-  // Get the canonical RTT value for a given HeapType. For nominal types, the
-  // canonical RTT reflects the static supertype chain.
-  static Literal makeCanonicalRtt(HeapType type);
+  // Wasm has nondeterministic rules for NaN propagation in some operations. For
+  // example. f32.neg is deterministic and just flips the sign, even of a NaN,
+  // but f32.add is nondeterministic, and if one or more of the inputs is a NaN,
+  // then
+  //
+  //  * if all NaNs are canonical, the output is some arbitrary canonical NaN
+  //  * otherwise the output is some arbitrary arithmetic NaN
+  //
+  // (canonical = NaN payload is 1000..000; arithmetic: 1???..???, that is, the
+  // high bit is 1 and all others can be 0 or 1)
+  //
+  // For many things we don't need to care, and can just do a normal C++ add for
+  // an f32.add, for example - the wasm rules are specified so that things like
+  // that just work (in order for such math to be fast). However, for our
+  // optimizer, it is useful to "standardize" NaNs when there is nondeterminism.
+  // That is, when there are multiple valid outputs, it's nice to emit the same
+  // one consistently, so that it doesn't look like the optimization changed
+  // something. In other words, if the valid output of an expression is a set of
+  // valid NaNs, and after optimization the output is still that same set, then
+  // the optimization is valid. And if the interpreter picks the same NaN in
+  // both cases from that identical set then nothing looks wrong to the fuzzer.
+  static Literal standardizeNaN(const Literal& input);
 
   Literal castToF32();
   Literal castToF64();
@@ -276,7 +276,8 @@ public:
   }
   int32_t geti31(bool signed_ = true) const {
     assert(type.getHeapType() == HeapType::i31);
-    return signed_ ? (i32 << 1) >> 1 : i32;
+    // Cast to unsigned for the left shift to avoid undefined behavior.
+    return signed_ ? int32_t((uint32_t(i32) << 1)) >> 1 : (i32 & 0x7fffffff);
   }
   int64_t geti64() const {
     assert(type == Type::i64);
@@ -296,7 +297,6 @@ public:
     return func;
   }
   std::shared_ptr<GCData> getGCData() const;
-  const RttSupers& getRttSupers() const;
 
   // careful!
   int32_t* geti32Ptr() {
@@ -659,11 +659,6 @@ public:
   Literal relaxedFmaF64x2(const Literal& left, const Literal& right) const;
   Literal relaxedFmsF64x2(const Literal& left, const Literal& right) const;
 
-  // Checks if an RTT value is a sub-rtt of another, that is, whether GC data
-  // with this object's RTT can be successfuly cast using the other RTT
-  // according to the wasm rules for that.
-  bool isSubRtt(const Literal& other) const;
-
 private:
   Literal addSatSI8(const Literal& other) const;
   Literal addSatUI8(const Literal& other) const;
@@ -714,60 +709,24 @@ public:
 std::ostream& operator<<(std::ostream& o, wasm::Literal literal);
 std::ostream& operator<<(std::ostream& o, wasm::Literals literals);
 
-// A GC Struct or Array is a set of values with a run-time type saying what it
-// is. In the case of static (rtt-free) typing, the rtt is not present and
-// instead we have a static type.
+// A GC Struct or Array is a set of values with a type saying how it should be
+// interpreted.
 struct GCData {
-  // The runtime type info for this struct or array.
-  Literal rtt;
+  // The type of this struct or array.
+  HeapType type;
 
   // The element or field values.
   Literals values;
 
-  GCData(Literal rtt, Literals values) : rtt(rtt), values(values) {}
+  GCData(HeapType type, Literals values) : type(type), values(values) {}
 };
 
-struct RttSuper {
-  // The type of the super.
-  HeapType type;
-  // A shared allocation, used to implement rtt.fresh_sub. This is null for a
-  // normal sub, and for a fresh one we allocate a value here, which can then be
-  // used to differentiate rtts. (The allocation is shared so that when copying
-  // an rtt we remain equal.)
-  // TODO: Remove or optimize this when the spec stabilizes.
-  std::shared_ptr<size_t> freshPtr;
-
-  RttSuper(HeapType type) : type(type) {}
-
-  void makeFresh() { freshPtr = std::make_shared<size_t>(); }
-
-  bool operator==(const RttSuper& other) const {
-    return type == other.type && freshPtr == other.freshPtr;
-  }
-  bool operator!=(const RttSuper& other) const { return !(*this == other); }
-};
-
-struct RttSupers : std::vector<RttSuper> {};
-
 } // namespace wasm
 
 namespace std {
 template<> struct hash<wasm::Literal> {
   size_t operator()(const wasm::Literal& a) const {
-    auto digest = wasm::hash(a.type.getID());
-    auto hashRef = [&]() {
-      assert(a.type.isRef());
-      if (a.isNull()) {
-        return digest;
-      }
-      if (a.type.isFunction()) {
-        wasm::rehash(digest, a.getFunc());
-        return digest;
-      }
-      // other non-null reference type literals cannot represent concrete
-      // values, i.e. there is no concrete anyref or eqref other than null.
-      WASM_UNREACHABLE("unexpected type");
-    };
+    auto digest = wasm::hash(a.type);
     if (a.type.isBasic()) {
       switch (a.type.getBasic()) {
         case wasm::Type::i32:
@@ -788,28 +747,25 @@ template<> struct hash<wasm::Literal> {
           wasm::rehash(digest, chunks[0]);
           wasm::rehash(digest, chunks[1]);
           return digest;
-        case wasm::Type::funcref:
-        case wasm::Type::anyref:
-        case wasm::Type::eqref:
-        case wasm::Type::dataref:
-          return hashRef();
-        case wasm::Type::i31ref:
-          wasm::rehash(digest, a.geti31(true));
-          return digest;
         case wasm::Type::none:
         case wasm::Type::unreachable:
           break;
       }
     } else if (a.type.isRef()) {
-      return hashRef();
-    } else if (a.type.isRtt()) {
-      const auto& supers = a.getRttSupers();
-      wasm::rehash(digest, supers.size());
-      for (auto super : supers) {
-        wasm::rehash(digest, super.type.getID());
-        wasm::rehash(digest, uintptr_t(super.freshPtr.get()));
+      if (a.isNull()) {
+        return digest;
+      }
+      if (a.type.isFunction()) {
+        wasm::rehash(digest, a.getFunc());
+        return digest;
       }
-      return digest;
+      if (a.type.getHeapType() == wasm::HeapType::i31) {
+        wasm::rehash(digest, a.geti31(true));
+        return digest;
+      }
+      // other non-null reference type literals cannot represent concrete
+      // values, i.e. there is no concrete anyref or eqref other than null.
+      WASM_UNREACHABLE("unexpected type");
     }
     WASM_UNREACHABLE("unexpected type");
   }
diff --git a/src/pass.h b/src/pass.h
index 20e0993..120ed26 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -36,7 +36,7 @@ struct PassRegistry {
 
   static PassRegistry* get();
 
-  typedef std::function<Pass*()> Creator;
+  using Creator = std::function<Pass*()>;
 
   void registerPass(const char* name, const char* description, Creator create);
   // Register a pass that's used for internal testing. These passes do not show
@@ -95,13 +95,18 @@ struct InliningOptions {
   Index partialInliningIfs = 0;
 };
 
+// Forward declaration for FuncEffectsMap.
+class EffectAnalyzer;
+
+using FuncEffectsMap = std::unordered_map<Name, EffectAnalyzer>;
+
 struct PassOptions {
   // Run passes in debug mode, doing extra validation and timing checks.
   bool debug = false;
   // Whether to run the validator to check for errors.
   bool validate = true;
   // When validating validate globally and not just locally
-  bool validateGlobally = false;
+  bool validateGlobally = true;
   // 0, 1, 2 correspond to -O0, -O1, -O2, etc.
   int optimizeLevel = 0;
   // 0, 1, 2 correspond to -O0, -Os, -Oz
@@ -140,6 +145,25 @@ struct PassOptions {
   // neither of those can happen (and it is undefined behavior if they do).
   //
   // TODO: deprecate and remove ignoreImplicitTraps.
+  //
+  // Since trapsNeverHappen assumes a trap is never reached, it can in principle
+  // remove code like this:
+  //
+  //   (i32.store ..)
+  //   (unreachable)
+  //
+  // The trap isn't reached, by assumption, and if we reach the store then we'd
+  // reach the trap, so we can assume that isn't reached either, and TNH can
+  // remove both. We do have a specific limitation here, however, which is that
+  // trapsNeverHappen cannot remove calls to *imports*. We assume that an import
+  // might do things we cannot understand, so we never eliminate it. For
+  // example, in LLVM output we might see this:
+  //
+  //   (call $abort) ;; a noreturn import - the process is halted with an error
+  //   (unreachable)
+  //
+  // That trap is never actually reached since the abort halts execution. In TNH
+  // we can remove the trap but not the call right before it.
   bool trapsNeverHappen = false;
   // Optimize assuming that the low 1K of memory is not valid memory for the
   // application to use. In that case, we can optimize load/store offsets in
@@ -164,7 +188,15 @@ struct PassOptions {
   bool debugInfo = false;
   // Arbitrary string arguments from the commandline, which we forward to
   // passes.
-  std::map<std::string, std::string> arguments;
+  std::unordered_map<std::string, std::string> arguments;
+
+  // Effect info computed for functions. One pass can generate this and then
+  // other passes later can benefit from it. It is up to the sequence of passes
+  // to update or discard this when necessary - in particular, when new effects
+  // are added to a function this must be changed or we may optimize
+  // incorrectly (however, it is extremely rare for a pass to *add* effects;
+  // passes normally only remove effects).
+  std::shared_ptr<FuncEffectsMap> funcEffectsMap;
 
   // -Os is our default
   static constexpr const int DEFAULT_OPTIMIZE_LEVEL = 2;
@@ -185,15 +217,17 @@ struct PassOptions {
     return PassOptions(); // defaults are to not optimize
   }
 
+  bool hasArgument(std::string key) { return arguments.count(key) > 0; }
+
   std::string getArgument(std::string key, std::string errorTextIfMissing) {
-    if (arguments.count(key) == 0) {
+    if (!hasArgument(key)) {
       Fatal() << errorTextIfMissing;
     }
     return arguments[key];
   }
 
   std::string getArgumentOrDefault(std::string key, std::string default_) {
-    if (arguments.count(key) == 0) {
+    if (!hasArgument(key)) {
       return default_;
     }
     return arguments[key];
@@ -239,9 +273,7 @@ struct PassRunner {
   }
 
   // Add a pass given an instance.
-  template<class P> void add(std::unique_ptr<P> pass) {
-    doAdd(std::move(pass));
-  }
+  void add(std::unique_ptr<Pass> pass) { doAdd(std::move(pass)); }
 
   // Adds the pass if there are no DWARF-related issues. There is an issue if
   // there is DWARF and if the pass does not support DWARF (as defined by the
@@ -280,9 +312,6 @@ struct PassRunner {
   // Run the passes on a specific function
   void runOnFunction(Function* func);
 
-  // Get the last pass that was already executed of a certain type.
-  template<class P> P* getLast();
-
   // When running a pass runner within another pass runner, this
   // flag should be set. This influences how pass debugging works,
   // and may influence other things in the future too.
@@ -335,18 +364,18 @@ private:
 // Core pass class
 //
 class Pass {
+  PassRunner* runner = nullptr;
+  friend PassRunner;
+
 public:
   virtual ~Pass() = default;
 
   // Implement this with code to run the pass on the whole module
-  virtual void run(PassRunner* runner, Module* module) {
-    WASM_UNREACHABLE("unimplemented");
-  }
+  virtual void run(Module* module) { WASM_UNREACHABLE("unimplemented"); }
 
   // Implement this with code to run the pass on a single function, for
   // a function-parallel pass
-  virtual void
-  runOnFunction(PassRunner* runner, Module* module, Function* function) {
+  virtual void runOnFunction(Module* module, Function* function) {
     WASM_UNREACHABLE("unimplemented");
   }
 
@@ -372,7 +401,7 @@ public:
   // This method is used to create instances per function for a
   // function-parallel pass. You may need to override this if you subclass a
   // Walker, as otherwise this will create the parent class.
-  virtual Pass* create() { WASM_UNREACHABLE("unimplenented"); }
+  virtual std::unique_ptr<Pass> create() { WASM_UNREACHABLE("unimplenented"); }
 
   // Whether this pass modifies the Binaryen IR in the module. This is true for
   // most passes, except for passes that have no side effects, or passes that
@@ -386,8 +415,25 @@ public:
   // for. This is used to issue a proper warning about that.
   virtual bool invalidatesDWARF() { return false; }
 
+  // Whether this pass modifies Binaryen IR in ways that may require fixups for
+  // non-nullable locals to validate according to the wasm spec. If the pass
+  // adds locals not in that form, or moves code around in ways that might break
+  // that validation, this must return true. In that case the pass runner will
+  // automatically run the necessary fixups afterwards.
+  //
+  // For more details see the LocalStructuralDominance class.
+  virtual bool requiresNonNullableLocalFixups() { return true; }
+
   std::string name;
 
+  PassRunner* getPassRunner() { return runner; }
+  void setPassRunner(PassRunner* runner_) {
+    assert((!runner || runner == runner_) && "Pass already had a runner");
+    runner = runner_;
+  }
+
+  PassOptions& getPassOptions() { return runner->options; }
+
 protected:
   Pass() = default;
   Pass(Pass&) = default;
@@ -400,47 +446,49 @@ protected:
 //
 template<typename WalkerType>
 class WalkerPass : public Pass, public WalkerType {
-  PassRunner* runner = nullptr;
 
 protected:
-  typedef WalkerPass<WalkerType> super;
+  using super = WalkerPass<WalkerType>;
 
 public:
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
+    assert(getPassRunner());
     // Parallel pass running is implemented in the PassRunner.
     if (isFunctionParallel()) {
-      PassRunner runner(module);
+      // TODO: We should almost certainly be propagating pass options here, but
+      // that is a widespread change, so make sure it doesn't unacceptably
+      // regress compile times.
+      PassRunner runner(module /*, getPassOptions()*/);
       runner.setIsNested(true);
-      std::unique_ptr<Pass> copy;
-      copy.reset(create());
-      runner.add(std::move(copy));
+      runner.add(create());
       runner.run();
       return;
     }
     // Single-thread running just calls the walkModule traversal.
-    setPassRunner(runner);
-    WalkerType::setModule(module);
     WalkerType::walkModule(module);
   }
 
-  void
-  runOnFunction(PassRunner* runner, Module* module, Function* func) override {
+  // Utility for ad-hoc running.
+  void run(PassRunner* runner, Module* module) {
     setPassRunner(runner);
-    WalkerType::setModule(module);
-    WalkerType::walkFunction(func);
+    run(module);
+  }
+
+  void runOnFunction(Module* module, Function* func) override {
+    assert(getPassRunner());
+    WalkerType::walkFunctionInModule(func, module);
+  }
+
+  // Utility for ad-hoc running.
+  void runOnFunction(PassRunner* runner, Module* module, Function* func) {
+    setPassRunner(runner);
+    runOnFunction(module, func);
   }
 
   void runOnModuleCode(PassRunner* runner, Module* module) {
     setPassRunner(runner);
-    WalkerType::setModule(module);
     WalkerType::walkModuleCode(module);
   }
-
-  PassRunner* getPassRunner() { return runner; }
-
-  PassOptions& getPassOptions() { return runner->options; }
-
-  void setPassRunner(PassRunner* runner_) { runner = runner_; }
 };
 
 } // namespace wasm
diff --git a/src/passes/AlignmentLowering.cpp b/src/passes/AlignmentLowering.cpp
index 8cab580..d0ceeb6 100644
--- a/src/passes/AlignmentLowering.cpp
+++ b/src/passes/AlignmentLowering.cpp
@@ -34,7 +34,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
     if (curr->align == 0 || curr->align == curr->bytes) {
       return curr;
     }
-    auto indexType = getModule()->memory.indexType;
+    auto mem = getModule()->getMemory(curr->memory);
+    auto indexType = mem->indexType;
     Builder builder(*getModule());
     assert(curr->type == Type::i32);
     auto temp = builder.addVar(getFunction(), indexType);
@@ -47,7 +48,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                          curr->offset,
                          1,
                          builder.makeLocalGet(temp, indexType),
-                         Type::i32),
+                         Type::i32,
+                         curr->memory),
         builder.makeBinary(
           ShlInt32,
           builder.makeLoad(1,
@@ -55,7 +57,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                            curr->offset + 1,
                            1,
                            builder.makeLocalGet(temp, indexType),
-                           Type::i32),
+                           Type::i32,
+                           curr->memory),
           builder.makeConst(int32_t(8))));
       if (curr->signed_) {
         ret = Bits::makeSignExt(ret, 2, *getModule());
@@ -71,7 +74,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                              curr->offset,
                              1,
                              builder.makeLocalGet(temp, indexType),
-                             Type::i32),
+                             Type::i32,
+                             curr->memory),
             builder.makeBinary(
               ShlInt32,
               builder.makeLoad(1,
@@ -79,7 +83,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                                curr->offset + 1,
                                1,
                                builder.makeLocalGet(temp, indexType),
-                               Type::i32),
+                               Type::i32,
+                               curr->memory),
               builder.makeConst(int32_t(8)))),
           builder.makeBinary(
             OrInt32,
@@ -90,7 +95,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                                curr->offset + 2,
                                1,
                                builder.makeLocalGet(temp, indexType),
-                               Type::i32),
+                               Type::i32,
+                               curr->memory),
               builder.makeConst(int32_t(16))),
             builder.makeBinary(
               ShlInt32,
@@ -99,7 +105,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                                curr->offset + 3,
                                1,
                                builder.makeLocalGet(temp, indexType),
-                               Type::i32),
+                               Type::i32,
+                               curr->memory),
               builder.makeConst(int32_t(24)))));
       } else if (curr->align == 2) {
         ret = builder.makeBinary(
@@ -109,7 +116,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                            curr->offset,
                            2,
                            builder.makeLocalGet(temp, indexType),
-                           Type::i32),
+                           Type::i32,
+                           curr->memory),
           builder.makeBinary(
             ShlInt32,
             builder.makeLoad(2,
@@ -117,7 +125,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                              curr->offset + 2,
                              2,
                              builder.makeLocalGet(temp, indexType),
-                             Type::i32),
+                             Type::i32,
+                             curr->memory),
             builder.makeConst(int32_t(16))));
       } else {
         WASM_UNREACHABLE("invalid alignment");
@@ -135,7 +144,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
     }
     Builder builder(*getModule());
     assert(curr->value->type == Type::i32);
-    auto indexType = getModule()->memory.indexType;
+    auto mem = getModule()->getMemory(curr->memory);
+    auto indexType = mem->indexType;
     auto tempPtr = builder.addVar(getFunction(), indexType);
     auto tempValue = builder.addVar(getFunction(), Type::i32);
     auto* block =
@@ -148,7 +158,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                           1,
                           builder.makeLocalGet(tempPtr, indexType),
                           builder.makeLocalGet(tempValue, Type::i32),
-                          Type::i32));
+                          Type::i32,
+                          curr->memory));
       block->list.push_back(builder.makeStore(
         1,
         curr->offset + 1,
@@ -157,7 +168,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
         builder.makeBinary(ShrUInt32,
                            builder.makeLocalGet(tempValue, Type::i32),
                            builder.makeConst(int32_t(8))),
-        Type::i32));
+        Type::i32,
+        curr->memory));
     } else if (curr->bytes == 4) {
       if (curr->align == 1) {
         block->list.push_back(
@@ -166,7 +178,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                             1,
                             builder.makeLocalGet(tempPtr, indexType),
                             builder.makeLocalGet(tempValue, Type::i32),
-                            Type::i32));
+                            Type::i32,
+                            curr->memory));
         block->list.push_back(builder.makeStore(
           1,
           curr->offset + 1,
@@ -175,7 +188,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
           builder.makeBinary(ShrUInt32,
                              builder.makeLocalGet(tempValue, Type::i32),
                              builder.makeConst(int32_t(8))),
-          Type::i32));
+          Type::i32,
+          curr->memory));
         block->list.push_back(builder.makeStore(
           1,
           curr->offset + 2,
@@ -184,7 +198,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
           builder.makeBinary(ShrUInt32,
                              builder.makeLocalGet(tempValue, Type::i32),
                              builder.makeConst(int32_t(16))),
-          Type::i32));
+          Type::i32,
+          curr->memory));
         block->list.push_back(builder.makeStore(
           1,
           curr->offset + 3,
@@ -193,7 +208,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
           builder.makeBinary(ShrUInt32,
                              builder.makeLocalGet(tempValue, Type::i32),
                              builder.makeConst(int32_t(24))),
-          Type::i32));
+          Type::i32,
+          curr->memory));
       } else if (curr->align == 2) {
         block->list.push_back(
           builder.makeStore(2,
@@ -201,7 +217,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                             2,
                             builder.makeLocalGet(tempPtr, indexType),
                             builder.makeLocalGet(tempValue, Type::i32),
-                            Type::i32));
+                            Type::i32,
+                            curr->memory));
         block->list.push_back(builder.makeStore(
           2,
           curr->offset + 2,
@@ -210,7 +227,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
           builder.makeBinary(ShrUInt32,
                              builder.makeLocalGet(tempValue, Type::i32),
                              builder.makeConst(int32_t(16))),
-          Type::i32));
+          Type::i32,
+          curr->memory));
       } else {
         WASM_UNREACHABLE("invalid alignment");
       }
@@ -256,7 +274,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
           break;
         }
         // Load two 32-bit pieces, and combine them.
-        auto indexType = getModule()->memory.indexType;
+        auto mem = getModule()->getMemory(curr->memory);
+        auto indexType = mem->indexType;
         auto temp = builder.addVar(getFunction(), indexType);
         auto* set = builder.makeLocalSet(temp, curr->ptr);
         Expression* low =
@@ -265,7 +284,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                                         curr->offset,
                                         curr->align,
                                         builder.makeLocalGet(temp, indexType),
-                                        Type::i32));
+                                        Type::i32,
+                                        curr->memory));
         low = builder.makeUnary(ExtendUInt32, low);
         // Note that the alignment is assumed to be the same here, even though
         // we add an offset of 4. That is because this is an unaligned load, so
@@ -277,7 +297,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                                         curr->offset + 4,
                                         curr->align,
                                         builder.makeLocalGet(temp, indexType),
-                                        Type::i32));
+                                        Type::i32,
+                                        curr->memory));
         high = builder.makeUnary(ExtendUInt32, high);
         high =
           builder.makeBinary(ShlInt64, high, builder.makeConst(int64_t(32)));
@@ -335,7 +356,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
           value = builder.makeUnary(ReinterpretFloat64, value);
         }
         // Store as two 32-bit pieces.
-        auto indexType = getModule()->memory.indexType;
+        auto mem = getModule()->getMemory(curr->memory);
+        auto indexType = mem->indexType;
         auto tempPtr = builder.addVar(getFunction(), indexType);
         auto* setPtr = builder.makeLocalSet(tempPtr, curr->ptr);
         auto tempValue = builder.addVar(getFunction(), Type::i64);
@@ -348,7 +370,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                             curr->align,
                             builder.makeLocalGet(tempPtr, indexType),
                             low,
-                            Type::i32));
+                            Type::i32,
+                            curr->memory));
         Expression* high =
           builder.makeBinary(ShrUInt64,
                              builder.makeLocalGet(tempValue, Type::i64),
@@ -364,7 +387,8 @@ struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
                             curr->align,
                             builder.makeLocalGet(tempPtr, indexType),
                             high,
-                            Type::i32));
+                            Type::i32,
+                            curr->memory));
         replacement = builder.makeBlock({setPtr, setValue, low, high});
         break;
     }
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp
index 8a50d49..342cd01 100644
--- a/src/passes/Asyncify.cpp
+++ b/src/passes/Asyncify.cpp
@@ -20,7 +20,7 @@
 // an async manner, for example, you can do a blocking wait, and that will
 // be turned into code that unwinds the stack at the "blocking" operation,
 // then is able to rewind it back up when the actual async operation
-// comples, the so the code appears to have been running synchronously
+// completes, so the code appears to have been running synchronously
 // all the while. Use cases for this include coroutines, python generators,
 // etc.
 //
@@ -107,11 +107,18 @@
 // contains a pointer to a data structure with the info needed to rewind
 // and unwind:
 //
-//   {                                            // offsets
+//   {                                           // offsets
 //     i32  - current asyncify stack location    //  0
 //     i32  - asyncify stack end                 //  4
 //   }
 //
+// Or for wasm64:
+//
+//   {                                           // offsets
+//     i64  - current asyncify stack location    //  0
+//     i64  - asyncify stack end                 //  8
+//   }
+//
 // The asyncify stack is a representation of the call frame, as a list of
 // indexes of calls. In the example above, we saw index "0" for calling "bar"
 // from "foo". When unwinding, the indexes are added to the stack; when
@@ -130,7 +137,7 @@
 // The pass will also create five functions that let you control unwinding
 // and rewinding:
 //
-//  * asyncify_start_unwind(data : i32): call this to start unwinding the
+//  * asyncify_start_unwind(data : iPTR): call this to start unwinding the
 //    stack from the current location. "data" must point to a data
 //    structure as described above (with fields containing valid data).
 //
@@ -142,8 +149,8 @@
 //    the code will think it is still unwinding when it should not be,
 //    which means it will keep unwinding in a meaningless way.
 //
-//  * asyncify_start_rewind(data : i32): call this to start rewinding the
-//    stack vack up to the location stored in the provided data. This prepares
+//  * asyncify_start_rewind(data : iPTR): call this to start rewinding the
+//    stack back up to the location stored in the provided data. This prepares
 //    for the rewind; to start it, you must call the first function in the
 //    call stack to be unwound.
 //
@@ -227,7 +234,7 @@
 //
 //   --pass-arg=asyncify-asserts
 //
-//      This enables extra asserts in the output, like checking if we in
+//      This enables extra asserts in the output, like checking if we put in
 //      an unwind/rewind in an invalid place (this can be helpful for manual
 //      tweaking of the only-list / remove-list, see later).
 //
@@ -304,6 +311,7 @@
 #include "ir/literal-utils.h"
 #include "ir/memory-utils.h"
 #include "ir/module-utils.h"
+#include "ir/names.h"
 #include "ir/utils.h"
 #include "pass.h"
 #include "support/file.h"
@@ -335,7 +343,7 @@ static const Name ASYNCIFY_CHECK_CALL_INDEX = "__asyncify_check_call_index";
 //       size, but make debugging harder
 enum class State { Normal = 0, Unwinding = 1, Rewinding = 2 };
 
-enum class DataOffset { BStackPos = 0, BStackEnd = 4 };
+enum class DataOffset { BStackPos = 0, BStackEnd = 4, BStackEnd64 = 8 };
 
 const auto STACK_ALIGN = 4;
 
@@ -437,9 +445,9 @@ public:
     // internal escaped names for later comparisons
     for (auto& name : list) {
       auto escaped = WasmBinaryBuilder::escape(name);
-      unescaped[escaped.str] = name;
+      unescaped[escaped.toString()] = name;
       if (name.find('*') != std::string::npos) {
-        patterns.insert(escaped.str);
+        patterns.insert(escaped.toString());
       } else {
         auto* func = module.getFunctionOrNull(escaped);
         if (!func) {
@@ -462,7 +470,7 @@ public:
       return true;
     } else {
       for (auto& pattern : patterns) {
-        if (String::wildcardMatch(pattern, funcName.str)) {
+        if (String::wildcardMatch(pattern, funcName.toString())) {
           patternsMatched.insert(pattern);
           return true;
         }
@@ -512,7 +520,7 @@ class ModuleAnalyzer {
     bool addedFromList = false;
   };
 
-  typedef std::map<Function*, Info> Map;
+  using Map = std::map<Function*, Info>;
   Map map;
 
 public:
@@ -794,28 +802,38 @@ static bool doesCall(Expression* curr) {
 
 class AsyncifyBuilder : public Builder {
 public:
-  AsyncifyBuilder(Module& wasm) : Builder(wasm) {}
+  Module& wasm;
+  Type pointerType;
+  Name asyncifyMemory;
+
+  AsyncifyBuilder(Module& wasm, Type pointerType, Name asyncifyMemory)
+    : Builder(wasm), wasm(wasm), pointerType(pointerType),
+      asyncifyMemory(asyncifyMemory) {}
 
   Expression* makeGetStackPos() {
-    return makeLoad(4,
+    return makeLoad(pointerType.getByteSize(),
                     false,
-                    int32_t(DataOffset::BStackPos),
-                    4,
-                    makeGlobalGet(ASYNCIFY_DATA, Type::i32),
-                    Type::i32);
+                    int(DataOffset::BStackPos),
+                    pointerType.getByteSize(),
+                    makeGlobalGet(ASYNCIFY_DATA, pointerType),
+                    pointerType,
+                    asyncifyMemory);
   }
 
   Expression* makeIncStackPos(int32_t by) {
     if (by == 0) {
       return makeNop();
     }
-    return makeStore(
-      4,
-      int32_t(DataOffset::BStackPos),
-      4,
-      makeGlobalGet(ASYNCIFY_DATA, Type::i32),
-      makeBinary(AddInt32, makeGetStackPos(), makeConst(Literal(by))),
-      Type::i32);
+    auto literal = Literal::makeFromInt64(by, pointerType);
+    return makeStore(pointerType.getByteSize(),
+                     int(DataOffset::BStackPos),
+                     pointerType.getByteSize(),
+                     makeGlobalGet(ASYNCIFY_DATA, pointerType),
+                     makeBinary(Abstract::getBinary(pointerType, Abstract::Add),
+                                makeGetStackPos(),
+                                makeConst(literal)),
+                     pointerType,
+                     asyncifyMemory);
   }
 
   Expression* makeStateCheck(State value) {
@@ -825,7 +843,8 @@ public:
   }
 
   Expression* makeNegatedStateCheck(State value) {
-    return makeUnary(EqZInt32, makeStateCheck(value));
+    return makeUnary(Abstract::getUnary(pointerType, Abstract::EqZ),
+                     makeStateCheck(value));
   }
 };
 
@@ -834,16 +853,23 @@ struct AsyncifyFlow : public Pass {
   bool isFunctionParallel() override { return true; }
 
   ModuleAnalyzer* analyzer;
+  Type pointerType;
+  Name asyncifyMemory;
 
-  AsyncifyFlow* create() override { return new AsyncifyFlow(analyzer); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<AsyncifyFlow>(
+      analyzer, pointerType, asyncifyMemory);
+  }
 
-  AsyncifyFlow(ModuleAnalyzer* analyzer) : analyzer(analyzer) {}
+  AsyncifyFlow(ModuleAnalyzer* analyzer, Type pointerType, Name asyncifyMemory)
+    : analyzer(analyzer), pointerType(pointerType),
+      asyncifyMemory(asyncifyMemory) {}
 
-  void
-  runOnFunction(PassRunner* runner, Module* module_, Function* func_) override {
+  void runOnFunction(Module* module_, Function* func_) override {
     module = module_;
     func = func_;
-    builder = make_unique<AsyncifyBuilder>(*module);
+    builder =
+      make_unique<AsyncifyBuilder>(*module, pointerType, asyncifyMemory);
     // If the function cannot change our state, we have nothing to do -
     // we will never unwind or rewind the stack here.
     if (!analyzer->needsInstrumentation(func)) {
@@ -1208,10 +1234,19 @@ struct AsyncifyLocals : public WalkerPass<PostWalker<AsyncifyLocals>> {
   bool isFunctionParallel() override { return true; }
 
   ModuleAnalyzer* analyzer;
+  Type pointerType;
+  Name asyncifyMemory;
 
-  AsyncifyLocals* create() override { return new AsyncifyLocals(analyzer); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<AsyncifyLocals>(
+      analyzer, pointerType, asyncifyMemory);
+  }
 
-  AsyncifyLocals(ModuleAnalyzer* analyzer) : analyzer(analyzer) {}
+  AsyncifyLocals(ModuleAnalyzer* analyzer,
+                 Type pointerType,
+                 Name asyncifyMemory)
+    : analyzer(analyzer), pointerType(pointerType),
+      asyncifyMemory(asyncifyMemory) {}
 
   void visitCall(Call* curr) {
     // Replace calls to the fake intrinsics.
@@ -1220,10 +1255,14 @@ struct AsyncifyLocals : public WalkerPass<PostWalker<AsyncifyLocals>> {
     } else if (curr->target == ASYNCIFY_GET_CALL_INDEX) {
       replaceCurrent(builder->makeSequence(
         builder->makeIncStackPos(-4),
-        builder->makeLocalSet(
-          rewindIndex,
-          builder->makeLoad(
-            4, false, 0, 4, builder->makeGetStackPos(), Type::i32))));
+        builder->makeLocalSet(rewindIndex,
+                              builder->makeLoad(4,
+                                                false,
+                                                0,
+                                                4,
+                                                builder->makeGetStackPos(),
+                                                Type::i32,
+                                                asyncifyMemory))));
     } else if (curr->target == ASYNCIFY_CHECK_CALL_INDEX) {
       replaceCurrent(builder->makeBinary(
         EqInt32,
@@ -1274,7 +1313,8 @@ struct AsyncifyLocals : public WalkerPass<PostWalker<AsyncifyLocals>> {
     auto unwindIndex = builder->addVar(func, Type::i32);
     rewindIndex = builder->addVar(func, Type::i32);
     // Rewrite the function body.
-    builder = make_unique<AsyncifyBuilder>(*getModule());
+    builder =
+      make_unique<AsyncifyBuilder>(*getModule(), pointerType, asyncifyMemory);
     walk(func->body);
     // After the normal function body, emit a barrier before the postamble.
     Expression* barrier;
@@ -1372,7 +1412,7 @@ private:
     }
     auto* block = builder->makeBlock();
     block->list.push_back(builder->makeIncStackPos(-total));
-    auto tempIndex = builder->addVar(func, Type::i32);
+    auto tempIndex = builder->addVar(func, builder->pointerType);
     block->list.push_back(
       builder->makeLocalSet(tempIndex, builder->makeGetStackPos()));
     Index offset = 0;
@@ -1386,13 +1426,14 @@ private:
         auto size = getByteSize(type);
         assert(size % STACK_ALIGN == 0);
         // TODO: higher alignment?
-        loads.push_back(
-          builder->makeLoad(size,
-                            true,
-                            offset,
-                            STACK_ALIGN,
-                            builder->makeLocalGet(tempIndex, Type::i32),
-                            type));
+        loads.push_back(builder->makeLoad(
+          size,
+          true,
+          offset,
+          STACK_ALIGN,
+          builder->makeLocalGet(tempIndex, builder->pointerType),
+          type,
+          asyncifyMemory));
         offset += size;
       }
       Expression* load;
@@ -1416,7 +1457,7 @@ private:
     auto* func = getFunction();
     auto numLocals = func->getNumLocals();
     auto* block = builder->makeBlock();
-    auto tempIndex = builder->addVar(func, Type::i32);
+    auto tempIndex = builder->addVar(func, builder->pointerType);
     block->list.push_back(
       builder->makeLocalSet(tempIndex, builder->makeGetStackPos()));
     Index offset = 0;
@@ -1434,13 +1475,14 @@ private:
         }
         assert(size % STACK_ALIGN == 0);
         // TODO: higher alignment?
-        block->list.push_back(
-          builder->makeStore(size,
-                             offset,
-                             STACK_ALIGN,
-                             builder->makeLocalGet(tempIndex, Type::i32),
-                             localGet,
-                             type));
+        block->list.push_back(builder->makeStore(
+          size,
+          offset,
+          STACK_ALIGN,
+          builder->makeLocalGet(tempIndex, builder->pointerType),
+          localGet,
+          type,
+          asyncifyMemory));
         offset += size;
         ++j;
       }
@@ -1458,7 +1500,8 @@ private:
                          4,
                          builder->makeGetStackPos(),
                          builder->makeLocalGet(tempIndex, Type::i32),
-                         Type::i32),
+                         Type::i32,
+                         asyncifyMemory),
       builder->makeIncStackPos(4));
   }
 
@@ -1475,56 +1518,63 @@ private:
 } // anonymous namespace
 
 static std::string getFullImportName(Name module, Name base) {
-  return std::string(module.str) + '.' + base.str;
+  return std::string(module.str) + '.' + base.toString();
 }
 
 struct Asyncify : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    bool optimize = runner->options.optimizeLevel > 0;
-
-    // Ensure there is a memory, as we need it.
-    MemoryUtils::ensureExists(module->memory);
+  void run(Module* module) override {
+    auto& options = getPassOptions();
+    bool optimize = options.optimizeLevel > 0;
 
     // Find which things can change the state.
     auto stateChangingImports = String::trim(read_possible_response_file(
-      runner->options.getArgumentOrDefault("asyncify-imports", "")));
+      options.getArgumentOrDefault("asyncify-imports", "")));
     auto ignoreImports =
-      runner->options.getArgumentOrDefault("asyncify-ignore-imports", "");
+      options.getArgumentOrDefault("asyncify-ignore-imports", "");
     bool allImportsCanChangeState =
       stateChangingImports == "" && ignoreImports == "";
     String::Split listedImports(stateChangingImports, ",");
-    // TODO: consider renaming asyncify-ignore-indirect to
-    //       asyncify-ignore-nondirect, but that could break users.
-    auto ignoreNonDirect = runner->options.getArgumentOrDefault(
-                             "asyncify-ignore-indirect", "") == "";
+    // canIndirectChangeState is the default.  asyncify-ignore-indirect sets it
+    // to false.
+    auto canIndirectChangeState =
+      !options.hasArgument("asyncify-ignore-indirect");
     std::string removeListInput =
-      runner->options.getArgumentOrDefault("asyncify-removelist", "");
+      options.getArgumentOrDefault("asyncify-removelist", "");
     if (removeListInput.empty()) {
       // Support old name for now to avoid immediate breakage TODO remove
-      removeListInput =
-        runner->options.getArgumentOrDefault("asyncify-blacklist", "");
+      removeListInput = options.getArgumentOrDefault("asyncify-blacklist", "");
     }
     String::Split removeList(
       String::trim(read_possible_response_file(removeListInput)), ",");
     String::Split addList(
       String::trim(read_possible_response_file(
-        runner->options.getArgumentOrDefault("asyncify-addlist", ""))),
+        options.getArgumentOrDefault("asyncify-addlist", ""))),
       ",");
     std::string onlyListInput =
-      runner->options.getArgumentOrDefault("asyncify-onlylist", "");
+      options.getArgumentOrDefault("asyncify-onlylist", "");
     if (onlyListInput.empty()) {
       // Support old name for now to avoid immediate breakage TODO remove
-      onlyListInput =
-        runner->options.getArgumentOrDefault("asyncify-whitelist", "");
+      onlyListInput = options.getArgumentOrDefault("asyncify-whitelist", "");
     }
     String::Split onlyList(
       String::trim(read_possible_response_file(onlyListInput)), ",");
-    auto asserts =
-      runner->options.getArgumentOrDefault("asyncify-asserts", "") != "";
-    auto verbose =
-      runner->options.getArgumentOrDefault("asyncify-verbose", "") != "";
-    auto relocatable =
-      runner->options.getArgumentOrDefault("asyncify-relocatable", "") != "";
+    auto asserts = options.hasArgument("asyncify-asserts");
+    auto verbose = options.hasArgument("asyncify-verbose");
+    auto relocatable = options.hasArgument("asyncify-relocatable");
+    auto secondaryMemory = options.hasArgument("asyncify-in-secondary-memory");
+
+    // Ensure there is a memory, as we need it.
+    if (secondaryMemory) {
+      auto secondaryMemorySizeString =
+        options.getArgumentOrDefault("asyncify-secondary-memory-size", "1");
+      Address secondaryMemorySize = std::stoi(secondaryMemorySizeString);
+      asyncifyMemory = createSecondaryMemory(module, secondaryMemorySize);
+    } else {
+      MemoryUtils::ensureExists(module);
+      asyncifyMemory = module->memories[0]->name;
+    }
+    pointerType =
+      module->getMemory(asyncifyMemory)->is64() ? Type::i64 : Type::i32;
 
     removeList = handleBracketingOperators(removeList);
     addList = handleBracketingOperators(addList);
@@ -1551,7 +1601,7 @@ struct Asyncify : public Pass {
     // Scan the module.
     ModuleAnalyzer analyzer(*module,
                             canImportChangeState,
-                            ignoreNonDirect,
+                            canIndirectChangeState,
                             removeList,
                             addList,
                             onlyList,
@@ -1585,7 +1635,8 @@ struct Asyncify : public Pass {
         runner.add("reorder-locals");
         runner.add("merge-blocks");
       }
-      runner.add(make_unique<AsyncifyFlow>(&analyzer));
+      runner.add(
+        make_unique<AsyncifyFlow>(&analyzer, pointerType, asyncifyMemory));
       runner.setIsNested(true);
       runner.setValidateGlobally(false);
       runner.run();
@@ -1600,7 +1651,8 @@ struct Asyncify : public Pass {
       if (optimize) {
         runner.addDefaultFunctionOptimizationPasses();
       }
-      runner.add(make_unique<AsyncifyLocals>(&analyzer));
+      runner.add(
+        make_unique<AsyncifyLocals>(&analyzer, pointerType, asyncifyMemory));
       if (optimize) {
         runner.addDefaultFunctionOptimizationPasses();
       }
@@ -1628,8 +1680,8 @@ private:
     module->addGlobal(std::move(asyncifyState));
 
     auto asyncifyData = builder.makeGlobal(ASYNCIFY_DATA,
-                                           Type::i32,
-                                           builder.makeConst(int32_t(0)),
+                                           pointerType,
+                                           builder.makeConst(pointerType),
                                            Builder::Mutable);
     if (imported) {
       asyncifyData->module = ENV;
@@ -1643,33 +1695,37 @@ private:
     auto makeFunction = [&](Name name, bool setData, State state) {
       std::vector<Type> params;
       if (setData) {
-        params.push_back(Type::i32);
+        params.push_back(pointerType);
       }
       auto* body = builder.makeBlock();
       body->list.push_back(builder.makeGlobalSet(
         ASYNCIFY_STATE, builder.makeConst(int32_t(state))));
       if (setData) {
         body->list.push_back(builder.makeGlobalSet(
-          ASYNCIFY_DATA, builder.makeLocalGet(0, Type::i32)));
+          ASYNCIFY_DATA, builder.makeLocalGet(0, pointerType)));
       }
       // Verify the data is valid.
       auto* stackPos =
-        builder.makeLoad(4,
+        builder.makeLoad(pointerType.getByteSize(),
                          false,
-                         int32_t(DataOffset::BStackPos),
-                         4,
-                         builder.makeGlobalGet(ASYNCIFY_DATA, Type::i32),
-                         Type::i32);
+                         int(DataOffset::BStackPos),
+                         pointerType.getByteSize(),
+                         builder.makeGlobalGet(ASYNCIFY_DATA, pointerType),
+                         pointerType,
+                         asyncifyMemory);
       auto* stackEnd =
-        builder.makeLoad(4,
+        builder.makeLoad(pointerType.getByteSize(),
                          false,
-                         int32_t(DataOffset::BStackEnd),
-                         4,
-                         builder.makeGlobalGet(ASYNCIFY_DATA, Type::i32),
-                         Type::i32);
-      body->list.push_back(
-        builder.makeIf(builder.makeBinary(GtUInt32, stackPos, stackEnd),
-                       builder.makeUnreachable()));
+                         int(pointerType == Type::i64 ? DataOffset::BStackEnd64
+                                                      : DataOffset::BStackEnd),
+                         pointerType.getByteSize(),
+                         builder.makeGlobalGet(ASYNCIFY_DATA, pointerType),
+                         pointerType,
+                         asyncifyMemory);
+      body->list.push_back(builder.makeIf(
+        builder.makeBinary(
+          Abstract::getBinary(pointerType, Abstract::GtU), stackPos, stackEnd),
+        builder.makeUnreachable()));
       body->finalize();
       auto func = builder.makeFunction(
         name, Signature(Type(params), Type::none), {}, body);
@@ -1690,6 +1746,17 @@ private:
     module->addExport(builder.makeExport(
       ASYNCIFY_GET_STATE, ASYNCIFY_GET_STATE, ExternalKind::Function));
   }
+
+  Name createSecondaryMemory(Module* module, Address secondaryMemorySize) {
+    Name name = Names::getValidMemoryName(*module, "asyncify_memory");
+    auto secondaryMemory =
+      Builder::makeMemory(name, secondaryMemorySize, secondaryMemorySize);
+    module->addMemory(std::move(secondaryMemory));
+    return name;
+  }
+
+  Type pointerType;
+  Name asyncifyMemory;
 };
 
 Pass* createAsyncifyPass() { return new Asyncify(); }
@@ -1702,8 +1769,9 @@ struct ModAsyncify
       ModAsyncify<neverRewind, neverUnwind, importsAlwaysUnwind>>> {
   bool isFunctionParallel() override { return true; }
 
-  ModAsyncify* create() override {
-    return new ModAsyncify<neverRewind, neverUnwind, importsAlwaysUnwind>();
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<
+      ModAsyncify<neverRewind, neverUnwind, importsAlwaysUnwind>>();
   }
 
   void doWalkFunction(Function* func) {
@@ -1826,7 +1894,7 @@ Pass* createModAsyncifyAlwaysOnlyUnwindPass() {
 // Assume that we never unwind, but may still rewind.
 //
 struct ModAsyncifyNeverUnwind : public Pass {
-  void run(PassRunner* runner, Module* module) override {}
+  void run(Module* module) override {}
 };
 
 Pass* createModAsyncifyNeverUnwindPass() {
diff --git a/src/passes/AvoidReinterprets.cpp b/src/passes/AvoidReinterprets.cpp
index c260772..3f47953 100644
--- a/src/passes/AvoidReinterprets.cpp
+++ b/src/passes/AvoidReinterprets.cpp
@@ -76,7 +76,9 @@ static bool isReinterpret(Unary* curr) {
 struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new AvoidReinterprets; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<AvoidReinterprets>();
+  }
 
   struct Info {
     // Info used when analyzing.
@@ -115,11 +117,11 @@ struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> {
 
   void optimize(Function* func) {
     std::set<Load*> unoptimizables;
-    auto indexType = getModule()->memory.indexType;
     for (auto& [load, info] : infos) {
       if (info.reinterpreted && canReplaceWithReinterpret(load)) {
         // We should use another load here, to avoid reinterprets.
-        info.ptrLocal = Builder::addVar(func, indexType);
+        auto mem = getModule()->getMemory(load->memory);
+        info.ptrLocal = Builder::addVar(func, mem->indexType);
         info.reinterpretedLocal =
           Builder::addVar(func, load->type.reinterpret());
       } else {
@@ -173,7 +175,8 @@ struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> {
           auto& info = iter->second;
           Builder builder(*module);
           auto* ptr = curr->ptr;
-          auto indexType = getModule()->memory.indexType;
+          auto mem = getModule()->getMemory(curr->memory);
+          auto indexType = mem->indexType;
           curr->ptr = builder.makeLocalGet(info.ptrLocal, indexType);
           // Note that the other load can have its sign set to false - if the
           // original were an integer, the other is a float anyhow; and if
@@ -195,7 +198,8 @@ struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> {
                                 load->offset,
                                 load->align,
                                 ptr,
-                                load->type.reinterpret());
+                                load->type.reinterpret(),
+                                load->memory);
       }
     } finalOptimizer(infos, localGraph, getModule(), getPassOptions());
 
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index c83e95d..c19f194 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -37,14 +37,18 @@ set(passes_SOURCES
   Flatten.cpp
   FuncCastEmulation.cpp
   GenerateDynCalls.cpp
+  GlobalEffects.cpp
   GlobalRefining.cpp
+  GlobalStructInference.cpp
   GlobalTypeOptimization.cpp
+  GUFA.cpp
   Heap2Local.cpp
   I64ToI32Lowering.cpp
   Inlining.cpp
   InstrumentLocals.cpp
   InstrumentMemory.cpp
   Intrinsics.cpp
+  JSPI.cpp
   LegalizeJSInterface.cpp
   LimitSegments.cpp
   LocalCSE.cpp
@@ -58,10 +62,13 @@ set(passes_SOURCES
   MergeLocals.cpp
   Metrics.cpp
   MinifyImportsAndExports.cpp
+  Monomorphize.cpp
+  MultiMemoryLowering.cpp
   NameList.cpp
   NameTypes.cpp
   OnceReduction.cpp
   OptimizeAddedConstants.cpp
+  OptimizeCasts.cpp
   OptimizeInstructions.cpp
   OptimizeForJS.cpp
   PickLoadSigns.cpp
@@ -77,6 +84,7 @@ set(passes_SOURCES
   StackIR.cpp
   SignaturePruning.cpp
   SignatureRefining.cpp
+  SignExtLowering.cpp
   Strip.cpp
   StripTargetFeatures.cpp
   RedundantSetElimination.cpp
@@ -86,8 +94,9 @@ set(passes_SOURCES
   RemoveUnusedBrs.cpp
   RemoveUnusedNames.cpp
   RemoveUnusedModuleElements.cpp
-  ReorderLocals.cpp
   ReorderFunctions.cpp
+  ReorderGlobals.cpp
+  ReorderLocals.cpp
   ReReloop.cpp
   TrapMode.cpp
   TypeRefining.cpp
@@ -95,6 +104,7 @@ set(passes_SOURCES
   SimplifyGlobals.cpp
   SimplifyLocals.cpp
   Souperify.cpp
+  SpillPointers.cpp
   StackCheck.cpp
   SSAify.cpp
   Untee.cpp
diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp
index ba9b875..558be07 100644
--- a/src/passes/CoalesceLocals.cpp
+++ b/src/passes/CoalesceLocals.cpp
@@ -51,7 +51,9 @@ struct CoalesceLocals
   // FIXME DWARF updating does not handle local changes yet.
   bool invalidatesDWARF() override { return true; }
 
-  Pass* create() override { return new CoalesceLocals; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<CoalesceLocals>();
+  }
 
   // main entry point
 
@@ -526,7 +528,25 @@ void CoalesceLocals::applyIndices(std::vector<Index>& indices,
             continue;
           }
         }
-        // remove ineffective actions
+
+        // Remove ineffective actions, that is, dead stores.
+        //
+        // Note that this may have downsides for non-nullable locals:
+        //
+        //   x = whatever; // dead set for validation
+        //   if (..) {
+        //     x = value1;
+        //   } else {
+        //     x = value2;
+        //   }
+        //
+        // The dead set ensures validation, at the cost of extra code size and
+        // slower speed in some tiers (the optimizing tier, at least, will
+        // remove such dead sets anyhow). In theory keeping such a dead set may
+        // be worthwhile, as it may save code size (by keeping the local
+        // non-nullable and avoiding ref.as_non_nulls later). But the tradeoff
+        // here isn't clear, so do the simple thing for now and remove all dead
+        // sets.
         if (!action.effective) {
           // value may have no side effects, further optimizations can eliminate
           // it
@@ -562,7 +582,9 @@ void CoalesceLocals::applyIndices(std::vector<Index>& indices,
 }
 
 struct CoalesceLocalsWithLearning : public CoalesceLocals {
-  virtual Pass* create() override { return new CoalesceLocalsWithLearning; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<CoalesceLocalsWithLearning>();
+  }
 
   virtual void pickIndices(std::vector<Index>& indices) override;
 };
diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp
index 0323223..0e57a79 100644
--- a/src/passes/CodeFolding.cpp
+++ b/src/passes/CodeFolding.cpp
@@ -86,7 +86,9 @@ struct ExpressionMarker
 struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new CodeFolding; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<CodeFolding>();
+  }
 
   // information about a "tail" - code that reaches a point that we can
   // merge (e.g., a branch and some code leading up to it)
diff --git a/src/passes/CodePushing.cpp b/src/passes/CodePushing.cpp
index 29a3ae7..20f16b1 100644
--- a/src/passes/CodePushing.cpp
+++ b/src/passes/CodePushing.cpp
@@ -20,6 +20,7 @@
 //
 
 #include <ir/effects.h>
+#include <ir/manipulation.h>
 #include <pass.h>
 #include <wasm-builder.h>
 #include <wasm.h>
@@ -95,21 +96,27 @@ public:
     // Find an optimization segment: from the first pushable thing, to the first
     // point past which we want to push. We then push in that range before
     // continuing forward.
-    // we never need to push past a final element, as we couldn't be used after
-    // it.
-    Index relevant = list.size() - 1;
     const Index nothing = -1;
     Index i = 0;
     Index firstPushable = nothing;
-    while (i < relevant) {
+    while (i < list.size()) {
       if (firstPushable == nothing && isPushable(list[i])) {
         firstPushable = i;
         i++;
         continue;
       }
       if (firstPushable != nothing && isPushPoint(list[i])) {
-        // optimize this segment, and proceed from where it tells us
-        i = optimizeSegment(firstPushable, i);
+        // Optimize this segment, and proceed from where it tells us. First
+        // optimize things into the if, if possible, which does not move the
+        // push point. Then move things past the push point (which has the
+        // relative effect of moving the push point backwards as other things
+        // move forward).
+        optimizeIntoIf(firstPushable, i);
+        // We never need to push past a final element, as we couldn't be used
+        // after it.
+        if (i < list.size() - 1) {
+          i = optimizeSegment(firstPushable, i);
+        }
         firstPushable = nothing;
         continue;
       }
@@ -124,24 +131,39 @@ private:
       return nullptr;
     }
     auto index = set->index;
-    // to be pushable, this must be SFA and the right # of gets,
-    // but also have no side effects, as it may not execute if pushed.
+    // To be pushable, this must be SFA and the right # of gets.
+    //
+    // It must also not have side effects, as it may no longer execute after it
+    // is pushed, since it may be behind a condition that ends up false some of
+    // the time. However, removable side effects are ok here. The general
+    // problem with removable effects is that we can only remove them, but not
+    // move them, because of stuff like this:
+    //
+    //   if (x != 0) foo(1 / x);
+    //
+    // If we move 1 / x to execute unconditionally then it may trap, but it
+    // would be fine to remove it. This pass does not move code to places where
+    // it might execute more, but *less*: we keep the code behind any conditions
+    // it was already behind, and potentially put it behind further ones. In
+    // effect, we "partially remove" the code, making it not execute some of the
+    // time, which is fine.
     if (analyzer.isSFA(index) &&
         numGetsSoFar[index] == analyzer.getNumGets(index) &&
-        !EffectAnalyzer(passOptions, module, set->value).hasSideEffects()) {
+        !EffectAnalyzer(passOptions, module, set->value)
+           .hasUnremovableSideEffects()) {
       return set;
     }
     return nullptr;
   }
 
-  // Push past conditional control flow.
+  // Try to push past conditional control flow.
   // TODO: push into ifs as well
   bool isPushPoint(Expression* curr) {
     // look through drops
     if (auto* drop = curr->dynCast<Drop>()) {
       curr = drop->value;
     }
-    if (curr->is<If>()) {
+    if (curr->is<If>() || curr->is<BrOn>()) {
       return true;
     }
     if (auto* br = curr->dynCast<Break>()) {
@@ -161,26 +183,27 @@ private:
     // everything that matters if you want to be pushed past the pushPoint
     EffectAnalyzer cumulativeEffects(passOptions, module);
     cumulativeEffects.walk(list[pushPoint]);
-    // it is ok to ignore the branching here, that is the crucial point of this
-    // opt
-    // TODO: it would be ok to ignore thrown exceptions here, if we know they
-    //       could not be caught and must go outside of the function
-    cumulativeEffects.ignoreBranches();
+    // It is ok to ignore branching out of the block here, that is the crucial
+    // point of this optimization. That is, we are in a situation like this:
+    //
+    // {
+    //   x = value;
+    //   if (..) break;
+    //   foo(x);
+    // }
+    //
+    // If the branch is taken, then that's fine, it will jump out of this block
+    // and reach some outer scope, and in that case we never need x at all
+    // (since we've proven before that x is not used outside of this block, see
+    // numGetsSoFar which we use for that). Similarly, control flow could
+    // transfer away via a return or an exception and that would be ok as well.
+    cumulativeEffects.ignoreControlFlowTransfers();
     std::vector<LocalSet*> toPush;
     Index i = pushPoint - 1;
     while (1) {
       auto* pushable = isPushable(list[i]);
       if (pushable) {
-        auto iter = pushableEffects.find(pushable);
-        if (iter == pushableEffects.end()) {
-          iter =
-            pushableEffects
-              .emplace(std::piecewise_construct,
-                       std::forward_as_tuple(pushable),
-                       std::forward_as_tuple(passOptions, module, pushable))
-              .first;
-        }
-        auto& effects = iter->second;
+        const auto& effects = getPushableEffects(pushable);
         if (cumulativeEffects.invalidates(effects)) {
           // we can't push this, so further pushables must pass it
           cumulativeEffects.mergeIn(effects);
@@ -188,14 +211,14 @@ private:
           // we can push this, great!
           toPush.push_back(pushable);
         }
-        if (i == firstPushable) {
-          // no point in looking further
-          break;
-        }
       } else {
         // something that can't be pushed, so it might block further pushing
         cumulativeEffects.walk(list[i]);
       }
+      if (i == firstPushable) {
+        // no point in looking further
+        break;
+      }
       assert(i > 0);
       i--;
     }
@@ -227,6 +250,184 @@ private:
     return pushPoint - total + 1;
   }
 
+  // Similar to optimizeSegment, but for the case where the push point is an if,
+  // and we try to push into the if's arms, doing things like this:
+  //
+  //    x = op();
+  //    if (..) {
+  //      ..
+  //    }
+  // =>
+  //    if (..) {
+  //      x = op(); // this moved
+  //      ..
+  //    }
+  //
+  // This does not move the push point, so it does not have a return value,
+  // unlike optimizeSegment.
+  void optimizeIntoIf(Index firstPushable, Index pushPoint) {
+    assert(firstPushable != Index(-1) && pushPoint != Index(-1) &&
+           firstPushable < pushPoint);
+
+    auto* iff = list[pushPoint]->dynCast<If>();
+    if (!iff) {
+      return;
+    }
+
+    // Everything that matters if you want to be pushed past the pushPoint. This
+    // begins with the if condition's effects, as we must always push past
+    // those. Later, we will add to this when we need to.
+    EffectAnalyzer cumulativeEffects(passOptions, module, iff->condition);
+
+    // See optimizeSegment for why we can ignore control flow transfers here.
+    cumulativeEffects.ignoreControlFlowTransfers();
+
+    // Find the effects of the arms, which will affect what can be pushed.
+    EffectAnalyzer ifTrueEffects(passOptions, module, iff->ifTrue);
+    EffectAnalyzer ifFalseEffects(passOptions, module);
+    if (iff->ifFalse) {
+      ifFalseEffects.walk(iff->ifFalse);
+    }
+
+    // We need to know which locals are used after the if, as that can determine
+    // if we can push or not.
+    EffectAnalyzer postIfEffects(passOptions, module);
+    for (Index i = pushPoint + 1; i < list.size(); i++) {
+      postIfEffects.walk(list[i]);
+    }
+
+    // Start at the instruction right before the push point, and go back from
+    // there:
+    //
+    //    x = op();
+    //    y = op();
+    //    if (..) {
+    //      ..
+    //    }
+    //
+    // Here we will try to push y first, and then x. Note that if we push y
+    // then we can immediately try to push x after it, as it will remain in
+    // order with x if we do. If we do *not* push y we can still try to push x
+    // but we must move it past y, which means we need to check for interference
+    // between them (which we do by adding y's effects to cumulativeEffects).
+    //
+    // Decrement at the top of the loop for simplicity, so start with i at one
+    // past the first thing we can push (which is right before the push point).
+    Index i = pushPoint;
+    while (1) {
+      if (i == firstPushable) {
+        // We just finished processing the first thing that could be pushed;
+        // stop.
+        break;
+      }
+      assert(i > 0);
+      i--;
+      auto* pushable = isPushable(list[i]);
+      if (!pushable) {
+        // Something that is staying where it is, so anything we push later must
+        // move past it. Note the effects and continue.
+        cumulativeEffects.walk(list[i]);
+        continue;
+      }
+
+      auto index = pushable->index;
+
+      const auto& effects = getPushableEffects(pushable);
+
+      if (cumulativeEffects.invalidates(effects)) {
+        // This can't be moved forward. Add it to the things that are not
+        // moving.
+        cumulativeEffects.walk(list[i]);
+        continue;
+      }
+
+      // We only try to push into an arm if the local is used there. If the
+      // local is not used in either arm then we'll want to push it past the
+      // entire if, which is what optimizeSegment handles.
+      //
+      // We can push into the if-true arm if the local cannot be used if we go
+      // through the other arm:
+      //
+      //    x = op();
+      //    if (..) {
+      //      // we would like to move "x = op()" to here
+      //      ..
+      //    } else {
+      //      use(x);
+      //    }
+      //    use(x);
+      //
+      // Either of those use(x)s would stop us from moving to if-true arm.
+      //
+      // One specific case we handle is if there is a use after the if but the
+      // arm we don't push into is unreachable. In that case we only get to the
+      // later code after going through the reachable arm, which is ok to push
+      // into:
+      //
+      //    x = op();
+      //    if (..) {
+      //      // We'll push "x = op()" to here.
+      //      use(x);
+      //    } else {
+      //      return;
+      //    }
+      //    use(x);
+      auto maybePushInto = [&](Expression*& arm,
+                               const Expression* otherArm,
+                               EffectAnalyzer& armEffects,
+                               const EffectAnalyzer& otherArmEffects) {
+        if (!arm || !armEffects.localsRead.count(index) ||
+            otherArmEffects.localsRead.count(index)) {
+          // No arm, or this arm has no read of the index, or the other arm
+          // reads the index.
+          return false;
+        }
+        if (postIfEffects.localsRead.count(index) &&
+            (!otherArm || otherArm->type != Type::unreachable)) {
+          // The local is read later, which is bad, and there is no unreachable
+          // in the other arm which as mentioned above is the only thing that
+          // could have made it work out for us.
+          return false;
+        }
+
+        // We can do it! Push into one of the if's arms, and put a nop where it
+        // used to be.
+        Builder builder(module);
+        auto* block = builder.blockify(arm);
+        arm = block;
+        // TODO: this is quadratic in the number of pushed things
+        ExpressionManipulator::spliceIntoBlock(block, 0, pushable);
+        list[i] = builder.makeNop();
+
+        // The code we pushed adds to the effects in that arm.
+        armEffects.walk(pushable);
+
+        // TODO: After pushing we could recurse and run both this function and
+        //       optimizeSegment in that location. For now, leave that to later
+        //       cycles of the optimizer, as this case seems rairly rare.
+        return true;
+      };
+
+      if (!maybePushInto(
+            iff->ifTrue, iff->ifFalse, ifTrueEffects, ifFalseEffects) &&
+          !maybePushInto(
+            iff->ifFalse, iff->ifTrue, ifFalseEffects, ifTrueEffects)) {
+        // We didn't push this anywhere, so further pushables must pass it.
+        cumulativeEffects.mergeIn(effects);
+      }
+    }
+  }
+
+  const EffectAnalyzer& getPushableEffects(LocalSet* pushable) {
+    auto iter = pushableEffects.find(pushable);
+    if (iter == pushableEffects.end()) {
+      iter =
+        pushableEffects.try_emplace(pushable, passOptions, module, pushable)
+          .first;
+    }
+    return iter->second;
+  }
+
   // Pushables may need to be scanned more than once, so cache their effects.
   std::unordered_map<LocalSet*, EffectAnalyzer> pushableEffects;
 };
@@ -234,7 +435,14 @@ private:
 struct CodePushing : public WalkerPass<PostWalker<CodePushing>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new CodePushing; }
+  // This pass moves code forward in blocks, but a local.set would not be moved
+  // after a local.get with the same index (effects prevent breaking things that
+  // way), so validation will be preserved.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<CodePushing>();
+  }
 
   LocalAnalyzer analyzer;
 
@@ -254,10 +462,9 @@ struct CodePushing : public WalkerPass<PostWalker<CodePushing>> {
   void visitLocalGet(LocalGet* curr) { numGetsSoFar[curr->index]++; }
 
   void visitBlock(Block* curr) {
-    // Pushing code only makes sense if we are size 3 or above: we need
-    // one element to push, an element to push it past, and an element to use
-    // what we pushed.
-    if (curr->list.size() < 3) {
+    // Pushing code only makes sense if we are size 2 or above: we need one
+    // element to push and an element to push it into, at minimum.
+    if (curr->list.size() < 2) {
       return;
     }
     // At this point in the postorder traversal we have gone through all our
diff --git a/src/passes/ConstHoisting.cpp b/src/passes/ConstHoisting.cpp
index 3320c12..6463221 100644
--- a/src/passes/ConstHoisting.cpp
+++ b/src/passes/ConstHoisting.cpp
@@ -44,7 +44,9 @@ static const Index MIN_USES = 2;
 struct ConstHoisting : public WalkerPass<PostWalker<ConstHoisting>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new ConstHoisting; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<ConstHoisting>();
+  }
 
   InsertOrderedMap<Literal, std::vector<Expression**>> uses;
 
@@ -89,15 +91,9 @@ private:
         size = value.type.getByteSize();
         break;
       }
-        // not implemented yet
+      // not implemented yet
       case Type::v128:
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref: {
         return false;
-      }
       case Type::none:
       case Type::unreachable:
         WASM_UNREACHABLE("unexpected type");
diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp
index 1755c97..5048b97 100644
--- a/src/passes/ConstantFieldPropagation.cpp
+++ b/src/passes/ConstantFieldPropagation.cpp
@@ -32,7 +32,6 @@
 #include "ir/struct-utils.h"
 #include "ir/utils.h"
 #include "pass.h"
-#include "support/unique_deferring_queue.h"
 #include "wasm-builder.h"
 #include "wasm-traversal.h"
 #include "wasm.h"
@@ -53,7 +52,12 @@ using PCVFunctionStructValuesMap =
 struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new FunctionOptimizer(infos); }
+  // Only modifies struct.get operations.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FunctionOptimizer>(infos);
+  }
 
   FunctionOptimizer(PCVStructValuesMap& infos) : infos(infos) {}
 
@@ -124,8 +128,8 @@ private:
 
 struct PCVScanner
   : public StructUtils::StructScanner<PossibleConstantValues, PCVScanner> {
-  Pass* create() override {
-    return new PCVScanner(functionNewInfos, functionSetGetInfos);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<PCVScanner>(functionNewInfos, functionSetGetInfos);
   }
 
   PCVScanner(StructUtils::FunctionStructValuesMap<PossibleConstantValues>&
@@ -176,15 +180,23 @@ struct PCVScanner
 };
 
 struct ConstantFieldPropagation : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      Fatal() << "ConstantFieldPropagation requires nominal typing";
+  // Only modifies struct.get operations.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "CFP requires nominal/isorecursive typing";
     }
 
     // Find and analyze all writes inside each function.
     PCVFunctionStructValuesMap functionNewInfos(*module),
       functionSetInfos(*module);
     PCVScanner scanner(functionNewInfos, functionSetInfos);
+    auto* runner = getPassRunner();
     scanner.run(runner, module);
     scanner.runOnModuleCode(runner, module);
 
diff --git a/src/passes/DWARF.cpp b/src/passes/DWARF.cpp
index bc2af32..efde51a 100644
--- a/src/passes/DWARF.cpp
+++ b/src/passes/DWARF.cpp
@@ -30,9 +30,7 @@
 namespace wasm {
 
 struct DWARFDump : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    Debug::dumpDWARF(*module);
-  }
+  void run(Module* module) override { Debug::dumpDWARF(*module); }
 };
 
 Pass* createDWARFDumpPass() { return new DWARFDump(); }
diff --git a/src/passes/DataFlowOpts.cpp b/src/passes/DataFlowOpts.cpp
index 40e0c3c..79878f4 100644
--- a/src/passes/DataFlowOpts.cpp
+++ b/src/passes/DataFlowOpts.cpp
@@ -39,7 +39,9 @@ namespace wasm {
 struct DataFlowOpts : public WalkerPass<PostWalker<DataFlowOpts>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new DataFlowOpts; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<DataFlowOpts>();
+  }
 
   DataFlow::Users nodeUsers;
 
diff --git a/src/passes/DeAlign.cpp b/src/passes/DeAlign.cpp
index 795b6a2..15adc2e 100644
--- a/src/passes/DeAlign.cpp
+++ b/src/passes/DeAlign.cpp
@@ -27,7 +27,9 @@ namespace wasm {
 struct DeAlign : public WalkerPass<PostWalker<DeAlign>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new DeAlign(); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<DeAlign>();
+  }
 
   void visitLoad(Load* curr) { curr->align = 1; }
 
diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp
index 784aec3..d319340 100644
--- a/src/passes/DeadArgumentElimination.cpp
+++ b/src/passes/DeadArgumentElimination.cpp
@@ -84,13 +84,15 @@ struct DAEFunctionInfo {
   DAEFunctionInfo() { hasUnseenCalls = false; }
 };
 
-typedef std::unordered_map<Name, DAEFunctionInfo> DAEFunctionInfoMap;
+using DAEFunctionInfoMap = std::unordered_map<Name, DAEFunctionInfo>;
 
 struct DAEScanner
   : public WalkerPass<PostWalker<DAEScanner, Visitor<DAEScanner>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new DAEScanner(infoMap); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<DAEScanner>(infoMap);
+  }
 
   DAEScanner(DAEFunctionInfoMap* infoMap) : infoMap(infoMap) {}
 
@@ -172,16 +174,16 @@ struct DAE : public Pass {
 
   bool optimize = false;
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     // Iterate to convergence.
     while (1) {
-      if (!iteration(runner, module)) {
+      if (!iteration(module)) {
         break;
       }
     }
   }
 
-  bool iteration(PassRunner* runner, Module* module) {
+  bool iteration(Module* module) {
     allDroppedCalls.clear();
 
     DAEFunctionInfoMap infoMap;
@@ -198,7 +200,7 @@ struct DAE : public Pass {
       }
     }
     // Scan all the functions.
-    scanner.run(runner, module);
+    scanner.run(getPassRunner(), module);
     // Combine all the info.
     std::map<Name, std::vector<Call*>> allCalls;
     std::unordered_set<Name> tailCallees;
@@ -245,7 +247,7 @@ struct DAE : public Pass {
       // Changing a call expression's return type can propagate out to its
       // parents, and so we must refinalize.
       // TODO: We could track in which functions we actually make changes.
-      ReFinalize().run(runner, module);
+      ReFinalize().run(getPassRunner(), module);
     }
     // Track which functions we changed, and optimize them later if necessary.
     std::unordered_set<Function*> changed;
@@ -260,7 +262,7 @@ struct DAE : public Pass {
         continue;
       }
       auto removedIndexes = ParamUtils::removeParameters(
-        {func}, infoMap[name].unusedParams, calls, {}, module, runner);
+        {func}, infoMap[name].unusedParams, calls, {}, module, getPassRunner());
       if (!removedIndexes.empty()) {
         // Success!
         changed.insert(func);
@@ -304,7 +306,7 @@ struct DAE : public Pass {
       }
     }
     if (optimize && !changed.empty()) {
-      OptUtils::optimizeAfterInlining(changed, module, runner);
+      OptUtils::optimizeAfterInlining(changed, module, getPassRunner());
     }
     return !changed.empty() || refinedReturnTypes;
   }
diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp
index c30da54..0b382f4 100644
--- a/src/passes/DeadCodeElimination.cpp
+++ b/src/passes/DeadCodeElimination.cpp
@@ -44,7 +44,13 @@ struct DeadCodeElimination
                  UnifiedExpressionVisitor<DeadCodeElimination>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new DeadCodeElimination; }
+  // This pass removes dead code, which can only help validation (a dead
+  // local.get might have prevented validation).
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<DeadCodeElimination>();
+  }
 
   // as we remove code, we must keep the types of other nodes valid
   TypeUpdater typeUpdater;
diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp
index 6853f1f..21254cd 100644
--- a/src/passes/Directize.cpp
+++ b/src/passes/Directize.cpp
@@ -19,11 +19,21 @@
 // the table cannot change, and if we see a constant argument for the
 // indirect call's index.
 //
+// If called with
+//
+//   --pass-arg=directize-initial-contents-immutable
+//
+// then the initial tables' contents are assumed to be immutable. That is, if
+// a table looks like [a, b, c] in the wasm, and we see a call to index 1, we
+// will assume it must call b. It is possible that the table is appended to, but
+// in this mode we assume the initial contents are not overwritten. This is the
+// case for output from LLVM, for example.
+//
 
 #include <unordered_map>
 
+#include "call-utils.h"
 #include "ir/table-utils.h"
-#include "ir/type-updating.h"
 #include "ir/utils.h"
 #include "pass.h"
 #include "wasm-builder.h"
@@ -34,87 +44,63 @@ namespace wasm {
 
 namespace {
 
+struct TableInfo {
+  // Whether the table may be modifed at runtime, either because it is imported
+  // or exported, or table.set operations exist for it in the code.
+  bool mayBeModified = false;
+
+  // Whether we can assume that the initial contents are immutable. See the
+  // toplevel comment.
+  bool initialContentsImmutable = false;
+
+  std::unique_ptr<TableUtils::FlatTable> flatTable;
+
+  bool canOptimize() const {
+    // We can optimize if:
+    //  * Either the table can't be modified at all, or it can be modified but
+    //    the initial contents are immutable (so we can optimize them).
+    //  * The table is flat.
+    return (!mayBeModified || initialContentsImmutable) && flatTable->valid;
+  }
+};
+
+using TableInfoMap = std::unordered_map<Name, TableInfo>;
+
 struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new FunctionDirectizer(tables); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FunctionDirectizer>(tables);
+  }
 
-  FunctionDirectizer(
-    const std::unordered_map<Name, TableUtils::FlatTable>& tables)
-    : tables(tables) {}
+  FunctionDirectizer(const TableInfoMap& tables) : tables(tables) {}
 
   void visitCallIndirect(CallIndirect* curr) {
-    auto it = tables.find(curr->table);
-    if (it == tables.end()) {
+    auto& table = tables.at(curr->table);
+    if (!table.canOptimize()) {
       return;
     }
-
-    auto& flatTable = it->second;
-
     // If the target is constant, we can emit a direct call.
     if (curr->target->is<Const>()) {
       std::vector<Expression*> operands(curr->operands.begin(),
                                         curr->operands.end());
-      replaceCurrent(makeDirectCall(operands, curr->target, flatTable, curr));
+      makeDirectCall(operands, curr->target, table, curr);
       return;
     }
 
-    // If the target is a select of two different constants, we can emit two
-    // direct calls.
-    // TODO: handle 3+
-    // TODO: handle the case where just one arm is a constant?
-    if (auto* select = curr->target->dynCast<Select>()) {
-      if (select->ifTrue->is<Const>() && select->ifFalse->is<Const>()) {
-        Builder builder(*getModule());
-        auto* func = getFunction();
-        std::vector<Expression*> blockContents;
-
-        if (select->condition->type == Type::unreachable) {
-          // Leave this for DCE.
-          return;
-        }
-
-        // We must use the operands twice, and also must move the condition to
-        // execute first; use locals for them all. While doing so, if we see
-        // any are unreachable, stop trying to optimize and leave this for DCE.
-        std::vector<Index> operandLocals;
-        for (auto* operand : curr->operands) {
-          if (operand->type == Type::unreachable ||
-              !TypeUpdating::canHandleAsLocal(operand->type)) {
-            return;
-          }
-        }
-
-        // None of the types are a problem, so we can proceed to add new vars as
-        // needed and perform this optimization.
-        for (auto* operand : curr->operands) {
-          auto currLocal = builder.addVar(func, operand->type);
-          operandLocals.push_back(currLocal);
-          blockContents.push_back(builder.makeLocalSet(currLocal, operand));
-          // By adding locals we must make type adjustments at the end.
-          changedTypes = true;
-        }
-
-        // Build the calls.
-        auto numOperands = curr->operands.size();
-        auto getOperands = [&]() {
-          std::vector<Expression*> newOperands(numOperands);
-          for (Index i = 0; i < numOperands; i++) {
-            newOperands[i] =
-              builder.makeLocalGet(operandLocals[i], curr->operands[i]->type);
-          }
-          return newOperands;
-        };
-        auto* ifTrueCall =
-          makeDirectCall(getOperands(), select->ifTrue, flatTable, curr);
-        auto* ifFalseCall =
-          makeDirectCall(getOperands(), select->ifFalse, flatTable, curr);
-
-        // Create the if to pick the calls, and emit the final block.
-        auto* iff = builder.makeIf(select->condition, ifTrueCall, ifFalseCall);
-        blockContents.push_back(iff);
-        replaceCurrent(builder.makeBlock(blockContents));
-      }
+    // Emit direct calls for things like a select over constants.
+    if (auto* calls = CallUtils::convertToDirectCalls(
+          curr,
+          [&](Expression* target) {
+            return getTargetInfo(target, table, curr);
+          },
+          *getFunction(),
+          *getModule())) {
+      replaceCurrent(calls);
+      // Note that types may have changed, as the utility here can add locals
+      // which require fixups if they are non-nullable, for example.
+      changedTypes = true;
+      return;
     }
   }
 
@@ -122,44 +108,82 @@ struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
     WalkerPass<PostWalker<FunctionDirectizer>>::doWalkFunction(func);
     if (changedTypes) {
       ReFinalize().walkFunctionInModule(func, getModule());
-      TypeUpdating::handleNonDefaultableLocals(func, *getModule());
     }
   }
 
 private:
-  const std::unordered_map<Name, TableUtils::FlatTable>& tables;
+  const TableInfoMap& tables;
 
   bool changedTypes = false;
 
-  // Create a direct call for a given list of operands, an expression which is
-  // known to contain a constant indicating the table offset, and the relevant
-  // table. If we can see that the call will trap, instead return an
-  // unreachable.
-  Expression* makeDirectCall(const std::vector<Expression*>& operands,
-                             Expression* c,
-                             const TableUtils::FlatTable& flatTable,
-                             CallIndirect* original) {
-    Index index = c->cast<Const>()->value.geti32();
+  // Given an expression that we will use as the target of an indirect call,
+  // analyze it and return one of the results of CallUtils::IndirectCallInfo,
+  // that is, whether we know a direct call target, or we know it will trap, or
+  // if we know nothing.
+  CallUtils::IndirectCallInfo getTargetInfo(Expression* target,
+                                            const TableInfo& table,
+                                            CallIndirect* original) {
+    auto* c = target->dynCast<Const>();
+    if (!c) {
+      return CallUtils::Unknown{};
+    }
 
-    // If the index is invalid, or the type is wrong, we can
-    // emit an unreachable here, since in Binaryen it is ok to
-    // reorder/replace traps when optimizing (but never to
-    // remove them, at least not by default).
+    Index index = c->value.geti32();
+
+    // Check if index is invalid, or the type is wrong.
+    auto& flatTable = *table.flatTable;
     if (index >= flatTable.names.size()) {
-      return replaceWithUnreachable(operands);
+      // The index is out of bounds for the initial table's content. This may
+      // trap, but it may also not trap if the table is modified later (if a
+      // function is appended to it).
+      if (!table.mayBeModified) {
+        return CallUtils::Trap{};
+      } else {
+        // The table may be modified, so it might be appended to. We should only
+        // get here in the case that the initial contents are immutable, as
+        // otherwise we have nothing to optimize at all.
+        assert(table.initialContentsImmutable);
+        return CallUtils::Unknown{};
+      }
     }
     auto name = flatTable.names[index];
     if (!name.is()) {
-      return replaceWithUnreachable(operands);
+      return CallUtils::Trap{};
     }
     auto* func = getModule()->getFunction(name);
     if (original->heapType != func->type) {
-      return replaceWithUnreachable(operands);
+      return CallUtils::Trap{};
+    }
+    return CallUtils::Known{name};
+  }
+
+  // Create a direct call for a given list of operands, an expression which is
+  // known to contain a constant indicating the table offset, and the relevant
+  // table, if we can. If we can see that the call will trap, instead replace
+  // with an unreachable.
+  void makeDirectCall(const std::vector<Expression*>& operands,
+                      Expression* c,
+                      const TableInfo& table,
+                      CallIndirect* original) {
+    auto info = getTargetInfo(c, table, original);
+    if (std::get_if<CallUtils::Unknown>(&info)) {
+      // We don't know anything here.
+      return;
+    }
+    // If the index is invalid, or the type is wrong, we can
+    // emit an unreachable here, since in Binaryen it is ok to
+    // reorder/replace traps when optimizing (but never to
+    // remove them, at least not by default).
+    if (std::get_if<CallUtils::Trap>(&info)) {
+      replaceCurrent(replaceWithUnreachable(operands));
+      return;
     }
 
     // Everything looks good!
-    return Builder(*getModule())
-      .makeCall(name, operands, original->type, original->isReturn);
+    auto name = std::get<CallUtils::Known>(info).target;
+    replaceCurrent(
+      Builder(*getModule())
+        .makeCall(name, operands, original->type, original->isReturn));
   }
 
   Expression* replaceWithUnreachable(const std::vector<Expression*>& operands) {
@@ -177,12 +201,54 @@ private:
 };
 
 struct Directize : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    // Find which tables are valid to optimize on. They must not be imported nor
-    // exported (so the outside cannot modify them), and must have no sets in
-    // any part of the module.
+  void run(Module* module) override {
+    if (module->tables.empty()) {
+      return;
+    }
+
+    // TODO: consider a per-table option here
+    auto initialContentsImmutable =
+      getPassOptions().hasArgument("directize-initial-contents-immutable");
+
+    // Set up the initial info.
+    TableInfoMap tables;
+    for (auto& table : module->tables) {
+      tables[table->name].initialContentsImmutable = initialContentsImmutable;
+      tables[table->name].flatTable =
+        std::make_unique<TableUtils::FlatTable>(*module, *table);
+    }
+
+    // Next, look at the imports and exports.
+
+    for (auto& table : module->tables) {
+      if (table->imported()) {
+        tables[table->name].mayBeModified = true;
+      }
+    }
+
+    for (auto& ex : module->exports) {
+      if (ex->kind == ExternalKind::Table) {
+        tables[ex->value].mayBeModified = true;
+      }
+    }
+
+    // This may already be enough information to know that we can't optimize
+    // anything. If so, skip scanning all the module contents.
+    auto canOptimize = [&]() {
+      for (auto& [_, info] : tables) {
+        if (info.canOptimize()) {
+          return true;
+        }
+      }
+      return false;
+    };
+
+    if (!canOptimize()) {
+      return;
+    }
+
+    // Find which tables have sets.
 
-    // First, find which tables have sets.
     using TablesWithSet = std::unordered_set<Name>;
 
     ModuleUtils::ParallelFunctionAnalysis<TablesWithSet> analysis(
@@ -195,47 +261,20 @@ struct Directize : public Pass {
         }
       });
 
-    TablesWithSet tablesWithSet;
     for (auto& [_, names] : analysis.map) {
       for (auto name : names) {
-        tablesWithSet.insert(name);
-      }
-    }
-
-    std::unordered_map<Name, TableUtils::FlatTable> validTables;
-
-    for (auto& table : module->tables) {
-      if (table->imported()) {
-        continue;
-      }
-
-      if (tablesWithSet.count(table->name)) {
-        continue;
-      }
-
-      bool canOptimizeCallIndirect = true;
-      for (auto& ex : module->exports) {
-        if (ex->kind == ExternalKind::Table && ex->value == table->name) {
-          canOptimizeCallIndirect = false;
-          break;
-        }
-      }
-      if (!canOptimizeCallIndirect) {
-        continue;
-      }
-
-      // All conditions are valid, this is optimizable.
-      TableUtils::FlatTable flatTable(*module, *table);
-      if (flatTable.valid) {
-        validTables.emplace(table->name, flatTable);
+        tables[name].mayBeModified = true;
       }
     }
 
-    if (validTables.empty()) {
+    // Perhaps the new information about tables with sets shows we cannot
+    // optimize.
+    if (!canOptimize()) {
       return;
     }
 
-    FunctionDirectizer(validTables).run(runner, module);
+    // We can optimize!
+    FunctionDirectizer(tables).run(getPassRunner(), module);
   }
 };
 
diff --git a/src/passes/DuplicateFunctionElimination.cpp b/src/passes/DuplicateFunctionElimination.cpp
index 2e8c83c..e797cd8 100644
--- a/src/passes/DuplicateFunctionElimination.cpp
+++ b/src/passes/DuplicateFunctionElimination.cpp
@@ -34,11 +34,14 @@ struct DuplicateFunctionElimination : public Pass {
   // FIXME Merge DWARF info
   bool invalidatesDWARF() override { return true; }
 
-  void run(PassRunner* runner, Module* module) override {
+  // This pass merges functions but does not alter their contents.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
     // Multiple iterations may be necessary: A and B may be identical only after
     // we see the functions C1 and C2 that they call are in fact identical.
     // Rarely, such "chains" can be very long, so we limit how many we do.
-    auto& options = runner->options;
+    auto& options = getPassOptions();
     Index limit;
     if (options.optimizeLevel >= 3 || options.shrinkLevel >= 1) {
       limit = module->functions.size(); // no limit
@@ -53,7 +56,7 @@ struct DuplicateFunctionElimination : public Pass {
       limit--;
       // Hash all the functions
       auto hashes = FunctionHasher::createMap(module);
-      FunctionHasher(&hashes).run(runner, module);
+      FunctionHasher(&hashes).run(getPassRunner(), module);
       // Find hash-equal groups
       std::map<uint32_t, std::vector<Function*>> hashGroups;
       ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) {
@@ -93,7 +96,7 @@ struct DuplicateFunctionElimination : public Pass {
         // remove the duplicates
         module->removeFunctions(
           [&](Function* func) { return duplicates.count(func->name) > 0; });
-        OptUtils::replaceFunctions(runner, *module, replacements);
+        OptUtils::replaceFunctions(getPassRunner(), *module, replacements);
       } else {
         break;
       }
diff --git a/src/passes/DuplicateImportElimination.cpp b/src/passes/DuplicateImportElimination.cpp
index faf2bb7..33ce228 100644
--- a/src/passes/DuplicateImportElimination.cpp
+++ b/src/passes/DuplicateImportElimination.cpp
@@ -28,7 +28,10 @@
 namespace wasm {
 
 struct DuplicateImportElimination : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  // This pass does not alter function contents.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
     ImportInfo imports(*module);
     std::map<Name, Name> replacements;
     std::map<std::pair<Name, Name>, Name> seen;
@@ -51,7 +54,7 @@ struct DuplicateImportElimination : public Pass {
     }
     if (!replacements.empty()) {
       module->updateMaps();
-      OptUtils::replaceFunctions(runner, *module, replacements);
+      OptUtils::replaceFunctions(getPassRunner(), *module, replacements);
       for (auto name : toRemove) {
         module->removeFunction(name);
       }
diff --git a/src/passes/ExtractFunction.cpp b/src/passes/ExtractFunction.cpp
index 0939ebe..e3f2a55 100644
--- a/src/passes/ExtractFunction.cpp
+++ b/src/passes/ExtractFunction.cpp
@@ -54,25 +54,24 @@ static void extract(PassRunner* runner, Module* module, Name name) {
   // Remove unneeded things.
   PassRunner postRunner(runner);
   postRunner.add("remove-unused-module-elements");
-  postRunner.setIsNested(true);
   postRunner.run();
 }
 
 struct ExtractFunction : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    Name name = runner->options.getArgument(
+  void run(Module* module) override {
+    Name name = getPassOptions().getArgument(
       "extract-function",
       "ExtractFunction usage:  wasm-opt --extract-function=FUNCTION_NAME");
-    extract(runner, module, name);
+    extract(getPassRunner(), module, name);
   }
 };
 
 struct ExtractFunctionIndex : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     std::string index =
-      runner->options.getArgument("extract-function-index",
-                                  "ExtractFunctionIndex usage: wasm-opt "
-                                  "--extract-function-index=FUNCTION_INDEX");
+      getPassOptions().getArgument("extract-function-index",
+                                   "ExtractFunctionIndex usage: wasm-opt "
+                                   "--extract-function-index=FUNCTION_INDEX");
     for (char c : index) {
       if (!std::isdigit(c)) {
         Fatal() << "Expected numeric function index";
@@ -80,11 +79,12 @@ struct ExtractFunctionIndex : public Pass {
     }
     Index i = std::stoi(index);
     if (i >= module->functions.size()) {
-      Fatal() << "Invalid function index";
+      Fatal() << "Out of bounds function index " << i << "! (module has only "
+              << module->functions.size() << " functions)";
     }
     // Assumes imports are at the beginning
-    Name name = module->functions[std::stoi(index)]->name;
-    extract(runner, module, name);
+    Name name = module->functions[i]->name;
+    extract(getPassRunner(), module, name);
   }
 };
 
diff --git a/src/passes/Flatten.cpp b/src/passes/Flatten.cpp
index be43e57..e8dec30 100644
--- a/src/passes/Flatten.cpp
+++ b/src/passes/Flatten.cpp
@@ -44,7 +44,6 @@
 #include <ir/eh-utils.h>
 #include <ir/flat.h>
 #include <ir/properties.h>
-#include <ir/type-updating.h>
 #include <ir/utils.h>
 #include <pass.h>
 #include <wasm-builder.h>
@@ -77,7 +76,9 @@ struct Flatten
   // FIXME DWARF updating does not handle local changes yet.
   bool invalidatesDWARF() override { return true; }
 
-  Pass* create() override { return new Flatten; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<Flatten>();
+  }
 
   // For each expression, a bunch of expressions that should execute right
   // before it
@@ -368,17 +369,6 @@ struct Flatten
     }
     // the body may have preludes
     curr->body = getPreludesWithExpression(originalBody, curr->body);
-    // New locals we added may be non-nullable.
-    TypeUpdating::handleNonDefaultableLocals(curr, *getModule());
-    // We cannot handle non-nullable tuples currently, see the comment at the
-    // top of the file.
-    for (auto type : curr->vars) {
-      if (!type.isDefaultable()) {
-        Fatal() << "Flatten was forced to add a local of a type it cannot "
-                   "handle yet: "
-                << type;
-      }
-    }
 
     // Flatten can generate blocks within 'catch', making pops invalid. Fix them
     // up.
diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp
index 704f44e..3255208 100644
--- a/src/passes/FuncCastEmulation.cpp
+++ b/src/passes/FuncCastEmulation.cpp
@@ -62,13 +62,6 @@ static Expression* toABI(Expression* value, Module* module) {
     case Type::v128: {
       WASM_UNREACHABLE("v128 not implemented yet");
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref: {
-      WASM_UNREACHABLE("reference types cannot be converted to i64");
-    }
     case Type::none: {
       // the value is none, but we need a value here
       value =
@@ -107,13 +100,6 @@ static Expression* fromABI(Expression* value, Type type, Module* module) {
     case Type::v128: {
       WASM_UNREACHABLE("v128 not implemented yet");
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref: {
-      WASM_UNREACHABLE("reference types cannot be converted from i64");
-    }
     case Type::none: {
       value = builder.makeDrop(value);
       break;
@@ -130,8 +116,8 @@ struct ParallelFuncCastEmulation
   : public WalkerPass<PostWalker<ParallelFuncCastEmulation>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override {
-    return new ParallelFuncCastEmulation(ABIType, numParams);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<ParallelFuncCastEmulation>(ABIType, numParams);
   }
 
   ParallelFuncCastEmulation(HeapType ABIType, Index numParams)
@@ -165,9 +151,9 @@ private:
 };
 
 struct FuncCastEmulation : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    Index numParams =
-      std::stoul(runner->options.getArgumentOrDefault("max-func-params", "16"));
+  void run(Module* module) override {
+    Index numParams = std::stoul(
+      getPassOptions().getArgumentOrDefault("max-func-params", "16"));
     // we just need the one ABI function type for all indirect calls
     HeapType ABIType(
       Signature(Type(std::vector<Type>(numParams, Type::i64)), Type::i64));
@@ -185,13 +171,13 @@ struct FuncCastEmulation : public Pass {
     });
 
     // update call_indirects
-    ParallelFuncCastEmulation(ABIType, numParams).run(runner, module);
+    ParallelFuncCastEmulation(ABIType, numParams).run(getPassRunner(), module);
   }
 
 private:
   // Creates a thunk for a function, casting args and return value as needed.
   Name makeThunk(Name name, Module* module, Index numParams) {
-    Name thunk = std::string("byn$fpcast-emu$") + name.str;
+    Name thunk = std::string("byn$fpcast-emu$") + name.toString();
     if (module->getFunctionOrNull(thunk)) {
       Fatal() << "FuncCastEmulation::makeThunk seems a thunk name already in "
                  "use. Was the pass already run on this code?";
diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp
new file mode 100644
index 0000000..9774c1b
--- /dev/null
+++ b/src/passes/GUFA.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Grand Unified Flow Analysis (GUFA)
+//
+// Optimize based on information about what content can appear in each location
+// in the program. This does a whole-program analysis to find that out and
+// hopefully learn more than the type system does - for example, a type might be
+// $A, which means $A or any subtype can appear there, but perhaps the analysis
+// can find that only $A', a particular subtype, can appear there in practice,
+// and not $A or any subtypes of $A', etc. Or, we may find that no type is
+// actually possible at a particular location, say if we can prove that the
+// casts on the way to that location allow nothing through. We can also find
+// that only a particular value is possible of that type.
+//
+// GUFA will infer constants and unreachability, and add those to the code. This
+// can increase code size if further optimizations are not run later like dead
+// code elimination and vacuum. The "optimizing" variant of this pass will run
+// such followup opts automatically in functions where we make changes, and so
+// it is useful if GUFA is run near the end of the optimization pipeline.
+//
+// TODO: GUFA + polymorphic devirtualization + traps-never-happen. If we see
+//       that the possible call targets are {A, B, C}, and GUFA info lets us
+//       prove that A, C will trap if called - say, if they cast the first
+//       parameter to something GUFA proved it cannot be - then we can ignore
+//       them, and devirtualize to a call to B.
+//
+
+#include "ir/drop.h"
+#include "ir/eh-utils.h"
+#include "ir/possible-contents.h"
+#include "ir/properties.h"
+#include "ir/utils.h"
+#include "pass.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+struct GUFAOptimizer
+  : public WalkerPass<
+      PostWalker<GUFAOptimizer, UnifiedExpressionVisitor<GUFAOptimizer>>> {
+  bool isFunctionParallel() override { return true; }
+
+  ContentOracle& oracle;
+  bool optimizing;
+
+  GUFAOptimizer(ContentOracle& oracle, bool optimizing)
+    : oracle(oracle), optimizing(optimizing) {}
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<GUFAOptimizer>(oracle, optimizing);
+  }
+
+  bool optimized = false;
+
+  // As we optimize, we replace expressions and create new ones. For new ones
+  // we can infer their contents based on what they replaced, e.g., if we
+  // replaced a local.get with a const, then the PossibleContents of the const
+  // are the same as the local.get (in this simple example, we could also just
+  // infer them from the const itself, of course). Rather than update the
+  // ContentOracle with new contents, which is a shared object among threads,
+  // each function-parallel worker stores a map of new things it created to the
+  // contents for them.
+  std::unordered_map<Expression*, PossibleContents> newContents;
+
+  Expression* replaceCurrent(Expression* rep) {
+    newContents[rep] = oracle.getContents(getCurrent());
+
+    return WalkerPass<
+      PostWalker<GUFAOptimizer,
+                 UnifiedExpressionVisitor<GUFAOptimizer>>>::replaceCurrent(rep);
+  }
+
+  const PossibleContents getContents(Expression* curr) {
+    // If this is something we added ourselves, use that; otherwise the info is
+    // in the oracle.
+    if (auto iter = newContents.find(curr); iter != newContents.end()) {
+      return iter->second;
+    }
+
+    return oracle.getContents(curr);
+  }
+
+  void visitExpression(Expression* curr) {
+    // Skip things we can't improve in any way.
+    auto type = curr->type;
+    if (type == Type::unreachable || type == Type::none ||
+        Properties::isConstantExpression(curr)) {
+      return;
+    }
+
+    if (type.isTuple()) {
+      // TODO: tuple types.
+      return;
+    }
+
+    if (type.isRef() && (getTypeSystem() != TypeSystem::Nominal &&
+                         getTypeSystem() != TypeSystem::Isorecursive)) {
+      // Without type info we can't analyze subtypes, so we cannot infer
+      // anything about refs.
+      return;
+    }
+
+    // Ok, this is an interesting location that we might optimize. See what the
+    // oracle says is possible there.
+    auto contents = getContents(curr);
+
+    auto& options = getPassOptions();
+    auto& wasm = *getModule();
+    Builder builder(wasm);
+
+    if (contents.getType() == Type::unreachable) {
+      // This cannot contain any possible value at all. It must be unreachable
+      // code.
+      replaceCurrent(getDroppedChildrenAndAppend(
+        curr, wasm, options, builder.makeUnreachable()));
+      optimized = true;
+      return;
+    }
+
+    // This is reachable. Check if we can emit something optimized for it.
+    // TODO: can we handle more general things here too?
+    if (!contents.canMakeExpression()) {
+      return;
+    }
+
+    if (contents.isNull() && curr->type.isNullable()) {
+      // Null values are all identical, so just fix up the type here if we need
+      // to (the null's type might not fit in this expression, if it passed
+      // through casts).
+      if (!Type::isSubType(contents.getType(), curr->type)) {
+        contents = PossibleContents::literal(
+          Literal::makeNull(curr->type.getHeapType()));
+      }
+
+      // Note that if curr's type is *not* nullable, then the code will trap at
+      // runtime (the null must arrive through a cast that will trap). We handle
+      // that below, so we don't need to think about it here.
+
+      // TODO: would emitting a more specific null be useful when valid?
+    }
+
+    auto* c = contents.makeExpression(wasm);
+
+    // We can only place the constant value here if it has the right type. For
+    // example, a block may return (ref any), that is, not allow a null, but in
+    // practice only a null may flow there if it goes through casts that will
+    // trap at runtime.
+    // TODO: GUFA should eventually do this, but it will require it properly
+    //       filtering content not just on ref.cast as it does now, but also
+    //       ref.as etc. Once it does those we could assert on the type being
+    //       valid here.
+    if (Type::isSubType(c->type, curr->type)) {
+      replaceCurrent(getDroppedChildrenAndAppend(curr, wasm, options, c));
+      optimized = true;
+    } else {
+      // The type is not compatible: we cannot place |c| in this location, even
+      // though we have proven it is the only value possible here.
+      if (Properties::isConstantExpression(c)) {
+        // The type is not compatible and this is a simple constant expression
+        // like a ref.func. That means this code must be unreachable. (See below
+        // for the case of a non-constant.)
+        replaceCurrent(getDroppedChildrenAndAppend(
+          curr, wasm, options, builder.makeUnreachable()));
+        optimized = true;
+      } else {
+        // This is not a constant expression, but we are certain it is the right
+        // value. Atm the only such case we handle is a global.get of an
+        // immutable global. We don't know what the value will be, nor its
+        // specific type, but we do know that a global.get will get that value
+        // properly. However, in this case it does not have the right type for
+        // this location. That can happen since the global.get does not have
+        // exactly the proper type for the contents: the global.get might be
+        // nullable, for example, even though the contents are not actually a
+        // null. For example, consider what happens here:
+        //
+        //  (global $foo (ref null any) (struct.new $Foo))
+        //  ..
+        //    (ref.as_non_null
+        //      (global.get $foo))
+        //
+        // We create a $Foo in the global $foo, so its value is not a null. But
+        // the global's type is nullable, so the global.get's type will be as
+        // well. When we get to the ref.as_non_null, we then want to replace it
+        // with a global.get - in fact that's what its child already is, showing
+        // it is the right content for it - but that global.get would not have a
+        // non-nullable type like a ref.as_non_null must have, so we cannot
+        // simply replace it.
+        //
+        // For now, do nothing here, but in some cases we could probably
+        // optimize (e.g. by adding a ref.as_non_null in the example) TODO
+        assert(c->is<GlobalGet>());
+      }
+    }
+  }
+
+  void visitRefEq(RefEq* curr) {
+    if (curr->type == Type::unreachable) {
+      // Leave this for DCE.
+      return;
+    }
+
+    auto leftContents = getContents(curr->left);
+    auto rightContents = getContents(curr->right);
+
+    if (!PossibleContents::haveIntersection(leftContents, rightContents)) {
+      // The contents prove the two sides cannot contain the same reference, so
+      // we infer 0.
+      //
+      // Note that this is fine even if one of the sides is None. In that case,
+      // no value is possible there, and the intersection is empty, so we will
+      // get here and emit a 0. That 0 will never be reached as the None child
+      // will be turned into an unreachable, so it does not cause any problem.
+      auto* result = Builder(*getModule()).makeConst(Literal(int32_t(0)));
+      replaceCurrent(getDroppedChildrenAndAppend(
+        curr, *getModule(), getPassOptions(), result));
+    }
+  }
+
+  void visitRefTest(RefTest* curr) {
+    if (curr->type == Type::unreachable) {
+      // Leave this for DCE.
+      return;
+    }
+
+    auto refContents = getContents(curr->ref);
+    auto refType = refContents.getType();
+    if (refType.isRef()) {
+      // We have some knowledge of the type here. Use that to optimize: RefTest
+      // returns 1 if the input is of a subtype of the intended type, that is,
+      // we are looking for a type in that cone of types. (Note that we use a
+      // non-nullable cone since only a non-null can pass the test.)
+      auto intendedContents =
+        PossibleContents::fullConeType(Type(curr->intendedType, NonNullable));
+
+      auto optimize = [&](int32_t result) {
+        auto* last = Builder(*getModule()).makeConst(Literal(int32_t(result)));
+        replaceCurrent(getDroppedChildrenAndAppend(
+          curr, *getModule(), getPassOptions(), last));
+      };
+
+      if (!PossibleContents::haveIntersection(refContents, intendedContents)) {
+        optimize(0);
+      } else if (PossibleContents::isSubContents(refContents,
+                                                 intendedContents)) {
+        optimize(1);
+      }
+    }
+  }
+
+  // TODO: If an instruction would trap on null, like struct.get, we could
+  //       remove it here if it has no possible contents and if we are in
+  //       traps-never-happen mode (that is, we'd have proven it can only trap,
+  //       but we assume no traps happen, so it must be dead code). That info
+  //       is present in OptimizeInstructions where it removes redundant
+  //       ref.as_non_null (it removes them because it knows that the parent
+  //       will trap on null anyhow), so maybe there is a way to share that
+  //       information about parents.
+
+  void visitFunction(Function* func) {
+    if (!optimized) {
+      return;
+    }
+
+    // Optimization may introduce more unreachables, which we need to
+    // propagate.
+    ReFinalize().walkFunctionInModule(func, getModule());
+
+    // We may add blocks around pops, which we must fix up.
+    EHUtils::handleBlockNestedPops(func, *getModule());
+
+    // If we are in "optimizing" mode, we'll also run some more passes on this
+    // function that we just optimized. If not, leave now.
+    if (!optimizing) {
+      return;
+    }
+
+    PassRunner runner(getPassRunner());
+    // New unreachables we added have created dead code we can remove. If we do
+    // not do this, then running GUFA repeatedly can actually increase code size
+    // (by adding multiple unneeded unreachables).
+    runner.add("dce");
+    // New drops we added allow us to remove more unused code and values. As
+    // with unreachables, without a vacuum we may increase code size as in
+    // nested expressions we may apply the same value multiple times:
+    //
+    //  (block $out
+    //   (block $in
+    //    (i32.const 10)))
+    //
+    // In each of the blocks we'll infer the value must be 10, so we'll end up
+    // with this repeating code:
+    //
+    //  (block ;; a new block just to drop the old outer block
+    //   (drop
+    //    (block $out
+    //     (drop
+    //      (block $in
+    //       (i32.const 10)
+    //      )
+    //     )
+    //     (i32.const 10)
+    //    )
+    //   )
+    //   (i32.const 10)
+    //  )
+    runner.add("vacuum");
+    runner.runOnFunction(func);
+  }
+};
+
+struct GUFAPass : public Pass {
+  bool optimizing;
+
+  GUFAPass(bool optimizing) : optimizing(optimizing) {}
+
+  void run(Module* module) override {
+    ContentOracle oracle(*module);
+    GUFAOptimizer(oracle, optimizing).run(getPassRunner(), module);
+  }
+};
+
+} // anonymous namespace
+
+Pass* createGUFAPass() { return new GUFAPass(false); }
+Pass* createGUFAOptimizingPass() { return new GUFAPass(true); }
+
+} // namespace wasm
diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp
new file mode 100644
index 0000000..6ed2d41
--- /dev/null
+++ b/src/passes/GlobalEffects.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Handle the computation of global effects. The effects are stored on the
+// PassOptions structure; see more details there.
+//
+
+#include "ir/module-utils.h"
+#include "pass.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct GenerateGlobalEffects : public Pass {
+  void run(Module* module) override {
+    // TODO: Full transitive closure of effects. For now, we just look at each
+    //       function by itself.
+
+    auto& funcEffectsMap = getPassOptions().funcEffectsMap;
+
+    // First, clear any previous effects.
+    funcEffectsMap.reset();
+
+    // When we find useful effects, we'll save them. If we can't find anything,
+    // the final map we emit will not have an entry there at all.
+    using PossibleEffects = std::unique_ptr<EffectAnalyzer>;
+
+    ModuleUtils::ParallelFunctionAnalysis<PossibleEffects> analysis(
+      *module, [&](Function* func, PossibleEffects& storedEffects) {
+        if (func->imported()) {
+          // Imports can do anything, so we need to assume the worst anyhow,
+          // which is the same as not specifying any effects for them in the
+          // map.
+          return;
+        }
+
+        // Gather the effects.
+        auto effects =
+          std::make_unique<EffectAnalyzer>(getPassOptions(), *module, func);
+
+        // If the body has a call, give up - that means we can't infer a more
+        // specific set of effects than the pessimistic case of just assuming
+        // any call to here is an arbitrary call. (See the TODO above for
+        // improvements.)
+        if (effects->calls) {
+          return;
+        }
+
+        // Save the useful effects we found.
+        storedEffects = std::move(effects);
+      });
+
+    // Generate the final data structure.
+    for (auto& [func, possibleEffects] : analysis.map) {
+      if (possibleEffects) {
+        // Only allocate a new funcEffectsMap if we actually have data for it
+        // (which might make later effect computation slightly faster, to
+        // quickly skip the funcEffectsMap code path).
+        if (!funcEffectsMap) {
+          funcEffectsMap = std::make_shared<FuncEffectsMap>();
+        }
+        funcEffectsMap->emplace(func->name, *possibleEffects);
+      }
+    }
+  }
+};
+
+struct DiscardGlobalEffects : public Pass {
+  void run(Module* module) override { getPassOptions().funcEffectsMap.reset(); }
+};
+
+Pass* createGenerateGlobalEffectsPass() { return new GenerateGlobalEffects(); }
+
+Pass* createDiscardGlobalEffectsPass() { return new DiscardGlobalEffects(); }
+
+} // namespace wasm
diff --git a/src/passes/GlobalRefining.cpp b/src/passes/GlobalRefining.cpp
index e43f8ef..f994d40 100644
--- a/src/passes/GlobalRefining.cpp
+++ b/src/passes/GlobalRefining.cpp
@@ -31,9 +31,16 @@ namespace wasm {
 namespace {
 
 struct GlobalRefining : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      Fatal() << "GlobalRefining requires nominal typing";
+  // Only modifies globals and global.get operations.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "GlobalRefining requires nominal/isorecursive typing";
     }
 
     // First, find all the global.sets.
@@ -99,13 +106,18 @@ struct GlobalRefining : public Pass {
     struct GetUpdater : public WalkerPass<PostWalker<GetUpdater>> {
       bool isFunctionParallel() override { return true; }
 
+      // Only modifies global.get operations.
+      bool requiresNonNullableLocalFixups() override { return false; }
+
       GlobalRefining& parent;
       Module& wasm;
 
       GetUpdater(GlobalRefining& parent, Module& wasm)
         : parent(parent), wasm(wasm) {}
 
-      GetUpdater* create() override { return new GetUpdater(parent, wasm); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<GetUpdater>(parent, wasm);
+      }
 
       // If we modify anything in a function then we must refinalize so that
       // types propagate outwards.
@@ -126,7 +138,7 @@ struct GlobalRefining : public Pass {
         }
       }
     };
-    GetUpdater(*this, *module).run(runner, module);
+    GetUpdater(*this, *module).run(getPassRunner(), module);
   }
 };
 
diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp
new file mode 100644
index 0000000..8bb4c0b
--- /dev/null
+++ b/src/passes/GlobalStructInference.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Finds types which are only created in assignments to immutable globals. For
+// such types we can replace a struct.get with a global.get when there is a
+// single possible global, or if there are two then with this pattern:
+//
+//  (struct.get $foo i
+//    (..ref..))
+//  =>
+//  (select
+//    (value1)
+//    (value2)
+//    (ref.eq
+//      (..ref..)
+//      (global.get $global1)))
+//
+// That is a valid transformation if there are only two struct.news of $foo, it
+// is created in two immutable globals $global1 and $global2, the field is
+// immutable, the values of field |i| in them are value1 and value2
+// respectively, and $foo has no subtypes. In that situation, the reference must
+// be one of those two, so we can compare the reference to the globals and pick
+// the right value there. (We can also handle subtypes, if we look at their
+// values as well, see below.)
+//
+// The benefit of this optimization is primarily in the case of constant values
+// that we can heavily optimize, like function references (constant function
+// refs let us inline, etc.). Function references cannot be directly compared,
+// so we cannot use ConstantFieldPropagation or such with an extension to
+// multiple values, as the select pattern shown above can't be used - it needs a
+// comparison. But we can compare structs, so if the function references are in
+// vtables, and the vtables follow the above pattern, then we can optimize.
+//
+// TODO: Only do the case with a select when shrinkLevel == 0?
+//
+
+#include "ir/find_all.h"
+#include "ir/module-utils.h"
+#include "ir/subtypes.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+struct GlobalStructInference : public Pass {
+  // Only modifies struct.get operations.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  // Maps optimizable struct types to the globals whose init is a struct.new of
+  // them. If a global is not present here, it cannot be optimized.
+  std::unordered_map<HeapType, std::vector<Name>> typeGlobals;
+
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "GlobalStructInference requires nominal/isorecursive typing";
+    }
+
+    // First, find all the information we need. We need to know which struct
+    // types are created in functions, because we will not be able to optimize
+    // those.
+
+    using HeapTypes = std::unordered_set<HeapType>;
+
+    ModuleUtils::ParallelFunctionAnalysis<HeapTypes> analysis(
+      *module, [&](Function* func, HeapTypes& types) {
+        if (func->imported()) {
+          return;
+        }
+
+        for (auto* structNew : FindAll<StructNew>(func->body).list) {
+          auto type = structNew->type;
+          if (type.isRef()) {
+            types.insert(type.getHeapType());
+          }
+        }
+      });
+
+    // We cannot optimize types that appear in a struct.new in a function, which
+    // we just collected and merge now.
+    HeapTypes unoptimizable;
+
+    for (auto& [func, types] : analysis.map) {
+      for (auto type : types) {
+        unoptimizable.insert(type);
+      }
+    }
+
+    // Process the globals.
+    for (auto& global : module->globals) {
+      if (global->imported()) {
+        continue;
+      }
+
+      // We cannot optimize a type that appears in a non-toplevel location in a
+      // global init.
+      for (auto* structNew : FindAll<StructNew>(global->init).list) {
+        auto type = structNew->type;
+        if (type.isRef() && structNew != global->init) {
+          unoptimizable.insert(type.getHeapType());
+        }
+      }
+
+      if (!global->init->type.isRef()) {
+        continue;
+      }
+
+      auto type = global->init->type.getHeapType();
+
+      // We cannot optimize mutable globals.
+      if (global->mutable_) {
+        unoptimizable.insert(type);
+        continue;
+      }
+
+      // Finally, if this is a struct.new then it is one we can optimize; note
+      // it.
+      if (global->init->is<StructNew>()) {
+        typeGlobals[type].push_back(global->name);
+      }
+    }
+
+    // A struct.get might also read from any of the subtypes. As a result, an
+    // unoptimizable type makes all its supertypes unoptimizable as well.
+    // TODO: this could be specific per field (and not all supers have all
+    //       fields)
+    for (auto type : unoptimizable) {
+      while (1) {
+        typeGlobals.erase(type);
+        auto super = type.getSuperType();
+        if (!super) {
+          break;
+        }
+        type = *super;
+      }
+    }
+
+    // Similarly, propagate global names: if one type has [global1], then a get
+    // of any supertype might access that, so propagate to them.
+    auto typeGlobalsCopy = typeGlobals;
+    for (auto& [type, globals] : typeGlobalsCopy) {
+      auto curr = type;
+      while (1) {
+        auto super = curr.getSuperType();
+        if (!super) {
+          break;
+        }
+        curr = *super;
+        for (auto global : globals) {
+          typeGlobals[curr].push_back(global);
+        }
+      }
+    }
+
+    if (typeGlobals.empty()) {
+      // We found nothing we can optimize.
+      return;
+    }
+
+    // The above loop on typeGlobalsCopy is on an unsorted data structure, and
+    // that can lead to nondeterminism in typeGlobals. Sort the vectors there to
+    // ensure determinism.
+    for (auto& [type, globals] : typeGlobals) {
+      std::sort(globals.begin(), globals.end());
+    }
+
+    // Optimize based on the above.
+    struct FunctionOptimizer
+      : public WalkerPass<PostWalker<FunctionOptimizer>> {
+      bool isFunctionParallel() override { return true; }
+
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<FunctionOptimizer>(parent);
+      }
+
+      FunctionOptimizer(GlobalStructInference& parent) : parent(parent) {}
+
+      void visitStructGet(StructGet* curr) {
+        auto type = curr->ref->type;
+        if (type == Type::unreachable) {
+          return;
+        }
+
+        auto iter = parent.typeGlobals.find(type.getHeapType());
+        if (iter == parent.typeGlobals.end()) {
+          return;
+        }
+
+        // The field must be immutable.
+        auto fieldIndex = curr->index;
+        auto& field = type.getHeapType().getStruct().fields[fieldIndex];
+        if (field.mutable_ == Mutable) {
+          return;
+        }
+
+        const auto& globals = iter->second;
+        if (globals.size() == 0) {
+          return;
+        }
+
+        auto& wasm = *getModule();
+        Builder builder(wasm);
+
+        if (globals.size() == 1) {
+          // Leave it to other passes to infer the constant value of the field,
+          // if there is one: just change the reference to the global, which
+          // will unlock those other optimizations. Note we must trap if the ref
+          // is null, so add RefAsNonNull here.
+          auto global = globals[0];
+          curr->ref = builder.makeSequence(
+            builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)),
+            builder.makeGlobalGet(global, wasm.getGlobal(globals[0])->type));
+          return;
+        }
+
+        // We are looking for the case where we can pick between two values
+        // using a single comparison. More than two values, or more than a
+        // single comparison, add tradeoffs that may not be worth it, and a
+        // single value (or no value) is already handled by other passes.
+        //
+        // That situation may involve more than two globals. For example we may
+        // have three relevant globals, but two may have the same value. In that
+        // case we can compare against the third:
+        //
+        //  $global0: (struct.new $Type (i32.const 42))
+        //  $global1: (struct.new $Type (i32.const 42))
+        //  $global2: (struct.new $Type (i32.const 1337))
+        //
+        // (struct.get $Type (ref))
+        //   =>
+        // (select
+        //   (i32.const 1337)
+        //   (i32.const 42)
+        //   (ref.eq (ref) $global2))
+
+        // Find the constant values and which globals correspond to them.
+        // TODO: SmallVectors?
+        std::vector<Literal> values;
+        std::vector<std::vector<Name>> globalsForValue;
+
+        // Check if the relevant fields contain constants.
+        auto fieldType = field.type;
+        for (Index i = 0; i < globals.size(); i++) {
+          Name global = globals[i];
+          auto* structNew = wasm.getGlobal(global)->init->cast<StructNew>();
+          Literal value;
+          if (structNew->isWithDefault()) {
+            value = Literal::makeZero(fieldType);
+          } else {
+            auto* init = structNew->operands[fieldIndex];
+            if (!Properties::isConstantExpression(init)) {
+              // Non-constant; give up entirely.
+              return;
+            }
+            value = Properties::getLiteral(init);
+          }
+
+          // Process the current value, comparing it against the previous.
+          auto found = std::find(values.begin(), values.end(), value);
+          if (found == values.end()) {
+            // This is a new value.
+            assert(values.size() <= 2);
+            if (values.size() == 2) {
+              // Adding this value would mean we have too many, so give up.
+              return;
+            }
+            values.push_back(value);
+            globalsForValue.push_back({global});
+          } else {
+            // This is an existing value.
+            Index index = found - values.begin();
+            globalsForValue[index].push_back(global);
+          }
+        }
+
+        // We have some globals (at least 2), and so must have at least one
+        // value. And we have already exited if we have more than 2 values (see
+        // the early return above) so that only leaves 1 and 2.
+        if (values.size() == 1) {
+          // The case of 1 value is simple: trap if the ref is null, and
+          // otherwise return the value.
+          replaceCurrent(builder.makeSequence(
+            builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)),
+            builder.makeConstantExpression(values[0])));
+          return;
+        }
+        assert(values.size() == 2);
+
+        // We have two values. Check that we can pick between them using a
+        // single comparison. While doing so, ensure that the index we can check
+        // on is 0, that is, the first value has a single global.
+        if (globalsForValue[0].size() == 1) {
+          // The checked global is already in index 0.
+        } else if (globalsForValue[1].size() == 1) {
+          std::swap(values[0], values[1]);
+          std::swap(globalsForValue[0], globalsForValue[1]);
+        } else {
+          // Both indexes have more than one option, so we'd need more than one
+          // comparison. Give up.
+          return;
+        }
+
+        // Excellent, we can optimize here! Emit a select.
+        //
+        // Note that we must trap on null, so add a ref.as_non_null here.
+        auto checkGlobal = globalsForValue[0][0];
+        replaceCurrent(builder.makeSelect(
+          builder.makeRefEq(builder.makeRefAs(RefAsNonNull, curr->ref),
+                            builder.makeGlobalGet(
+                              checkGlobal, wasm.getGlobal(checkGlobal)->type)),
+          builder.makeConstantExpression(values[0]),
+          builder.makeConstantExpression(values[1])));
+      }
+
+    private:
+      GlobalStructInference& parent;
+    };
+
+    FunctionOptimizer(*this).run(getPassRunner(), module);
+  }
+};
+
+} // anonymous namespace
+
+Pass* createGlobalStructInferencePass() { return new GlobalStructInference(); }
+
+} // namespace wasm
diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp
index 9b2a1f2..95d8859 100644
--- a/src/passes/GlobalTypeOptimization.cpp
+++ b/src/passes/GlobalTypeOptimization.cpp
@@ -24,6 +24,7 @@
 
 #include "ir/effects.h"
 #include "ir/localize.h"
+#include "ir/ordering.h"
 #include "ir/struct-utils.h"
 #include "ir/subtypes.h"
 #include "ir/type-updating.h"
@@ -61,8 +62,9 @@ struct FieldInfo {
 
 struct FieldInfoScanner
   : public StructUtils::StructScanner<FieldInfo, FieldInfoScanner> {
-  Pass* create() override {
-    return new FieldInfoScanner(functionNewInfos, functionSetGetInfos);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FieldInfoScanner>(functionNewInfos,
+                                              functionSetGetInfos);
   }
 
   FieldInfoScanner(
@@ -111,17 +113,21 @@ struct GlobalTypeOptimization : public Pass {
   static const Index RemovedField = Index(-1);
   std::unordered_map<HeapType, std::vector<Index>> indexesAfterRemovals;
 
-  void run(PassRunner* runner, Module* module) override {
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      Fatal() << "GlobalTypeOptimization requires nominal typing";
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "GlobalTypeOptimization requires nominal/isorecursive typing";
     }
 
     // Find and analyze struct operations inside each function.
     StructUtils::FunctionStructValuesMap<FieldInfo> functionNewInfos(*module),
       functionSetGetInfos(*module);
     FieldInfoScanner scanner(functionNewInfos, functionSetGetInfos);
-    scanner.run(runner, module);
-    scanner.runOnModuleCode(runner, module);
+    scanner.run(getPassRunner(), module);
+    scanner.runOnModuleCode(getPassRunner(), module);
 
     // Combine the data from the functions.
     functionSetGetInfos.combineInto(combinedSetGetInfos);
@@ -237,7 +243,7 @@ struct GlobalTypeOptimization : public Pass {
     // that we can identify, and only after this should we update all the types
     // throughout the module.)
     if (!indexesAfterRemovals.empty()) {
-      removeFieldsInInstructions(runner, *module);
+      removeFieldsInInstructions(*module);
     }
 
     // Update the types in the entire module.
@@ -310,7 +316,7 @@ struct GlobalTypeOptimization : public Pass {
 
   // After updating the types to remove certain fields, we must also remove
   // them from struct instructions.
-  void removeFieldsInInstructions(PassRunner* runner, Module& wasm) {
+  void removeFieldsInInstructions(Module& wasm) {
     struct FieldRemover : public WalkerPass<PostWalker<FieldRemover>> {
       bool isFunctionParallel() override { return true; }
 
@@ -318,7 +324,9 @@ struct GlobalTypeOptimization : public Pass {
 
       FieldRemover(GlobalTypeOptimization& parent) : parent(parent) {}
 
-      FieldRemover* create() override { return new FieldRemover(parent); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<FieldRemover>(parent);
+      }
 
       void visitStructNew(StructNew* curr) {
         if (curr->type == Type::unreachable) {
@@ -362,7 +370,6 @@ struct GlobalTypeOptimization : public Pass {
           block->list.push_back(curr);
           block->finalize(curr->type);
           replaceCurrent(block);
-          addedLocals = true;
         }
 
         // Remove the unneeded operands.
@@ -389,12 +396,18 @@ struct GlobalTypeOptimization : public Pass {
           // Map to the new index.
           curr->index = newIndex;
         } else {
-          // This field was removed, so just emit drops of our children (plus a
-          // trap if the input is null).
+          // This field was removed, so just emit drops of our children, plus a
+          // trap if the ref is null. Note that we must preserve the order of
+          // operations here: the trap on a null ref happens after the value,
+          // which might have side effects.
           Builder builder(*getModule());
-          replaceCurrent(builder.makeSequence(
-            builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)),
-            builder.makeDrop(curr->value)));
+          auto flipped = getResultOfFirst(curr->ref,
+                                          builder.makeDrop(curr->value),
+                                          getFunction(),
+                                          getModule(),
+                                          getPassOptions());
+          replaceCurrent(
+            builder.makeDrop(builder.makeRefAs(RefAsNonNull, flipped)));
         }
       }
 
@@ -409,15 +422,7 @@ struct GlobalTypeOptimization : public Pass {
         curr->index = newIndex;
       }
 
-      void visitFunction(Function* curr) {
-        if (addedLocals) {
-          TypeUpdating::handleNonDefaultableLocals(curr, *getModule());
-        }
-      }
-
     private:
-      bool addedLocals = false;
-
       Index getNewIndex(HeapType type, Index index) {
         auto iter = parent.indexesAfterRemovals.find(type);
         if (iter == parent.indexesAfterRemovals.end()) {
@@ -432,8 +437,8 @@ struct GlobalTypeOptimization : public Pass {
     };
 
     FieldRemover remover(*this);
-    remover.run(runner, &wasm);
-    remover.runOnModuleCode(runner, &wasm);
+    remover.run(getPassRunner(), &wasm);
+    remover.runOnModuleCode(getPassRunner(), &wasm);
   }
 };
 
diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp
index 19259e8..9129810 100644
--- a/src/passes/Heap2Local.cpp
+++ b/src/passes/Heap2Local.cpp
@@ -43,9 +43,8 @@
 //
 //     ;; Allocate a boxed integer of 42 and save the reference to it.
 //     (local.set $ref
-//      (struct.new_with_rtt $boxed-int
+//      (struct.new $boxed-int
 //       (i32.const 42)
-//       (rtt.canon $boxed-int)
 //      )
 //     )
 //
@@ -182,8 +181,6 @@ struct Heap2LocalOptimizer {
   Parents parents;
   BranchUtils::BranchTargets branchTargets;
 
-  bool optimized = false;
-
   Heap2LocalOptimizer(Function* func,
                       Module* module,
                       const PassOptions& passOptions)
@@ -204,9 +201,7 @@ struct Heap2LocalOptimizer {
         continue;
       }
 
-      if (convertToLocals(allocation)) {
-        optimized = true;
-      }
+      convertToLocals(allocation);
     }
   }
 
@@ -407,10 +402,6 @@ struct Heap2LocalOptimizer {
         }
       }
 
-      // Drop the RTT (as it may have side effects; leave it to other passes).
-      if (allocation->rtt) {
-        contents.push_back(builder.makeDrop(allocation->rtt));
-      }
       // Replace the allocation with a null reference. This changes the type
       // from non-nullable to nullable, but as we optimize away the code that
       // the allocation reaches, we will handle that.
@@ -478,7 +469,7 @@ struct Heap2LocalOptimizer {
 
   // Analyze an allocation to see if we can convert it from a heap allocation to
   // locals.
-  bool convertToLocals(StructNew* allocation) {
+  void convertToLocals(StructNew* allocation) {
     Rewriter rewriter(allocation, func, module);
 
     // A queue of flows from children to parents. When something is in the queue
@@ -507,13 +498,13 @@ struct Heap2LocalOptimizer {
       // different call to this function and use a different queue (any overlap
       // between calls would prove non-exclusivity).
       if (!seen.emplace(parent).second) {
-        return false;
+        return;
       }
 
       switch (getParentChildInteraction(parent, child)) {
         case ParentChildInteraction::Escapes: {
           // If the parent may let us escape then we are done.
-          return false;
+          return;
         }
         case ParentChildInteraction::FullyConsumes: {
           // If the parent consumes us without letting us escape then all is
@@ -529,7 +520,7 @@ struct Heap2LocalOptimizer {
         case ParentChildInteraction::Mixes: {
           // Our allocation is not used exclusively via the parent, as other
           // values are mixed with it. Give up.
-          return false;
+          return;
         }
       }
 
@@ -563,13 +554,11 @@ struct Heap2LocalOptimizer {
 
     // We finished the loop over the flows. Do the final checks.
     if (!getsAreExclusiveToSets(rewriter.sets)) {
-      return false;
+      return;
     }
 
     // We can do it, hurray!
     rewriter.applyOptimization();
-
-    return true;
   }
 
   ParentChildInteraction getParentChildInteraction(Expression* parent,
@@ -743,7 +732,9 @@ struct Heap2LocalOptimizer {
 struct Heap2Local : public WalkerPass<PostWalker<Heap2Local>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new Heap2Local(); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<Heap2Local>();
+  }
 
   void doWalkFunction(Function* func) {
     // Multiple rounds of optimization may work in theory, as once we turn one
@@ -755,9 +746,7 @@ struct Heap2Local : public WalkerPass<PostWalker<Heap2Local>> {
     // vacuum, in particular, to optimize such nested allocations.
     // TODO Consider running multiple iterations here, and running vacuum in
     //      between them.
-    if (Heap2LocalOptimizer(func, getModule(), getPassOptions()).optimized) {
-      TypeUpdating::handleNonDefaultableLocals(func, *getModule());
-    }
+    Heap2LocalOptimizer(func, getModule(), getPassOptions());
   }
 };
 
diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp
index 7a07d94..aee65c2 100644
--- a/src/passes/I64ToI32Lowering.cpp
+++ b/src/passes/I64ToI32Lowering.cpp
@@ -22,13 +22,13 @@
 //
 
 #include "abi/js.h"
-#include "emscripten-optimizer/istring.h"
 #include "ir/flat.h"
 #include "ir/iteration.h"
 #include "ir/memory-utils.h"
 #include "ir/module-utils.h"
 #include "ir/names.h"
 #include "pass.h"
+#include "support/istring.h"
 #include "support/name.h"
 #include "wasm-builder.h"
 #include "wasm.h"
@@ -36,7 +36,7 @@
 
 namespace wasm {
 
-static Name makeHighName(Name n) { return std::string(n.c_str()) + "$hi"; }
+static Name makeHighName(Name n) { return n.toString() + "$hi"; }
 
 struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
   struct TempVar {
@@ -99,7 +99,9 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
   // TODO: allow module-level transformations in parallel passes
   bool isFunctionParallel() override { return false; }
 
-  Pass* create() override { return new I64ToI32Lowering; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<I64ToI32Lowering>();
+  }
 
   void doWalkModule(Module* module) {
     if (!builder) {
@@ -261,7 +263,8 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
     // If this was to an import, we need to call the legal version. This assumes
     // that legalize-js-interface has been run before.
     if (fixedCall && getModule()->getFunction(fixedCall->target)->imported()) {
-      fixedCall->target = std::string("legalfunc$") + fixedCall->target.str;
+      fixedCall->target =
+        std::string("legalfunc$") + fixedCall->target.toString();
       return;
     }
   }
@@ -386,7 +389,8 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
                           curr->offset + 4,
                           std::min(uint32_t(curr->align), uint32_t(4)),
                           builder->makeLocalGet(ptrTemp, Type::i32),
-                          Type::i32));
+                          Type::i32,
+                          curr->memory));
     } else if (curr->signed_) {
       loadHigh = builder->makeLocalSet(
         highBits,
@@ -432,7 +436,8 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
                            std::min(uint32_t(curr->align), uint32_t(4)),
                            builder->makeLocalGet(ptrTemp, Type::i32),
                            builder->makeLocalGet(highBits, Type::i32),
-                           Type::i32);
+                           Type::i32,
+                           curr->memory);
       replaceCurrent(builder->blockify(setPtr, curr, storeHigh));
     }
   }
@@ -539,6 +544,39 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
     replaceCurrent(result);
   }
 
+  void lowerExtendSInt64(Unary* curr) {
+    TempVar highBits = getTemp();
+    TempVar lowBits = getTemp();
+
+    // free the temp var
+    fetchOutParam(curr->value);
+
+    Expression* lowValue = curr->value;
+    switch (curr->op) {
+      case ExtendS8Int64:
+        lowValue = builder->makeUnary(ExtendS8Int32, lowValue);
+        break;
+      case ExtendS16Int64:
+        lowValue = builder->makeUnary(ExtendS16Int32, lowValue);
+        break;
+      default:
+        break;
+    }
+
+    LocalSet* setLow = builder->makeLocalSet(lowBits, lowValue);
+    LocalSet* setHigh = builder->makeLocalSet(
+      highBits,
+      builder->makeBinary(ShrSInt32,
+                          builder->makeLocalGet(lowBits, Type::i32),
+                          builder->makeConst(int32_t(31))));
+
+    Block* result = builder->blockify(
+      setLow, setHigh, builder->makeLocalGet(lowBits, Type::i32));
+
+    setOutParam(result, std::move(highBits));
+    replaceCurrent(result);
+  }
+
   void lowerWrapInt64(Unary* curr) {
     // free the temp var
     fetchOutParam(curr->value);
@@ -561,7 +599,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
                         Type::i32));
     setOutParam(result, std::move(highBits));
     replaceCurrent(result);
-    MemoryUtils::ensureExists(getModule()->memory);
+    MemoryUtils::ensureExists(getModule());
     ABI::wasm2js::ensureHelpers(getModule());
   }
 
@@ -579,7 +617,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
                         Type::none),
       builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_F64, {}, Type::f64));
     replaceCurrent(result);
-    MemoryUtils::ensureExists(getModule()->memory);
+    MemoryUtils::ensureExists(getModule());
     ABI::wasm2js::ensureHelpers(getModule());
   }
 
@@ -845,6 +883,9 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
       case ConvertUInt64ToFloat32:
       case ConvertUInt64ToFloat64:
       case ReinterpretInt64:
+      case ExtendS8Int64:
+      case ExtendS16Int64:
+      case ExtendS32Int64:
         return true;
       default:
         return false;
@@ -895,6 +936,11 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
       case ConvertUInt64ToFloat64:
         lowerConvertIntToFloat(curr);
         break;
+      case ExtendS8Int64:
+      case ExtendS16Int64:
+      case ExtendS32Int64:
+        lowerExtendSInt64(curr);
+        break;
       case PopcntInt64:
         WASM_UNREACHABLE("i64.popcnt should already be removed");
       default:
diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp
index 21c06e5..0c95dc4 100644
--- a/src/passes/Inlining.cpp
+++ b/src/passes/Inlining.cpp
@@ -32,6 +32,7 @@
 
 #include "ir/branch-utils.h"
 #include "ir/debug.h"
+#include "ir/drop.h"
 #include "ir/eh-utils.h"
 #include "ir/element-utils.h"
 #include "ir/literal-utils.h"
@@ -132,7 +133,7 @@ static bool canHandleParams(Function* func) {
   return true;
 }
 
-typedef std::unordered_map<Name, FunctionInfo> NameInfoMap;
+using NameInfoMap = std::unordered_map<Name, FunctionInfo>;
 
 struct FunctionInfoScanner
   : public WalkerPass<PostWalker<FunctionInfoScanner>> {
@@ -140,8 +141,8 @@ struct FunctionInfoScanner
 
   FunctionInfoScanner(NameInfoMap* infos) : infos(infos) {}
 
-  FunctionInfoScanner* create() override {
-    return new FunctionInfoScanner(infos);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FunctionInfoScanner>(infos);
   }
 
   void visitLoop(Loop* curr) {
@@ -209,7 +210,9 @@ struct Planner : public WalkerPass<PostWalker<Planner>> {
 
   Planner(InliningState* state) : state(state) {}
 
-  Planner* create() override { return new Planner(state); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<Planner>(state);
+  }
 
   void visitCall(Call* curr) {
     // plan to inline if we know this is valid to inline, and if the call is
@@ -249,6 +252,10 @@ struct Updater : public PostWalker<Updater> {
   Name returnName;
   bool isReturn;
   Builder* builder;
+  PassOptions& options;
+
+  Updater(PassOptions& options) : options(options) {}
+
   void visitReturn(Return* curr) {
     replaceCurrent(builder->makeBreak(returnName, curr->value));
   }
@@ -257,7 +264,7 @@ struct Updater : public PostWalker<Updater> {
   // achieve this, make the call a non-return call and add a break. This does
   // not cause unbounded stack growth because inlining and return calling both
   // avoid creating a new stack frame.
-  template<typename T> void handleReturnCall(T* curr, HeapType targetType) {
+  template<typename T> void handleReturnCall(T* curr, Type results) {
     if (isReturn) {
       // If the inlined callsite was already a return_call, then we can keep
       // return_calls in the inlined function rather than downgrading them.
@@ -267,7 +274,7 @@ struct Updater : public PostWalker<Updater> {
       return;
     }
     curr->isReturn = false;
-    curr->type = targetType.getSignature().results;
+    curr->type = results;
     if (curr->type.isConcrete()) {
       replaceCurrent(builder->makeBreak(returnName, curr));
     } else {
@@ -276,17 +283,25 @@ struct Updater : public PostWalker<Updater> {
   }
   void visitCall(Call* curr) {
     if (curr->isReturn) {
-      handleReturnCall(curr, module->getFunction(curr->target)->type);
+      handleReturnCall(curr, module->getFunction(curr->target)->getResults());
     }
   }
   void visitCallIndirect(CallIndirect* curr) {
     if (curr->isReturn) {
-      handleReturnCall(curr, curr->heapType);
+      handleReturnCall(curr, curr->heapType.getSignature().results);
     }
   }
   void visitCallRef(CallRef* curr) {
+    Type targetType = curr->target->type;
+    if (targetType.isNull()) {
+      // We don't know what type the call should return, but we can't leave it
+      // as a potentially-invalid return_call_ref, either.
+      replaceCurrent(getDroppedChildrenAndAppend(
+        curr, *module, options, Builder(*module).makeUnreachable()));
+      return;
+    }
     if (curr->isReturn) {
-      handleReturnCall(curr, curr->target->type.getHeapType());
+      handleReturnCall(curr, targetType.getHeapType().getSignature().results);
     }
   }
   void visitLocalGet(LocalGet* curr) {
@@ -299,15 +314,17 @@ struct Updater : public PostWalker<Updater> {
 
 // Core inlining logic. Modifies the outside function (adding locals as
 // needed), and returns the inlined code.
-static Expression*
-doInlining(Module* module, Function* into, const InliningAction& action) {
+static Expression* doInlining(Module* module,
+                              Function* into,
+                              const InliningAction& action,
+                              PassOptions& options) {
   Function* from = action.contents;
   auto* call = (*action.callSite)->cast<Call>();
   // Works for return_call, too
   Type retType = module->getFunction(call->target)->getResults();
   Builder builder(*module);
   auto* block = builder.makeBlock();
-  block->name = Name(std::string("__inlined_func$") + from->name.str);
+  block->name = Name(std::string("__inlined_func$") + from->name.toString());
   // In the unlikely event that the function already has a branch target with
   // this name, fix that up, as otherwise we can get unexpected capture of our
   // branches, that is, we could end up with this:
@@ -335,7 +352,7 @@ doInlining(Module* module, Function* into, const InliningAction& action) {
     *action.callSite = block;
   }
   // Prepare to update the inlined code's locals and other things.
-  Updater updater;
+  Updater updater(options);
   updater.module = module;
   updater.returnName = block->name;
   updater.isReturn = call->isReturn;
@@ -750,7 +767,8 @@ private:
     return ModuleUtils::copyFunction(
       func,
       *module,
-      Names::getValidFunctionName(*module, prefix + '$' + func->name.str));
+      Names::getValidFunctionName(*module,
+                                  prefix + '$' + func->name.toString()));
   }
 
   // Get the i-th item in a sequence of initial items in an expression. That is,
@@ -832,11 +850,9 @@ struct Inlining : public Pass {
 
   std::unique_ptr<FunctionSplitter> functionSplitter;
 
-  PassRunner* runner = nullptr;
   Module* module = nullptr;
 
-  void run(PassRunner* runner_, Module* module_) override {
-    runner = runner_;
+  void run(Module* module_) override {
     module = module_;
 
     // No point to do more iterations than the number of functions, as it means
@@ -919,9 +935,8 @@ struct Inlining : public Pass {
       infos[func->name];
     }
     {
-      PassRunner runner(module);
       FunctionInfoScanner scanner(&infos);
-      scanner.run(&runner, module);
+      scanner.run(getPassRunner(), module);
       scanner.walkModuleCode(module);
     }
     for (auto& ex : module->exports) {
@@ -935,9 +950,9 @@ struct Inlining : public Pass {
 
     // When optimizing heavily for size, we may potentially split functions in
     // order to inline parts of them.
-    if (runner->options.optimizeLevel >= 3 && !runner->options.shrinkLevel) {
+    if (getPassOptions().optimizeLevel >= 3 && !getPassOptions().shrinkLevel) {
       functionSplitter =
-        std::make_unique<FunctionSplitter>(module, runner->options);
+        std::make_unique<FunctionSplitter>(module, getPassOptions());
     }
   }
 
@@ -962,7 +977,7 @@ struct Inlining : public Pass {
       funcNames.push_back(func->name);
     }
     // find and plan inlinings
-    Planner(&state).run(runner, module);
+    Planner(&state).run(getPassRunner(), module);
     // perform inlinings TODO: parallelize
     std::unordered_map<Name, Index> inlinedUses; // how many uses we inlined
     // which functions were inlined into
@@ -1003,7 +1018,7 @@ struct Inlining : public Pass {
         action.contents = getActuallyInlinedFunction(action.contents);
 
         // Perform the inlining and update counts.
-        doInlining(module, func, action);
+        doInlining(module, func, action, getPassOptions());
         inlinedUses[inlinedName]++;
         inlinedInto.insert(func);
         assert(inlinedUses[inlinedName] <= infos[inlinedName].refs);
@@ -1015,7 +1030,7 @@ struct Inlining : public Pass {
       wasm::UniqueNameMapper::uniquify(func->body);
     }
     if (optimize && inlinedInto.size() > 0) {
-      OptUtils::optimizeAfterInlining(inlinedInto, module, runner);
+      OptUtils::optimizeAfterInlining(inlinedInto, module, getPassRunner());
     }
     // remove functions that we no longer need after inlining
     module->removeFunctions([&](Function* func) {
@@ -1028,7 +1043,7 @@ struct Inlining : public Pass {
 
   bool worthInlining(Name name) {
     // Check if the function itself is worth inlining as it is.
-    if (infos[name].worthInlining(runner->options)) {
+    if (infos[name].worthInlining(getPassOptions())) {
       return true;
     }
 
@@ -1051,7 +1066,7 @@ struct Inlining : public Pass {
   // are guaranteed to inline after this.
   Function* getActuallyInlinedFunction(Function* func) {
     // If we want to inline this function itself, do so.
-    if (infos[func->name].worthInlining(runner->options)) {
+    if (infos[func->name].worthInlining(getPassOptions())) {
       return func;
     }
 
@@ -1095,7 +1110,7 @@ static const char* MAIN = "main";
 static const char* ORIGINAL_MAIN = "__original_main";
 
 struct InlineMainPass : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     auto* main = module->getFunctionOrNull(MAIN);
     auto* originalMain = module->getFunctionOrNull(ORIGINAL_MAIN);
     if (!main || main->imported() || !originalMain ||
@@ -1117,7 +1132,8 @@ struct InlineMainPass : public Pass {
       // No call at all.
       return;
     }
-    doInlining(module, main, InliningAction(callSite, originalMain));
+    doInlining(
+      module, main, InliningAction(callSite, originalMain), getPassOptions());
   }
 };
 
diff --git a/src/passes/InstrumentLocals.cpp b/src/passes/InstrumentLocals.cpp
index 3bf23b6..311d4a2 100644
--- a/src/passes/InstrumentLocals.cpp
+++ b/src/passes/InstrumentLocals.cpp
@@ -57,10 +57,7 @@ Name get_f32("get_f32");
 Name get_f64("get_f64");
 Name get_v128("get_v128");
 Name get_funcref("get_funcref");
-Name get_anyref("get_anyref");
-Name get_eqref("get_eqref");
-Name get_i31ref("get_i31ref");
-Name get_dataref("get_dataref");
+Name get_externref("get_externref");
 
 Name set_i32("set_i32");
 Name set_i64("set_i64");
@@ -68,49 +65,42 @@ Name set_f32("set_f32");
 Name set_f64("set_f64");
 Name set_v128("set_v128");
 Name set_funcref("set_funcref");
-Name set_anyref("set_anyref");
-Name set_eqref("set_eqref");
-Name set_i31ref("set_i31ref");
-Name set_dataref("set_dataref");
+Name set_externref("set_externref");
 
 struct InstrumentLocals : public WalkerPass<PostWalker<InstrumentLocals>> {
   void visitLocalGet(LocalGet* curr) {
     Builder builder(*getModule());
     Name import;
-    TODO_SINGLE_COMPOUND(curr->type);
-    switch (curr->type.getBasic()) {
-      case Type::i32:
-        import = get_i32;
-        break;
-      case Type::i64:
-        return; // TODO
-      case Type::f32:
-        import = get_f32;
-        break;
-      case Type::f64:
-        import = get_f64;
-        break;
-      case Type::v128:
-        import = get_v128;
-        break;
-      case Type::funcref:
+    if (curr->type.isRef()) {
+      auto heapType = curr->type.getHeapType();
+      if (heapType == HeapType::func && curr->type.isNullable()) {
         import = get_funcref;
-        break;
-      case Type::anyref:
-        import = get_anyref;
-        break;
-      case Type::eqref:
-        import = get_eqref;
-        break;
-      case Type::i31ref:
-        import = get_i31ref;
-        break;
-      case Type::dataref:
-        import = get_dataref;
-        break;
-      case Type::none:
-      case Type::unreachable:
-        WASM_UNREACHABLE("unexpected type");
+      } else if (heapType == HeapType::ext && curr->type.isNullable()) {
+        import = get_externref;
+      } else {
+        WASM_UNREACHABLE("TODO: general reference types");
+      }
+    } else {
+      TODO_SINGLE_COMPOUND(curr->type);
+      switch (curr->type.getBasic()) {
+        case Type::i32:
+          import = get_i32;
+          break;
+        case Type::i64:
+          return; // TODO
+        case Type::f32:
+          import = get_f32;
+          break;
+        case Type::f64:
+          import = get_f64;
+          break;
+        case Type::v128:
+          import = get_v128;
+          break;
+        case Type::none:
+        case Type::unreachable:
+          WASM_UNREACHABLE("unexpected type");
+      }
     }
     replaceCurrent(builder.makeCall(import,
                                     {builder.makeConst(int32_t(id++)),
@@ -130,45 +120,41 @@ struct InstrumentLocals : public WalkerPass<PostWalker<InstrumentLocals>> {
     Builder builder(*getModule());
     Name import;
     auto type = curr->value->type;
-    if (type.isFunction() && type != Type::funcref) {
+    if (type.isFunction() && type.getHeapType() != HeapType::func) {
       // FIXME: support typed function references
       return;
     }
-    TODO_SINGLE_COMPOUND(curr->value->type);
-    switch (type.getBasic()) {
-      case Type::i32:
-        import = set_i32;
-        break;
-      case Type::i64:
-        return; // TODO
-      case Type::f32:
-        import = set_f32;
-        break;
-      case Type::f64:
-        import = set_f64;
-        break;
-      case Type::v128:
-        import = set_v128;
-        break;
-      case Type::funcref:
+    if (type.isRef()) {
+      auto heapType = type.getHeapType();
+      if (heapType == HeapType::func && type.isNullable()) {
         import = set_funcref;
-        break;
-      case Type::anyref:
-        import = set_anyref;
-        break;
-      case Type::eqref:
-        import = set_eqref;
-        break;
-      case Type::i31ref:
-        import = set_i31ref;
-        break;
-      case Type::dataref:
-        import = set_dataref;
-        break;
-      case Type::unreachable:
-        return; // nothing to do here
-      default:
-        WASM_UNREACHABLE("unexpected type");
+      } else if (heapType == HeapType::ext && type.isNullable()) {
+        import = set_externref;
+      } else {
+        WASM_UNREACHABLE("TODO: general reference types");
+      }
+    } else {
+      TODO_SINGLE_COMPOUND(curr->value->type);
+      switch (type.getBasic()) {
+        case Type::i32:
+          import = set_i32;
+          break;
+        case Type::i64:
+          return; // TODO
+        case Type::f32:
+          import = set_f32;
+          break;
+        case Type::f64:
+          import = set_f64;
+          break;
+        case Type::v128:
+          import = set_v128;
+          break;
+        case Type::unreachable:
+          return; // nothing to do here
+        case Type::none:
+          WASM_UNREACHABLE("unexpected type");
+      }
     }
     curr->value = builder.makeCall(import,
                                    {builder.makeConst(int32_t(id++)),
@@ -188,36 +174,13 @@ struct InstrumentLocals : public WalkerPass<PostWalker<InstrumentLocals>> {
     addImport(curr, set_f64, {Type::i32, Type::i32, Type::f64}, Type::f64);
 
     if (curr->features.hasReferenceTypes()) {
-      addImport(curr,
-                get_funcref,
-                {Type::i32, Type::i32, Type::funcref},
-                Type::funcref);
-      addImport(curr,
-                set_funcref,
-                {Type::i32, Type::i32, Type::funcref},
-                Type::funcref);
-      addImport(
-        curr, get_anyref, {Type::i32, Type::i32, Type::anyref}, Type::anyref);
-      addImport(
-        curr, set_anyref, {Type::i32, Type::i32, Type::anyref}, Type::anyref);
-      if (curr->features.hasGC()) {
-        addImport(
-          curr, get_eqref, {Type::i32, Type::i32, Type::eqref}, Type::eqref);
-        addImport(
-          curr, set_eqref, {Type::i32, Type::i32, Type::eqref}, Type::eqref);
-        addImport(
-          curr, get_i31ref, {Type::i32, Type::i32, Type::i31ref}, Type::i31ref);
-        addImport(
-          curr, set_i31ref, {Type::i32, Type::i32, Type::i31ref}, Type::i31ref);
-        addImport(curr,
-                  get_dataref,
-                  {Type::i32, Type::i32, Type::dataref},
-                  Type::dataref);
-        addImport(curr,
-                  set_dataref,
-                  {Type::i32, Type::i32, Type::dataref},
-                  Type::dataref);
-      }
+      Type func = Type(HeapType::func, Nullable);
+      Type ext = Type(HeapType::ext, Nullable);
+
+      addImport(curr, get_funcref, {Type::i32, Type::i32, func}, func);
+      addImport(curr, set_funcref, {Type::i32, Type::i32, func}, func);
+      addImport(curr, get_externref, {Type::i32, Type::i32, ext}, ext);
+      addImport(curr, set_externref, {Type::i32, Type::i32, ext}, ext);
     }
     if (curr->features.hasSIMD()) {
       addImport(curr, get_v128, {Type::i32, Type::i32, Type::v128}, Type::v128);
diff --git a/src/passes/InstrumentMemory.cpp b/src/passes/InstrumentMemory.cpp
index 073c788..486bc7c 100644
--- a/src/passes/InstrumentMemory.cpp
+++ b/src/passes/InstrumentMemory.cpp
@@ -54,7 +54,7 @@
 //
 // GC struct and array operations are similarly instrumented, but without their
 // pointers (which are references), and we only log MVP wasm types (i.e., not
-// references or rtts).
+// references).
 //
 
 #include "asmjs/shared-constants.h"
@@ -100,8 +100,9 @@ struct InstrumentMemory : public WalkerPass<PostWalker<InstrumentMemory>> {
   void visitLoad(Load* curr) {
     id++;
     Builder builder(*getModule());
-    auto indexType = getModule()->memory.indexType;
-    auto offset = builder.makeConstPtr(curr->offset.addr);
+    auto mem = getModule()->getMemory(curr->memory);
+    auto indexType = mem->indexType;
+    auto offset = builder.makeConstPtr(curr->offset.addr, indexType);
     curr->ptr = builder.makeCall(load_ptr,
                                  {builder.makeConst(int32_t(id)),
                                   builder.makeConst(int32_t(curr->bytes)),
@@ -132,8 +133,9 @@ struct InstrumentMemory : public WalkerPass<PostWalker<InstrumentMemory>> {
   void visitStore(Store* curr) {
     id++;
     Builder builder(*getModule());
-    auto indexType = getModule()->memory.indexType;
-    auto offset = builder.makeConstPtr(curr->offset.addr);
+    auto mem = getModule()->getMemory(curr->memory);
+    auto indexType = mem->indexType;
+    auto offset = builder.makeConstPtr(curr->offset.addr, indexType);
     curr->ptr = builder.makeCall(store_ptr,
                                  {builder.makeConst(int32_t(id)),
                                   builder.makeConst(int32_t(curr->bytes)),
@@ -246,7 +248,8 @@ struct InstrumentMemory : public WalkerPass<PostWalker<InstrumentMemory>> {
   }
 
   void visitModule(Module* curr) {
-    auto indexType = curr->memory.indexType;
+    auto indexType =
+      curr->memories.empty() ? Type::i32 : curr->memories[0]->indexType;
 
     // Load.
     addImport(
diff --git a/src/passes/Intrinsics.cpp b/src/passes/Intrinsics.cpp
index 1b5a50b..faba238 100644
--- a/src/passes/Intrinsics.cpp
+++ b/src/passes/Intrinsics.cpp
@@ -24,7 +24,9 @@ namespace wasm {
 struct IntrinsicLowering : public WalkerPass<PostWalker<IntrinsicLowering>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new IntrinsicLowering; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<IntrinsicLowering>();
+  }
 
   void visitCall(Call* curr) {
     if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp
new file mode 100644
index 0000000..aa15469
--- /dev/null
+++ b/src/passes/JSPI.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "asmjs/shared-constants.h"
+#include "ir/element-utils.h"
+#include "ir/import-utils.h"
+#include "ir/literal-utils.h"
+#include "ir/names.h"
+#include "ir/utils.h"
+#include "pass.h"
+#include "shared-constants.h"
+#include "support/file.h"
+#include "support/string.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+#include <utility>
+
+//
+// Convert a module to be compatible with JavaScript promise integration (JSPI).
+// Promising exports will be wrapped with a function that will handle storing
+// the suspsender that is passed in as the first param from a "promising"
+// `WebAssembly.Function`. Suspending imports will also be wrapped, but they
+// will take the stored suspender and pass it as the first param to the imported
+// function that should be created from a "suspending" `WebAssembly.Function`.
+//
+// By default all imports and exports will be wrapped, but this can be
+// controlled with the following options:
+//
+//   --pass-arg=jspi-imports@module1.base1,module2.base2,module3.base3
+//
+//      Wrap each import in the comma-separated list. Wildcards and a separate
+//      files are supported. See `asyncify-imports` for more details.
+//
+//   --pass-arg=jspi-exports@function_one,function_two,function_three
+//
+//      Wrap each export in the comma-separated list. Similar to jspi-imports,
+//      wildcards and separate files are supported.
+//
+
+namespace wasm {
+
+static std::string getFullFunctionName(Name module, Name base) {
+  return std::string(module.str) + '.' + base.toString();
+}
+
+static bool canChangeState(std::string name, String::Split stateChangers) {
+  // When no state changers are given default to everything changes state.
+  if (stateChangers.empty()) {
+    return true;
+  }
+  for (auto& stateChanger : stateChangers) {
+    if (String::wildcardMatch(stateChanger, name)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+struct JSPI : public Pass {
+
+  Type externref = Type(HeapType::ext, Nullable);
+
+  void run(Module* module) override {
+    Builder builder(*module);
+
+    auto& options = getPassOptions();
+    // Find which imports can suspend.
+    auto stateChangingImports = String::trim(read_possible_response_file(
+      options.getArgumentOrDefault("jspi-imports", "")));
+    String::Split listedImports(stateChangingImports, ",");
+
+    // Find which exports should create a promise.
+    auto stateChangingExports = String::trim(read_possible_response_file(
+      options.getArgumentOrDefault("jspi-exports", "")));
+    String::Split listedExports(stateChangingExports, ",");
+
+    // Create a global to store the suspender that is passed into exported
+    // functions and will then need to be passed out to the imported functions.
+    Name suspender = Names::getValidGlobalName(*module, "suspender");
+    module->addGlobal(builder.makeGlobal(suspender,
+                                         externref,
+                                         builder.makeRefNull(HeapType::noext),
+                                         Builder::Mutable));
+
+    // Keep track of already wrapped functions since they can be exported
+    // multiple times, but only one wrapper is needed.
+    std::unordered_map<Name, Name> wrappedExports;
+
+    // Wrap each exported function in a function that stores the suspender
+    // and calls the original export.
+    for (auto& ex : module->exports) {
+      if (ex->kind == ExternalKind::Function &&
+          canChangeState(ex->name.toString(), listedExports)) {
+        auto* func = module->getFunction(ex->value);
+        Name wrapperName;
+        auto iter = wrappedExports.find(func->name);
+        if (iter == wrappedExports.end()) {
+          wrapperName = makeWrapperForExport(func, module, suspender);
+          wrappedExports[func->name] = wrapperName;
+        } else {
+          wrapperName = iter->second;
+        }
+        ex->value = wrapperName;
+      }
+    }
+
+    // Avoid iterator invalidation later.
+    std::vector<Function*> originalFunctions;
+    for (auto& func : module->functions) {
+      originalFunctions.push_back(func.get());
+    }
+    // Wrap each imported function in a function that gets the global suspender
+    // and passes it on to the imported function.
+    for (auto* im : originalFunctions) {
+      if (im->imported() &&
+          canChangeState(getFullFunctionName(im->module, im->base),
+                         listedImports)) {
+        makeWrapperForImport(im, module, suspender);
+      }
+    }
+  }
+
+private:
+  Name makeWrapperForExport(Function* func, Module* module, Name suspender) {
+    Name wrapperName = Names::getValidFunctionName(
+      *module, std::string("export$") + func->name.toString());
+
+    Builder builder(*module);
+
+    auto* call = module->allocator.alloc<Call>();
+    call->target = func->name;
+    call->type = func->getResults();
+
+    // Add an externref param as the first argument and copy all the original
+    // params to new export.
+    std::vector<Type> wrapperParams;
+    std::vector<NameType> namedWrapperParams;
+    wrapperParams.push_back(externref);
+    namedWrapperParams.emplace_back(Names::getValidLocalName(*func, "susp"),
+                                    externref);
+    Index i = 0;
+    for (const auto& param : func->getParams()) {
+      call->operands.push_back(
+        builder.makeLocalGet(wrapperParams.size(), param));
+      wrapperParams.push_back(param);
+      namedWrapperParams.emplace_back(func->getLocalNameOrGeneric(i), param);
+      i++;
+    }
+    auto* block = builder.makeBlock();
+    block->list.push_back(
+      builder.makeGlobalSet(suspender, builder.makeLocalGet(0, externref)));
+    block->list.push_back(call);
+    Type resultsType = func->getResults();
+    if (resultsType == Type::none) {
+      // A void return is not currently allowed by v8. Add an i32 return value
+      // that is ignored.
+      // https://bugs.chromium.org/p/v8/issues/detail?id=13231
+      resultsType = Type::i32;
+      block->list.push_back(builder.makeConst(0));
+    }
+    block->finalize();
+    auto wrapperFunc =
+      Builder::makeFunction(wrapperName,
+                            std::move(namedWrapperParams),
+                            Signature(Type(wrapperParams), resultsType),
+                            {},
+                            block);
+    return module->addFunction(std::move(wrapperFunc))->name;
+  }
+
+  void makeWrapperForImport(Function* im, Module* module, Name suspender) {
+    Builder builder(*module);
+    auto wrapperIm = make_unique<Function>();
+    wrapperIm->name = Names::getValidFunctionName(
+      *module, std::string("import$") + im->name.toString());
+    wrapperIm->module = im->module;
+    wrapperIm->base = im->base;
+    auto stub = make_unique<Function>();
+    stub->name = Name(im->name.str);
+    stub->type = im->type;
+
+    auto* call = module->allocator.alloc<Call>();
+    call->target = wrapperIm->name;
+
+    // Add an externref as the first argument to the imported function.
+    std::vector<Type> params;
+    params.push_back(externref);
+    call->operands.push_back(builder.makeGlobalGet(suspender, externref));
+    Index i = 0;
+    for (const auto& param : im->getParams()) {
+      call->operands.push_back(builder.makeLocalGet(i, param));
+      params.push_back(param);
+      ++i;
+    }
+    auto* block = builder.makeBlock();
+    // Store the suspender so it can be restored after the call in case it is
+    // modified by another entry into a Wasm export.
+    auto supsenderCopyIndex = Builder::addVar(stub.get(), externref);
+    // If there's a return value we need to store it so it can be returned
+    // after restoring the suspender.
+    std::optional<Index> returnIndex;
+    if (stub->getResults().isConcrete()) {
+      returnIndex = Builder::addVar(stub.get(), stub->getResults());
+    }
+    block->list.push_back(builder.makeLocalSet(
+      supsenderCopyIndex, builder.makeGlobalGet(suspender, externref)));
+    if (returnIndex) {
+      block->list.push_back(builder.makeLocalSet(*returnIndex, call));
+    } else {
+      block->list.push_back(call);
+    }
+    // Restore the suspender.
+    block->list.push_back(builder.makeGlobalSet(
+      suspender, builder.makeLocalGet(supsenderCopyIndex, externref)));
+    if (returnIndex) {
+      block->list.push_back(
+        builder.makeLocalGet(*returnIndex, stub->getResults()));
+    }
+    block->finalize();
+    call->type = im->getResults();
+    stub->body = block;
+    wrapperIm->type = Signature(Type(params), call->type);
+
+    module->removeFunction(im->name);
+    module->addFunction(std::move(stub));
+    module->addFunction(std::move(wrapperIm));
+  }
+};
+
+Pass* createJSPIPass() { return new JSPI(); }
+
+} // namespace wasm
diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp
index e5a69be..9732738 100644
--- a/src/passes/LegalizeJSInterface.cpp
+++ b/src/passes/LegalizeJSInterface.cpp
@@ -43,16 +43,28 @@
 
 namespace wasm {
 
+// These are aliases for getTempRet0/setTempRet0 which emscripten defines in
+// compiler-rt and exports under these names.
+static Name GET_TEMP_RET_EXPORT("__get_temp_ret");
+static Name SET_TEMP_RET_EXPORT("__set_temp_ret");
+
+// For non-emscripten module we expect the host to define these functions so
+// and we import them under these names.
+static Name GET_TEMP_RET_IMPORT("getTempRet0");
+static Name SET_TEMP_RET_IMPORT("setTempRet0");
+
 struct LegalizeJSInterface : public Pass {
   bool full;
 
   LegalizeJSInterface(bool full) : full(full) {}
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
+    setTempRet0 = nullptr;
+    getTempRet0 = nullptr;
     auto exportOriginals =
-      !runner->options
-         .getArgumentOrDefault("legalize-js-interface-export-originals", "")
-         .empty();
+      getPassOptions().hasArgument("legalize-js-interface-export-originals");
+    exportedHelpers =
+      getPassOptions().hasArgument("legalize-js-interface-exported-helpers");
     // for each illegal export, we must export a legalized stub instead
     std::vector<std::unique_ptr<Export>> newExports;
     for (auto& ex : module->exports) {
@@ -74,7 +86,7 @@ struct LegalizeJSInterface : public Pass {
             // are only called from JS.
             if (!func->imported() && !isDynCall(ex->name)) {
               Builder builder(*module);
-              Name newName = std::string("orig$") + ex->name.str;
+              Name newName = std::string("orig$") + ex->name.toString();
               newExports.push_back(builder.makeExport(
                 newName, func->name, ExternalKind::Function));
             }
@@ -82,6 +94,7 @@ struct LegalizeJSInterface : public Pass {
         }
       }
     }
+
     for (auto& ex : newExports) {
       module->addExport(std::move(ex));
     }
@@ -112,7 +125,9 @@ struct LegalizeJSInterface : public Pass {
       struct Fixer : public WalkerPass<PostWalker<Fixer>> {
         bool isFunctionParallel() override { return true; }
 
-        Pass* create() override { return new Fixer(illegalImportsToLegal); }
+        std::unique_ptr<Pass> create() override {
+          return std::make_unique<Fixer>(illegalImportsToLegal);
+        }
 
         std::map<Name, Name>* illegalImportsToLegal;
 
@@ -142,19 +157,25 @@ struct LegalizeJSInterface : public Pass {
       };
 
       Fixer fixer(&illegalImportsToLegal);
-      fixer.run(runner, module);
-      fixer.runOnModuleCode(runner, module);
+      fixer.run(getPassRunner(), module);
+      fixer.runOnModuleCode(getPassRunner(), module);
 
       // Finally we can remove all the now-unused illegal imports
       for (const auto& pair : illegalImportsToLegal) {
         module->removeFunction(pair.first);
       }
     }
+
+    module->removeExport(GET_TEMP_RET_EXPORT);
+    module->removeExport(SET_TEMP_RET_EXPORT);
   }
 
 private:
   // map of illegal to legal names for imports
   std::map<Name, Name> illegalImportsToLegal;
+  bool exportedHelpers = false;
+  Function* getTempRet0 = nullptr;
+  Function* setTempRet0 = nullptr;
 
   template<typename T> bool isIllegal(T* t) {
     for (const auto& param : t->getParams()) {
@@ -185,10 +206,36 @@ private:
     return im->module == ENV && im->base.startsWith("invoke_");
   }
 
+  Function* tempSetter(Module* module) {
+    if (!setTempRet0) {
+      if (exportedHelpers) {
+        auto* ex = module->getExport(SET_TEMP_RET_EXPORT);
+        setTempRet0 = module->getFunction(ex->value);
+      } else {
+        setTempRet0 = getFunctionOrImport(
+          module, SET_TEMP_RET_IMPORT, Type::i32, Type::none);
+      }
+    }
+    return setTempRet0;
+  }
+
+  Function* tempGetter(Module* module) {
+    if (!getTempRet0) {
+      if (exportedHelpers) {
+        auto* ex = module->getExport(GET_TEMP_RET_EXPORT);
+        getTempRet0 = module->getFunction(ex->value);
+      } else {
+        getTempRet0 = getFunctionOrImport(
+          module, GET_TEMP_RET_IMPORT, Type::none, Type::i32);
+      }
+    }
+    return getTempRet0;
+  }
+
   // JS calls the export, so it must call a legal stub that calls the actual
   // wasm function
   Name makeLegalStub(Function* func, Module* module) {
-    Name legalName(std::string("legalstub$") + func->name.str);
+    Name legalName(std::string("legalstub$") + func->name.toString());
 
     // a method may be exported multiple times
     if (module->getFunctionOrNull(legalName)) {
@@ -220,13 +267,13 @@ private:
       func->getResults() == Type::i64 ? Type::i32 : func->getResults();
     legal->type = Signature(Type(legalParams), resultsType);
     if (func->getResults() == Type::i64) {
-      Function* f =
-        getFunctionOrImport(module, SET_TEMP_RET0, Type::i32, Type::none);
       auto index = Builder::addVar(legal, Name(), Type::i64);
       auto* block = builder.makeBlock();
       block->list.push_back(builder.makeLocalSet(index, call));
-      block->list.push_back(builder.makeCall(
-        f->name, {I64Utilities::getI64High(builder, index)}, Type::none));
+      block->list.push_back(
+        builder.makeCall(tempSetter(module)->name,
+                         {I64Utilities::getI64High(builder, index)},
+                         Type::none));
       block->list.push_back(I64Utilities::getI64Low(builder, index));
       block->finalize();
       legal->body = block;
@@ -241,11 +288,11 @@ private:
   Name makeLegalStubForCalledImport(Function* im, Module* module) {
     Builder builder(*module);
     auto legalIm = make_unique<Function>();
-    legalIm->name = Name(std::string("legalimport$") + im->name.str);
+    legalIm->name = Name(std::string("legalimport$") + im->name.toString());
     legalIm->module = im->module;
     legalIm->base = im->base;
     auto stub = make_unique<Function>();
-    stub->name = Name(std::string("legalfunc$") + im->name.str);
+    stub->name = Name(std::string("legalfunc$") + im->name.toString());
     stub->type = im->type;
 
     auto* call = module->allocator.alloc<Call>();
@@ -267,10 +314,9 @@ private:
     }
 
     if (im->getResults() == Type::i64) {
-      Function* f =
-        getFunctionOrImport(module, GET_TEMP_RET0, Type::none, Type::i32);
       call->type = Type::i32;
-      Expression* get = builder.makeCall(f->name, {}, call->type);
+      Expression* get =
+        builder.makeCall(tempGetter(module)->name, {}, call->type);
       stub->body = I64Utilities::recreateI64(builder, call, get);
     } else {
       call->type = im->getResults();
diff --git a/src/passes/LimitSegments.cpp b/src/passes/LimitSegments.cpp
index 0af54c5..2ea95b5 100644
--- a/src/passes/LimitSegments.cpp
+++ b/src/passes/LimitSegments.cpp
@@ -21,7 +21,7 @@
 namespace wasm {
 
 struct LimitSegments : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     if (!MemoryUtils::ensureLimitedSegments(*module)) {
       std::cerr << "Unable to merge segments. "
                 << "wasm VMs may not accept this binary" << std::endl;
diff --git a/src/passes/LocalCSE.cpp b/src/passes/LocalCSE.cpp
index 4728f18..2b9b26f 100644
--- a/src/passes/LocalCSE.cpp
+++ b/src/passes/LocalCSE.cpp
@@ -23,7 +23,7 @@
 // an add operation appear twice, and the inputs must be identical in both
 // cases, then the second one requests to reuse the computed value from the
 // first. The first one to appear is the "original" expression that will remain
-// in the code; we will save it's value to a local, and get it from that local
+// in the code; we will save its value to a local, and get it from that local
 // later:
 //
 //  (i32.add (A) (B))
@@ -526,7 +526,9 @@ struct LocalCSE : public WalkerPass<PostWalker<LocalCSE>> {
   // FIXME DWARF updating does not handle local changes yet.
   bool invalidatesDWARF() override { return true; }
 
-  Pass* create() override { return new LocalCSE(); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<LocalCSE>();
+  }
 
   void doWalkFunction(Function* func) {
     auto& options = getPassOptions();
@@ -549,8 +551,6 @@ struct LocalCSE : public WalkerPass<PostWalker<LocalCSE>> {
 
     Applier applier(requestInfos);
     applier.walkFunctionInModule(func, getModule());
-
-    TypeUpdating::handleNonDefaultableLocals(func, *getModule());
   }
 };
 
diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp
index b6e94b7..cf85709 100644
--- a/src/passes/LocalSubtyping.cpp
+++ b/src/passes/LocalSubtyping.cpp
@@ -25,6 +25,7 @@
 #include <ir/find_all.h>
 #include <ir/linear-execution.h>
 #include <ir/local-graph.h>
+#include <ir/local-structural-dominance.h>
 #include <ir/lubs.h>
 #include <ir/utils.h>
 #include <pass.h>
@@ -36,7 +37,13 @@ namespace wasm {
 struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new LocalSubtyping(); }
+  // This pass carefully avoids breaking validation by only refining a local's
+  // type to be non-nullable if it would validate.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<LocalSubtyping>();
+  }
 
   void doWalkFunction(Function* func) {
     if (!getModule()->features.hasGC()) {
@@ -68,24 +75,30 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> {
       }
     }
 
-    // Find which vars use the default value, if we allow non-nullable locals.
-    //
-    // If that feature is not enabled, then we can safely assume that the
-    // default is never used - the default would be a null value, and the type
-    // of the null does not really matter as all nulls compare equally, so we do
-    // not need to worry.
-    std::unordered_set<Index> usesDefault;
+    // Find which vars can be non-nullable.
+    std::unordered_set<Index> cannotBeNonNullable;
 
     if (getModule()->features.hasGCNNLocals()) {
+      // If the feature is enabled then the only constraint is being able to
+      // read the default value - if it is readable, the local cannot become
+      // non-nullable.
       for (auto& [get, sets] : localGraph.getSetses) {
         auto index = get->index;
         if (func->isVar(index) &&
             std::any_of(sets.begin(), sets.end(), [&](LocalSet* set) {
               return set == nullptr;
             })) {
-          usesDefault.insert(index);
+          cannotBeNonNullable.insert(index);
         }
       }
+    } else {
+      // Without GCNNLocals, validation rules follow the spec rules: all gets
+      // must be dominated structurally by sets, for the local to be non-
+      // nullable.
+      LocalStructuralDominance info(func, *getModule());
+      for (auto index : info.nonDominatingIndices) {
+        cannotBeNonNullable.insert(index);
+      }
     }
 
     auto varBase = func->getVarIndexBase();
@@ -136,10 +149,7 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> {
 
         // Remove non-nullability if we disallow that in locals.
         if (newType.isNonNullable()) {
-          // As mentioned earlier, even if we allow non-nullability, there may
-          // be a problem if the default value - a null - is used. In that case,
-          // remove non-nullability as well.
-          if (!getModule()->features.hasGCNNLocals() || usesDefault.count(i)) {
+          if (cannotBeNonNullable.count(i)) {
             newType = Type(newType.getHeapType(), Nullable);
           }
         } else if (!newType.isDefaultable()) {
diff --git a/src/passes/LogExecution.cpp b/src/passes/LogExecution.cpp
index 5978ecc..63ada58 100644
--- a/src/passes/LogExecution.cpp
+++ b/src/passes/LogExecution.cpp
@@ -59,7 +59,26 @@ struct LogExecution : public WalkerPass<PostWalker<LogExecution>> {
     // Add the import
     auto import =
       Builder::makeFunction(LOGGER, Signature(Type::i32, Type::none), {});
-    import->module = ENV;
+
+    // Import the log function from import "env" if the module
+    // imports other functions from that name.
+    for (auto& func : curr->functions) {
+      if (func->imported() && func->module == ENV) {
+        import->module = func->module;
+        break;
+      }
+    }
+
+    // If not, then pick the import name of the first function we find.
+    if (!import->module) {
+      for (auto& func : curr->functions) {
+        if (func->imported()) {
+          import->module = func->module;
+          break;
+        }
+      }
+    }
+
     import->base = LOGGER;
     curr->addFunction(std::move(import));
   }
diff --git a/src/passes/LoopInvariantCodeMotion.cpp b/src/passes/LoopInvariantCodeMotion.cpp
index c70c8e5..1329c02 100644
--- a/src/passes/LoopInvariantCodeMotion.cpp
+++ b/src/passes/LoopInvariantCodeMotion.cpp
@@ -37,9 +37,11 @@ struct LoopInvariantCodeMotion
   : public WalkerPass<ExpressionStackWalker<LoopInvariantCodeMotion>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new LoopInvariantCodeMotion; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<LoopInvariantCodeMotion>();
+  }
 
-  typedef std::unordered_set<LocalSet*> LoopSets;
+  using LoopSets = std::unordered_set<LocalSet*>;
 
   // main entry point
 
diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp
index 95dbf4f..661d6cd 100644
--- a/src/passes/Memory64Lowering.cpp
+++ b/src/passes/Memory64Lowering.cpp
@@ -21,96 +21,143 @@
 // TODO(wvo): make this run in parallel if needed.
 
 #include "ir/bits.h"
+#include "ir/import-utils.h"
 #include "pass.h"
 #include "wasm-builder.h"
 #include "wasm.h"
 
 namespace wasm {
 
-struct Memory64Lowering : public WalkerPass<PostWalker<Memory64Lowering>> {
+static Name MEMORY_BASE("__memory_base");
+static Name MEMORY_BASE32("__memory_base32");
 
-  void run(PassRunner* runner, Module* module) override {
-    if (module->memory.is64()) {
-      super::run(runner, module);
-    }
-  }
+struct Memory64Lowering : public WalkerPass<PostWalker<Memory64Lowering>> {
 
-  void wrapAddress64(Expression*& ptr) {
+  void wrapAddress64(Expression*& ptr, Name memoryName) {
     if (ptr->type == Type::unreachable) {
       return;
     }
     auto& module = *getModule();
-    assert(module.memory.is64());
-    assert(ptr->type == Type::i64);
-    Builder builder(module);
-    ptr = builder.makeUnary(UnaryOp::WrapInt64, ptr);
+    auto memory = module.getMemory(memoryName);
+    if (memory->is64()) {
+      assert(ptr->type == Type::i64);
+      Builder builder(module);
+      ptr = builder.makeUnary(UnaryOp::WrapInt64, ptr);
+    }
   }
 
-  void extendAddress64(Expression*& ptr) {
+  void extendAddress64(Expression*& ptr, Name memoryName) {
     if (ptr->type == Type::unreachable) {
       return;
     }
     auto& module = *getModule();
-    assert(module.memory.is64());
-    assert(ptr->type == Type::i64);
-    ptr->type = Type::i32;
-    Builder builder(module);
-    ptr = builder.makeUnary(UnaryOp::ExtendUInt32, ptr);
+    auto memory = module.getMemory(memoryName);
+    if (memory->is64()) {
+      assert(ptr->type == Type::i64);
+      ptr->type = Type::i32;
+      Builder builder(module);
+      ptr = builder.makeUnary(UnaryOp::ExtendUInt32, ptr);
+    }
   }
 
-  void visitLoad(Load* curr) { wrapAddress64(curr->ptr); }
+  void visitLoad(Load* curr) { wrapAddress64(curr->ptr, curr->memory); }
 
-  void visitStore(Store* curr) { wrapAddress64(curr->ptr); }
+  void visitStore(Store* curr) { wrapAddress64(curr->ptr, curr->memory); }
 
   void visitMemorySize(MemorySize* curr) {
-    auto size = static_cast<Expression*>(curr);
-    extendAddress64(size);
-    curr->ptrType = Type::i32;
-    replaceCurrent(size);
+    auto& module = *getModule();
+    auto memory = module.getMemory(curr->memory);
+    if (memory->is64()) {
+      auto size = static_cast<Expression*>(curr);
+      extendAddress64(size, curr->memory);
+      curr->ptrType = Type::i32;
+      replaceCurrent(size);
+    }
   }
 
   void visitMemoryGrow(MemoryGrow* curr) {
-    wrapAddress64(curr->delta);
-    auto size = static_cast<Expression*>(curr);
-    extendAddress64(size);
-    curr->ptrType = Type::i32;
-    replaceCurrent(size);
+    auto& module = *getModule();
+    auto memory = module.getMemory(curr->memory);
+    if (memory->is64()) {
+      wrapAddress64(curr->delta, curr->memory);
+      auto size = static_cast<Expression*>(curr);
+      extendAddress64(size, curr->memory);
+      curr->ptrType = Type::i32;
+      replaceCurrent(size);
+    }
   }
 
-  void visitMemoryInit(MemoryInit* curr) { wrapAddress64(curr->dest); }
+  void visitMemoryInit(MemoryInit* curr) {
+    wrapAddress64(curr->dest, curr->memory);
+  }
 
   void visitMemoryFill(MemoryFill* curr) {
-    wrapAddress64(curr->dest);
-    wrapAddress64(curr->size);
+    wrapAddress64(curr->dest, curr->memory);
+    wrapAddress64(curr->size, curr->memory);
   }
 
   void visitMemoryCopy(MemoryCopy* curr) {
-    wrapAddress64(curr->dest);
-    wrapAddress64(curr->source);
-    wrapAddress64(curr->size);
+    wrapAddress64(curr->dest, curr->destMemory);
+    wrapAddress64(curr->source, curr->sourceMemory);
+    wrapAddress64(curr->size, curr->destMemory);
   }
 
-  void visitAtomicRMW(AtomicRMW* curr) { wrapAddress64(curr->ptr); }
+  void visitAtomicRMW(AtomicRMW* curr) {
+    wrapAddress64(curr->ptr, curr->memory);
+  }
 
-  void visitAtomicCmpxchg(AtomicCmpxchg* curr) { wrapAddress64(curr->ptr); }
+  void visitAtomicCmpxchg(AtomicCmpxchg* curr) {
+    wrapAddress64(curr->ptr, curr->memory);
+  }
 
-  void visitAtomicWait(AtomicWait* curr) { wrapAddress64(curr->ptr); }
+  void visitAtomicWait(AtomicWait* curr) {
+    wrapAddress64(curr->ptr, curr->memory);
+  }
 
-  void visitAtomicNotify(AtomicNotify* curr) { wrapAddress64(curr->ptr); }
+  void visitAtomicNotify(AtomicNotify* curr) {
+    wrapAddress64(curr->ptr, curr->memory);
+  }
 
   void visitMemory(Memory* memory) {
-    for (auto& segment : memory->segments) {
-      if (!segment.isPassive) {
-        auto* c = segment.offset->cast<Const>();
+    // This is visited last.
+    if (memory->is64()) {
+      memory->indexType = Type::i32;
+      if (memory->hasMax() && memory->max > Memory::kMaxSize32) {
+        memory->max = Memory::kMaxSize32;
+      }
+    }
+  }
+
+  void visitDataSegment(DataSegment* segment) {
+    if (!segment->isPassive) {
+      if (auto* c = segment->offset->dynCast<Const>()) {
         c->value = Literal(static_cast<uint32_t>(c->value.geti64()));
         c->type = Type::i32;
+      } else if (auto* get = segment->offset->dynCast<GlobalGet>()) {
+        auto& module = *getModule();
+        auto* g = module.getGlobal(get->name);
+        if (g->imported() && g->base == MEMORY_BASE) {
+          ImportInfo info(module);
+          auto* memoryBase32 = info.getImportedGlobal(g->module, MEMORY_BASE32);
+          if (!memoryBase32) {
+            Builder builder(module);
+            memoryBase32 = builder
+                             .makeGlobal(MEMORY_BASE32,
+                                         Type::i32,
+                                         builder.makeConst(int32_t(0)),
+                                         Builder::Immutable)
+                             .release();
+            memoryBase32->module = g->module;
+            memoryBase32->base = MEMORY_BASE32;
+            module.addGlobal(memoryBase32);
+          }
+          // Use this alternative import when initializing the segment.
+          assert(memoryBase32);
+          get->type = Type::i32;
+          get->name = memoryBase32->name;
+        }
       }
     }
-    // This is visited last.
-    memory->indexType = Type::i32;
-    if (memory->hasMax() && memory->max > Memory::kMaxSize32) {
-      memory->max = Memory::kMaxSize32;
-    }
   }
 };
 
diff --git a/src/passes/MemoryPacking.cpp b/src/passes/MemoryPacking.cpp
index 7b4caf1..02f53e2 100644
--- a/src/passes/MemoryPacking.cpp
+++ b/src/passes/MemoryPacking.cpp
@@ -21,7 +21,7 @@
 // pass assumes that memory initialized by active segments is zero on
 // instantiation and therefore simply drops the zero ranges from the active
 // segments. For passive segments, we perform the same splitting, but we also
-// record how each segment was split and update all bulk memory operations
+// record how each segment was split and update all instructions that use it
 // accordingly. To preserve trapping semantics for memory.init instructions, it
 // is sometimes necessary to explicitly track whether input segments would have
 // been dropped in globals. We are careful to emit only as many of these globals
@@ -50,7 +50,7 @@ struct Range {
   size_t end;
 };
 
-// A function that produces the transformed bulk memory op. We need to use a
+// A function that produces the transformed instruction. We need to use a
 // function here instead of simple data because the replacement code sequence
 // may require allocating new locals, which in turn requires the enclosing
 // Function, which is only available in the parallelized instruction replacement
@@ -62,10 +62,10 @@ struct Range {
 // locals as necessary.
 using Replacement = std::function<Expression*(Function*)>;
 
-// Maps each bulk memory op to the replacement that must be applied to it.
+// Maps each instruction to the replacement that must be applied to it.
 using Replacements = std::unordered_map<Expression*, Replacement>;
 
-// A collection of bulk memory operations referring to a particular segment.
+// A collection of instructions referring to a particular segment.
 using Referrers = std::vector<Expression*>;
 
 // Map segment indices to referrers.
@@ -83,65 +83,75 @@ const size_t DATA_DROP_SIZE = 3;
 
 Expression*
 makeGtShiftedMemorySize(Builder& builder, Module& module, MemoryInit* curr) {
+  auto mem = module.getMemory(curr->memory);
   return builder.makeBinary(
-    module.memory.is64() ? GtUInt64 : GtUInt32,
+    mem->is64() ? GtUInt64 : GtUInt32,
     curr->dest,
-    builder.makeBinary(module.memory.is64() ? ShlInt64 : ShlInt32,
-                       builder.makeMemorySize(),
-                       builder.makeConstPtr(16)));
+    builder.makeBinary(mem->is64() ? ShlInt64 : ShlInt32,
+                       builder.makeMemorySize(mem->name),
+                       builder.makeConstPtr(16, mem->indexType)));
 }
 
 } // anonymous namespace
 
 struct MemoryPacking : public Pass {
-  void run(PassRunner* runner, Module* module) override;
-  bool canOptimize(const Memory& memory, const PassOptions& passOptions);
-  void optimizeBulkMemoryOps(PassRunner* runner, Module* module);
+  // This pass operates on linear memory, and does not affect reference locals.
+  // TODO: don't run at all if the module has no memories
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override;
+  bool canOptimize(std::vector<std::unique_ptr<Memory>>& memories,
+                   std::vector<std::unique_ptr<DataSegment>>& dataSegments);
+  void optimizeSegmentOps(Module* module);
   void getSegmentReferrers(Module* module, ReferrersMap& referrers);
-  void dropUnusedSegments(std::vector<Memory::Segment>& segments,
+  void dropUnusedSegments(Module* module,
+                          std::vector<std::unique_ptr<DataSegment>>& segments,
                           ReferrersMap& referrers);
-  bool canSplit(const Memory::Segment& segment, const Referrers& referrers);
-  void calculateRanges(const Memory::Segment& segment,
+  bool canSplit(const std::unique_ptr<DataSegment>& segment,
+                const Referrers& referrers);
+  void calculateRanges(const std::unique_ptr<DataSegment>& segment,
                        const Referrers& referrers,
                        std::vector<Range>& ranges);
   void createSplitSegments(Builder& builder,
-                           const Memory::Segment& segment,
+                           const DataSegment* segment,
                            std::vector<Range>& ranges,
-                           std::vector<Memory::Segment>& packed,
+                           std::vector<std::unique_ptr<DataSegment>>& packed,
                            size_t segmentsRemaining);
   void createReplacements(Module* module,
                           const std::vector<Range>& ranges,
                           const Referrers& referrers,
                           Replacements& replacements,
                           const Index segmentIndex);
-  void replaceBulkMemoryOps(PassRunner* runner,
-                            Module* module,
-                            Replacements& replacements);
+  void replaceSegmentOps(Module* module, Replacements& replacements);
 };
 
-void MemoryPacking::run(PassRunner* runner, Module* module) {
-  if (!canOptimize(module->memory, runner->options)) {
+void MemoryPacking::run(Module* module) {
+  // Does not have multi-memories support
+  if (!canOptimize(module->memories, module->dataSegments)) {
     return;
   }
 
-  auto& segments = module->memory.segments;
+  bool canHaveSegmentReferrers =
+    module->features.hasBulkMemory() || module->features.hasGC();
 
-  // For each segment, a list of bulk memory instructions that refer to it
+  auto& segments = module->dataSegments;
+
+  // For each segment, a list of instructions that refer to it
   ReferrersMap referrers;
 
-  if (module->features.hasBulkMemory()) {
+  if (canHaveSegmentReferrers) {
     // Optimize out memory.inits and data.drops that can be entirely replaced
     // with other instruction sequences. This can increase the number of unused
     // segments that can be dropped entirely and allows later replacement
     // creation to make more assumptions about what these instructions will look
     // like, such as memory.inits not having both zero offset and size.
-    optimizeBulkMemoryOps(runner, module);
+    optimizeSegmentOps(module);
     getSegmentReferrers(module, referrers);
-    dropUnusedSegments(segments, referrers);
+    dropUnusedSegments(module, segments, referrers);
   }
 
   // The new, split memory segments
-  std::vector<Memory::Segment> packed;
+  std::vector<std::unique_ptr<DataSegment>> packed;
 
   Replacements replacements;
   Builder builder(*module);
@@ -156,48 +166,49 @@ void MemoryPacking::run(PassRunner* runner, Module* module) {
     } else {
       // A single range covers the entire segment. Set isZero to false so the
       // original memory.init will be used even if segment is all zeroes.
-      ranges.push_back({false, 0, segment.data.size()});
+      ranges.push_back({false, 0, segment->data.size()});
     }
 
     Index firstNewIndex = packed.size();
     size_t segmentsRemaining = segments.size() - origIndex;
-    createSplitSegments(builder, segment, ranges, packed, segmentsRemaining);
+    createSplitSegments(
+      builder, segment.get(), ranges, packed, segmentsRemaining);
     createReplacements(
       module, ranges, currReferrers, replacements, firstNewIndex);
   }
 
   segments.swap(packed);
+  module->updateDataSegmentsMap();
 
-  if (module->features.hasBulkMemory()) {
-    replaceBulkMemoryOps(runner, module, replacements);
+  if (canHaveSegmentReferrers) {
+    replaceSegmentOps(module, replacements);
   }
 }
 
-bool MemoryPacking::canOptimize(const Memory& memory,
-                                const PassOptions& passOptions) {
-  if (!memory.exists) {
+bool MemoryPacking::canOptimize(
+  std::vector<std::unique_ptr<Memory>>& memories,
+  std::vector<std::unique_ptr<DataSegment>>& dataSegments) {
+  if (memories.empty() || memories.size() > 1) {
     return false;
   }
-
+  auto& memory = memories[0];
   // We must optimize under the assumption that memory has been initialized to
   // zero. That is the case for a memory declared in the module, but for a
   // memory that is imported, we must be told that it is zero-initialized.
-  if (memory.imported() && !passOptions.zeroFilledMemory) {
+  if (memory->imported() && !getPassOptions().zeroFilledMemory) {
     return false;
   }
 
-  auto& segments = memory.segments;
-
   // One segment is always ok to optimize, as it does not have the potential
   // problems handled below.
-  if (segments.size() <= 1) {
+  if (dataSegments.size() <= 1) {
     return true;
   }
   // Check if it is ok for us to optimize.
   Address maxAddress = 0;
-  for (auto& segment : segments) {
-    if (!segment.isPassive) {
-      auto* c = segment.offset->dynCast<Const>();
+  for (auto& segment : dataSegments) {
+    if (!segment->isPassive) {
+      auto* c = segment->offset->dynCast<Const>();
       // If an active segment has a non-constant offset, then what gets written
       // cannot be known until runtime. That is, the active segments are written
       // out at startup, in order, and one may trample the data of another, like
@@ -222,7 +233,7 @@ bool MemoryPacking::canOptimize(const Memory& memory,
       }
       // Note the maximum address so far.
       maxAddress = std::max(
-        maxAddress, Address(c->value.getUnsigned() + segment.data.size()));
+        maxAddress, Address(c->value.getUnsigned() + segment->data.size()));
     }
   }
   // All active segments have constant offsets, known at this time, so we may be
@@ -230,11 +241,11 @@ bool MemoryPacking::canOptimize(const Memory& memory,
   // earlier.
   // TODO: optimize in the trampling case
   DisjointSpans space;
-  for (auto& segment : segments) {
-    if (!segment.isPassive) {
-      auto* c = segment.offset->cast<Const>();
+  for (auto& segment : dataSegments) {
+    if (!segment->isPassive) {
+      auto* c = segment->offset->cast<Const>();
       Address start = c->value.getUnsigned();
-      DisjointSpans::Span span{start, start + segment.data.size()};
+      DisjointSpans::Span span{start, start + segment->data.size()};
       if (space.addAndCheckOverlap(span)) {
         std::cerr << "warning: active memory segments have overlap, which "
                   << "prevents some optimizations.\n";
@@ -245,36 +256,38 @@ bool MemoryPacking::canOptimize(const Memory& memory,
   return true;
 }
 
-bool MemoryPacking::canSplit(const Memory::Segment& segment,
+bool MemoryPacking::canSplit(const std::unique_ptr<DataSegment>& segment,
                              const Referrers& referrers) {
   // Don't mess with segments related to llvm coverage tools such as
   // __llvm_covfun. There segments are expected/parsed by external downstream
   // tools (llvm-cov) so they need to be left intact.
   // See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
-  if (segment.name.is() && segment.name.startsWith("__llvm")) {
+  if (segment->name.is() && segment->name.startsWith("__llvm")) {
     return false;
   }
 
-  if (segment.isPassive) {
-    for (auto* referrer : referrers) {
-      if (auto* init = referrer->dynCast<MemoryInit>()) {
+  for (auto* referrer : referrers) {
+    if (auto* curr = referrer->dynCast<MemoryInit>()) {
+      if (segment->isPassive) {
         // Do not try to split if there is a nonconstant offset or size
-        if (!init->offset->is<Const>() || !init->size->is<Const>()) {
+        if (!curr->offset->is<Const>() || !curr->size->is<Const>()) {
           return false;
         }
       }
+    } else if (referrer->is<ArrayNewSeg>()) {
+      // TODO: Split segments referenced by array.new_data instructions.
+      return false;
     }
-    return true;
   }
 
   // Active segments can only be split if they have constant offsets
-  return segment.offset->is<Const>();
+  return segment->isPassive || segment->offset->is<Const>();
 }
 
-void MemoryPacking::calculateRanges(const Memory::Segment& segment,
+void MemoryPacking::calculateRanges(const std::unique_ptr<DataSegment>& segment,
                                     const Referrers& referrers,
                                     std::vector<Range>& ranges) {
-  auto& data = segment.data;
+  auto& data = segment->data;
   if (data.size() == 0) {
     return;
   }
@@ -304,7 +317,7 @@ void MemoryPacking::calculateRanges(const Memory::Segment& segment,
   // entire segment and that all its arguments are constants. These assumptions
   // are true of all memory.inits generated by the tools.
   size_t threshold = 0;
-  if (segment.isPassive) {
+  if (segment->isPassive) {
     // Passive segment metadata size
     threshold += 2;
     // Zeroes on the edge do not increase the number of segments or data.drops,
@@ -366,17 +379,23 @@ void MemoryPacking::calculateRanges(const Memory::Segment& segment,
   std::swap(ranges, mergedRanges);
 }
 
-void MemoryPacking::optimizeBulkMemoryOps(PassRunner* runner, Module* module) {
+void MemoryPacking::optimizeSegmentOps(Module* module) {
   struct Optimizer : WalkerPass<PostWalker<Optimizer>> {
     bool isFunctionParallel() override { return true; }
-    Pass* create() override { return new Optimizer; }
+
+    // This operates on linear memory, and does not affect reference locals.
+    bool requiresNonNullableLocalFixups() override { return false; }
+
+    std::unique_ptr<Pass> create() override {
+      return std::make_unique<Optimizer>();
+    }
 
     bool needsRefinalizing;
 
     void visitMemoryInit(MemoryInit* curr) {
       Builder builder(*getModule());
-      Memory::Segment& segment = getModule()->memory.segments[curr->segment];
-      size_t maxRuntimeSize = segment.isPassive ? segment.data.size() : 0;
+      auto& segment = getModule()->dataSegments[curr->segment];
+      size_t maxRuntimeSize = segment->isPassive ? segment->data.size() : 0;
       bool mustNop = false;
       bool mustTrap = false;
       auto* offset = curr->offset->dynCast<Const>();
@@ -409,7 +428,7 @@ void MemoryPacking::optimizeBulkMemoryOps(PassRunner* runner, Module* module) {
                                         builder.makeDrop(curr->size),
                                         builder.makeUnreachable()));
         needsRefinalizing = true;
-      } else if (!segment.isPassive) {
+      } else if (!segment->isPassive) {
         // trap if (dest > memory.size | offset | size) != 0
         replaceCurrent(builder.makeIf(
           builder.makeBinary(
@@ -420,7 +439,7 @@ void MemoryPacking::optimizeBulkMemoryOps(PassRunner* runner, Module* module) {
       }
     }
     void visitDataDrop(DataDrop* curr) {
-      if (!getModule()->memory.segments[curr->segment].isPassive) {
+      if (!getModule()->dataSegments[curr->segment]->isPassive) {
         ExpressionManipulator::nop(curr);
       }
     }
@@ -432,7 +451,7 @@ void MemoryPacking::optimizeBulkMemoryOps(PassRunner* runner, Module* module) {
       }
     }
   } optimizer;
-  optimizer.run(runner, module);
+  optimizer.run(getPassRunner(), module);
 }
 
 void MemoryPacking::getSegmentReferrers(Module* module,
@@ -451,6 +470,11 @@ void MemoryPacking::getSegmentReferrers(Module* module,
       void visitDataDrop(DataDrop* curr) {
         referrers[curr->segment].push_back(curr);
       }
+      void visitArrayNewSeg(ArrayNewSeg* curr) {
+        if (curr->op == NewData) {
+          referrers[curr->segment].push_back(curr);
+        }
+      }
       void doWalkFunction(Function* func) {
         super::doWalkFunction(func);
       }
@@ -467,9 +491,11 @@ void MemoryPacking::getSegmentReferrers(Module* module,
   }
 }
 
-void MemoryPacking::dropUnusedSegments(std::vector<Memory::Segment>& segments,
-                                       ReferrersMap& referrers) {
-  std::vector<Memory::Segment> usedSegments;
+void MemoryPacking::dropUnusedSegments(
+  Module* module,
+  std::vector<std::unique_ptr<DataSegment>>& segments,
+  ReferrersMap& referrers) {
+  std::vector<std::unique_ptr<DataSegment>> usedSegments;
   ReferrersMap usedReferrers;
   // Remove segments that are never used
   // TODO: remove unused portions of partially used segments as well
@@ -477,10 +503,10 @@ void MemoryPacking::dropUnusedSegments(std::vector<Memory::Segment>& segments,
     bool used = false;
     auto referrersIt = referrers.find(i);
     bool hasReferrers = referrersIt != referrers.end();
-    if (segments[i].isPassive) {
+    if (segments[i]->isPassive) {
       if (hasReferrers) {
         for (auto* referrer : referrersIt->second) {
-          if (referrer->is<MemoryInit>()) {
+          if (!referrer->is<DataDrop>()) {
             used = true;
             break;
           }
@@ -503,23 +529,26 @@ void MemoryPacking::dropUnusedSegments(std::vector<Memory::Segment>& segments,
     }
   }
   std::swap(segments, usedSegments);
+  module->updateDataSegmentsMap();
   std::swap(referrers, usedReferrers);
 }
 
-void MemoryPacking::createSplitSegments(Builder& builder,
-                                        const Memory::Segment& segment,
-                                        std::vector<Range>& ranges,
-                                        std::vector<Memory::Segment>& packed,
-                                        size_t segmentsRemaining) {
+void MemoryPacking::createSplitSegments(
+  Builder& builder,
+  const DataSegment* segment,
+  std::vector<Range>& ranges,
+  std::vector<std::unique_ptr<DataSegment>>& packed,
+  size_t segmentsRemaining) {
   size_t segmentCount = 0;
+  bool hasExplicitName = false;
   for (size_t i = 0; i < ranges.size(); ++i) {
     Range& range = ranges[i];
     if (range.isZero) {
       continue;
     }
     Expression* offset = nullptr;
-    if (!segment.isPassive) {
-      if (auto* c = segment.offset->dynCast<Const>()) {
+    if (!segment->isPassive) {
+      if (auto* c = segment->offset->dynCast<Const>()) {
         if (c->value.type == Type::i32) {
           offset = builder.makeConst(int32_t(c->value.geti32() + range.start));
         } else {
@@ -528,7 +557,7 @@ void MemoryPacking::createSplitSegments(Builder& builder,
         }
       } else {
         assert(ranges.size() == 1);
-        offset = segment.offset;
+        offset = segment->offset;
       }
     }
     if (WebLimitations::MaxDataSegments <= packed.size() + segmentsRemaining) {
@@ -541,24 +570,27 @@ void MemoryPacking::createSplitSegments(Builder& builder,
       ranges.erase(ranges.begin() + i + 1, lastNonzero + 1);
     }
     Name name;
-    if (segment.name.is()) {
+    if (segment->name.is()) {
       // Name the first range after the original segment and all following
       // ranges get numbered accordingly.  This means that for segments that
       // canot be split (segments that contains a single range) the input and
       // output segment have the same name.
       if (!segmentCount) {
-        name = segment.name;
+        name = segment->name;
+        hasExplicitName = segment->hasExplicitName;
       } else {
-        name = std::string(segment.name.c_str()) + "." +
-               std::to_string(segmentCount);
+        name = segment->name.toString() + "." + std::to_string(segmentCount);
       }
       segmentCount++;
     }
-    packed.emplace_back(name,
-                        segment.isPassive,
-                        offset,
-                        &segment.data[range.start],
-                        range.end - range.start);
+    auto curr = Builder::makeDataSegment(name,
+                                         segment->memory,
+                                         segment->isPassive,
+                                         offset,
+                                         &segment->data[range.start],
+                                         range.end - range.start);
+    curr->hasExplicitName = hasExplicitName;
+    packed.push_back(std::move(curr));
   }
 }
 
@@ -571,12 +603,14 @@ void MemoryPacking::createReplacements(Module* module,
   if (ranges.size() == 1 && !ranges.front().isZero) {
     for (auto referrer : referrers) {
       replacements[referrer] = [referrer, segmentIndex](Function*) {
-        if (auto* init = referrer->dynCast<MemoryInit>()) {
-          init->segment = segmentIndex;
-        } else if (auto* drop = referrer->dynCast<DataDrop>()) {
-          drop->segment = segmentIndex;
+        if (auto* curr = referrer->dynCast<MemoryInit>()) {
+          curr->segment = segmentIndex;
+        } else if (auto* curr = referrer->dynCast<DataDrop>()) {
+          curr->segment = segmentIndex;
+        } else if (auto* curr = referrer->dynCast<ArrayNewSeg>()) {
+          curr->segment = segmentIndex;
         } else {
-          WASM_UNREACHABLE("Unexpected bulk memory operation");
+          WASM_UNREACHABLE("Unexpected segment operation");
         }
         return referrer;
       };
@@ -701,11 +735,12 @@ void MemoryPacking::createReplacements(Module* module,
       // Create new memory.init or memory.fill
       if (range.isZero) {
         Expression* value = builder.makeConst(Literal::makeZero(Type::i32));
-        appendResult(builder.makeMemoryFill(dest, value, size));
+        appendResult(builder.makeMemoryFill(dest, value, size, init->memory));
       } else {
         size_t offsetBytes = std::max(start, range.start) - range.start;
         Expression* offset = builder.makeConst(int32_t(offsetBytes));
-        appendResult(builder.makeMemoryInit(initIndex, dest, offset, size));
+        appendResult(
+          builder.makeMemoryInit(initIndex, dest, offset, size, init->memory));
         initIndex++;
       }
     }
@@ -753,16 +788,20 @@ void MemoryPacking::createReplacements(Module* module,
   }
 }
 
-void MemoryPacking::replaceBulkMemoryOps(PassRunner* runner,
-                                         Module* module,
-                                         Replacements& replacements) {
+void MemoryPacking::replaceSegmentOps(Module* module,
+                                      Replacements& replacements) {
   struct Replacer : WalkerPass<PostWalker<Replacer>> {
     bool isFunctionParallel() override { return true; }
 
+    // This operates on linear memory, and does not affect reference locals.
+    bool requiresNonNullableLocalFixups() override { return false; }
+
     Replacements& replacements;
 
     Replacer(Replacements& replacements) : replacements(replacements){};
-    Pass* create() override { return new Replacer(replacements); }
+    std::unique_ptr<Pass> create() override {
+      return std::make_unique<Replacer>(replacements);
+    }
 
     void visitMemoryInit(MemoryInit* curr) {
       auto replacement = replacements.find(curr);
@@ -775,8 +814,16 @@ void MemoryPacking::replaceBulkMemoryOps(PassRunner* runner,
       assert(replacement != replacements.end());
       replaceCurrent(replacement->second(getFunction()));
     }
+
+    void visitArrayNewSeg(ArrayNewSeg* curr) {
+      if (curr->op == NewData) {
+        auto replacement = replacements.find(curr);
+        assert(replacement != replacements.end());
+        replaceCurrent(replacement->second(getFunction()));
+      }
+    }
   } replacer(replacements);
-  replacer.run(runner, module);
+  replacer.run(getPassRunner(), module);
 }
 
 Pass* createMemoryPackingPass() { return new MemoryPacking(); }
diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp
index 225f4f0..45b9ebd 100644
--- a/src/passes/MergeBlocks.cpp
+++ b/src/passes/MergeBlocks.cpp
@@ -403,7 +403,9 @@ struct MergeBlocks
       PostWalker<MergeBlocks, UnifiedExpressionVisitor<MergeBlocks>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new MergeBlocks; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<MergeBlocks>();
+  }
 
   BranchUtils::BranchSeekerCache branchInfo;
 
diff --git a/src/passes/MergeLocals.cpp b/src/passes/MergeLocals.cpp
index e478db7..a7f765c 100644
--- a/src/passes/MergeLocals.cpp
+++ b/src/passes/MergeLocals.cpp
@@ -62,7 +62,9 @@ struct MergeLocals
   // FIXME DWARF updating does not handle local changes yet.
   bool invalidatesDWARF() override { return true; }
 
-  Pass* create() override { return new MergeLocals(); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<MergeLocals>();
+  }
 
   void doWalkFunction(Function* func) {
     // first, instrument the graph by modifying each copy
diff --git a/src/passes/MergeSimilarFunctions.cpp b/src/passes/MergeSimilarFunctions.cpp
index 9edf327..8e03956 100644
--- a/src/passes/MergeSimilarFunctions.cpp
+++ b/src/passes/MergeSimilarFunctions.cpp
@@ -174,7 +174,7 @@ struct EquivalentClass {
 struct MergeSimilarFunctions : public Pass {
   bool invalidatesDWARF() override { return true; }
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     std::vector<EquivalentClass> classes;
     collectEquivalentClasses(classes, module);
     std::sort(
@@ -202,8 +202,7 @@ struct MergeSimilarFunctions : public Pass {
 
   // Parameterize direct calls if the module supports func ref values.
   bool isCallIndirectionEnabled(Module* module) const {
-    return module->features.hasReferenceTypes() &&
-           module->features.hasTypedFunctionReferences();
+    return module->features.hasReferenceTypes() && module->features.hasGC();
   }
   bool areInEquvalentClass(Function* lhs, Function* rhs, Module* module);
   void collectEquivalentClasses(std::vector<EquivalentClass>& classes,
@@ -527,8 +526,9 @@ bool EquivalentClass::hasMergeBenefit(Module* module,
 
 Function* EquivalentClass::createShared(Module* module,
                                         const std::vector<ParamInfo>& params) {
-  Name fnName = Names::getValidFunctionName(
-    *module, std::string("byn$mgfn-shared$") + primaryFunction->name.str);
+  Name fnName = Names::getValidFunctionName(*module,
+                                            std::string("byn$mgfn-shared$") +
+                                              primaryFunction->name.toString());
   Builder builder(*module);
   std::vector<Type> sigParams;
   Index extraParamBase = primaryFunction->getNumParams();
diff --git a/src/passes/Metrics.cpp b/src/passes/Metrics.cpp
index 5667381..d2e072a 100644
--- a/src/passes/Metrics.cpp
+++ b/src/passes/Metrics.cpp
@@ -24,7 +24,7 @@
 
 namespace wasm {
 
-typedef std::map<const char*, int> Counts;
+using Counts = std::map<const char*, int>;
 
 static Counts lastCounts;
 
@@ -53,32 +53,37 @@ struct Metrics
     }
     ModuleUtils::iterDefinedGlobals(*module,
                                     [&](Global* curr) { walkGlobal(curr); });
-    walkMemory(&module->memory);
 
-    // add imports / funcs / globals / exports / tables
+    // add imports / funcs / globals / exports / tables / memories
     counts["[imports]"] = imports.getNumImports();
     counts["[funcs]"] = imports.getNumDefinedFunctions();
     counts["[globals]"] = imports.getNumDefinedGlobals();
     counts["[tags]"] = imports.getNumDefinedTags();
     counts["[exports]"] = module->exports.size();
     counts["[tables]"] = imports.getNumDefinedTables();
-    // add memory and table
-    if (module->memory.exists) {
-      Index size = 0;
-      for (auto& segment : module->memory.segments) {
-        size += segment.data.size();
-      }
+    counts["[memories]"] = imports.getNumDefinedMemories();
+
+    // add memory
+    for (auto& memory : module->memories) {
+      walkMemory(memory.get());
+    }
+    Index size = 0;
+    for (auto& segment : module->dataSegments) {
+      walkDataSegment(segment.get());
+      size += segment->data.size();
+    }
+    if (!module->memories.empty()) {
       counts["[memory-data]"] = size;
     }
 
-    Index size = 0;
-    ModuleUtils::iterActiveElementSegments(
-      *module, [&](ElementSegment* segment) { size += segment->data.size(); });
+    // add table
+    size = 0;
     for (auto& table : module->tables) {
       walkTable(table.get());
     }
     for (auto& segment : module->elementSegments) {
       walkElementSegment(segment.get());
+      size += segment->data.size();
     }
     if (!module->tables.empty()) {
       counts["[table-data]"] = size;
@@ -99,7 +104,7 @@ struct Metrics
         counts["[vars]"] = func->getNumVars();
         counts["[binary-bytes]"] =
           writer.tableOfContents.functionBodies[binaryIndex++].size;
-        printCounts(std::string("func: ") + func->name.str);
+        printCounts(std::string("func: ") + func->name.toString());
       });
       // print for each export how much code size is due to it, i.e.,
       // how much the module could shrink without it.
@@ -129,8 +134,8 @@ struct Metrics
         counts.clear();
         counts["[removable-bytes-without-it]"] =
           baseline - sizeAfterGlobalCleanup(&test);
-        printCounts(std::string("export: ") + exp->name.str + " (" +
-                    exp->value.str + ')');
+        printCounts(std::string("export: ") + exp->name.toString() + " (" +
+                    exp->value.toString() + ')');
       }
       // check how much size depends on the start method
       if (!module->start.isNull()) {
@@ -140,7 +145,7 @@ struct Metrics
         counts.clear();
         counts["[removable-bytes-without-it]"] =
           baseline - sizeAfterGlobalCleanup(&test);
-        printCounts(std::string("start: ") + module->start.str);
+        printCounts(std::string("start: ") + module->start.toString());
       }
       // can't compare detailed info between passes yet
       lastCounts.clear();
diff --git a/src/passes/MinifyImportsAndExports.cpp b/src/passes/MinifyImportsAndExports.cpp
index 02675e4..2105f02 100644
--- a/src/passes/MinifyImportsAndExports.cpp
+++ b/src/passes/MinifyImportsAndExports.cpp
@@ -46,6 +46,9 @@
 namespace wasm {
 
 struct MinifyImportsAndExports : public Pass {
+  // This operates on import/export names only.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   bool minifyExports, minifyModules;
 
 public:
@@ -56,7 +59,7 @@ private:
   // Generates minified names that are valid in JS.
   // Names are computed lazily.
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     // Minify the imported names.
     Names::MinifiedNameGenerator names;
     std::map<Name, Name> oldToNew;
diff --git a/src/passes/Monomorphize.cpp b/src/passes/Monomorphize.cpp
new file mode 100644
index 0000000..f8ee4a6
--- /dev/null
+++ b/src/passes/Monomorphize.cpp
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// When we see a call foo(arg1, arg2) and at least one of the arguments has a
+// more refined type than is declared in the function being called, create a
+// copy of the function with the refined type. That copy can then potentially be
+// optimized in useful ways later.
+//
+// Inlining also monomorphizes in effect. What this pass does is handle the
+// cases where inlining cannot be done.
+//
+// To see when monomorphizing makes sense, this optimizes the target function
+// both with and without the more refined types. If the refined types help then
+// the version with might remove a cast, for example. Note that while doing so
+// we keep the optimization results of the version without - there is no reason
+// to forget them since we've gone to the trouble anyhow. So this pass may have
+// the side effect of performing minor optimizations on functions. There is also
+// a variant of the pass that always monomorphizes, even when it does not seem
+// helpful, which is useful for testing, and possibly in cases where we need
+// more than just local optimizations to see the benefit - for example, perhaps
+// GUFA ends up more powerful later on.
+//
+// TODO: When we optimize we could run multiple cycles: A calls B calls C might
+//       end up with the refined+optimized B now having refined types in its
+//       call to C, which it did not have before. This is in fact the expected
+//       pattern of incremental monomorphization. Doing it in the pass could be
+//       more efficient as later cycles can focus only on what was just
+//       optimized and changed. Also, operating on functions just modified would
+//       help the case of A calls B and we end up optimizing A after we consider
+//       A->B, and the optimized version sends more refined types to B, which
+//       could unlock more potential.
+// TODO: We could sort the functions so that we start from root functions and
+//       end on leaves. That would make it more likely for a single iteration to
+//       do more work, as if A->B->C then we'd do A->B and optimize B and only
+//       then look at B->C.
+// TODO: Also run the result-refining part of SignatureRefining, as if we
+//       refine the result then callers of the function may benefit, even if
+//       there is no benefit in the function itself.
+// TODO: If this is too slow, we could "group" things, for example we could
+//       compute the LUB of a bunch of calls to a target and then investigate
+//       that one case and use it in all those callers.
+// TODO: Not just direct calls? But updating vtables is complex.
+// TODO: Not just types? We could monomorphize using Literal values. E.g. for
+//       function references, if we monomorphized we'd end up specializing qsort
+//       for the particular functions it is given.
+//
+
+#include "ir/cost.h"
+#include "ir/find_all.h"
+#include "ir/module-utils.h"
+#include "ir/names.h"
+#include "ir/type-updating.h"
+#include "ir/utils.h"
+#include "pass.h"
+#include "wasm-type.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+struct Monomorphize : public Pass {
+  // If set, we run some opts to see if monomorphization helps, and skip it if
+  // not.
+  bool onlyWhenHelpful;
+
+  Monomorphize(bool onlyWhenHelpful) : onlyWhenHelpful(onlyWhenHelpful) {}
+
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+
+    // TODO: parallelize, see comments below
+
+    // Note the list of all functions. We'll be adding more, and do not want to
+    // operate on those.
+    std::vector<Name> funcNames;
+    ModuleUtils::iterDefinedFunctions(
+      *module, [&](Function* func) { funcNames.push_back(func->name); });
+
+    // Find the calls in each function and optimize where we can, changing them
+    // to call more refined targets.
+    for (auto name : funcNames) {
+      auto* func = module->getFunction(name);
+      for (auto* call : FindAll<Call>(func->body).list) {
+        if (call->type == Type::unreachable) {
+          // Ignore unreachable code.
+          // TODO: return_call?
+          continue;
+        }
+
+        if (call->target == name) {
+          // Avoid recursion, which adds some complexity (as we'd be modifying
+          // ourselves if we apply optimizations).
+          continue;
+        }
+
+        call->target = getRefinedTarget(call, module);
+      }
+    }
+  }
+
+  // Given a call, make a copy of the function it is calling that has more
+  // refined arguments that fit the arguments being passed perfectly.
+  Name getRefinedTarget(Call* call, Module* module) {
+    auto target = call->target;
+    auto* func = module->getFunction(target);
+    if (func->imported()) {
+      // Nothing to do since this calls outside of the module.
+      return target;
+    }
+    auto params = func->getParams();
+    bool hasRefinedParam = false;
+    for (Index i = 0; i < call->operands.size(); i++) {
+      if (call->operands[i]->type != params[i]) {
+        hasRefinedParam = true;
+        break;
+      }
+    }
+    if (!hasRefinedParam) {
+      // Nothing to do since all params are fully refined already.
+      return target;
+    }
+
+    std::vector<Type> refinedTypes;
+    for (auto* operand : call->operands) {
+      refinedTypes.push_back(operand->type);
+    }
+    auto refinedParams = Type(refinedTypes);
+    auto iter = funcParamMap.find({target, refinedParams});
+    if (iter != funcParamMap.end()) {
+      return iter->second;
+    }
+
+    // This is the first time we see this situation. Let's see if it is worth
+    // monomorphizing.
+
+    // Create a new function with refined parameters as a copy of the original.
+    // (Note we must clear stack IR on the original: atm we do not have the
+    // ability to copy stack IR, so we'd hit an internal error. But as we will
+    // be optimizing the function anyhow, we'd be throwing away stack IR later
+    // so this isn't a problem.)
+    func->stackIR.reset();
+    auto refinedTarget = Names::getValidFunctionName(*module, target);
+    auto* refinedFunc = ModuleUtils::copyFunction(func, *module, refinedTarget);
+    TypeUpdating::updateParamTypes(refinedFunc, refinedTypes, *module);
+    refinedFunc->type = HeapType(Signature(refinedParams, func->getResults()));
+
+    // Assume we'll choose to use the refined target, but if we are being
+    // careful then we might change our mind.
+    auto chosenTarget = refinedTarget;
+    if (onlyWhenHelpful) {
+      // Optimize both functions using minimal opts, hopefully enough to see if
+      // there is a benefit to the refined types (such as the new types allowing
+      // a cast to be removed).
+      // TODO: Atm this can be done many times per function as it is once per
+      //       function and per set of types sent to it. Perhaps have some
+      //       total limit to avoid slow runtimes.
+      // TODO: We can end up optimizing |func| more than once. It may be
+      //       different each time if the previous optimization helped, but
+      //       often it will be identical. We could save the original version
+      //       and use that as the starting point here (and cache the optimized
+      //       version), but then we'd be throwing away optimization results. Or
+      //       we could see if later optimizations do not further decrease the
+      //       cost, and if so, use a cached value for the cost on such
+      //       "already maximally optimized" functions. The former approach is
+      //       more amenable to parallelization, as it avoids path dependence -
+      //       the other approaches are deterministic but they depend on the
+      //       order in which we see things. But it does require saving a copy
+      //       of the function, which uses memory, which is avoided if we just
+      //       keep optimizing from the current contents as we go. It's not
+      //       obvious which approach is best here.
+      doMinimalOpts(func);
+      doMinimalOpts(refinedFunc);
+
+      auto costBefore = CostAnalyzer(func->body).cost;
+      auto costAfter = CostAnalyzer(refinedFunc->body).cost;
+      if (costAfter >= costBefore) {
+        // We failed to improve. Remove the new function and return the old
+        // target.
+        module->removeFunction(refinedTarget);
+        chosenTarget = target;
+      }
+    }
+
+    // Mark the chosen target in the map, so we don't do this work again: every
+    // pair of target and refinedParams is only considered once.
+    funcParamMap[{target, refinedParams}] = chosenTarget;
+
+    return chosenTarget;
+  }
+
+  // Run minimal function-level optimizations on a function. This optimizes at
+  // -O1 which is very fast and runs in linear time basically, and so it should
+  // be reasonable to run as part of this pass: -O1 is several times faster than
+  // a full -O2, in particular, and so if we run this as part of -O2 we should
+  // not be making it much slower overall.
+  // TODO: Perhaps we don't need all of -O1, and can focus on specific things we
+  //       expect to help. That would be faster, but we'd always run the risk of
+  //       missing things, especially as new passes are added later and we don't
+  //       think to add them here.
+  //       Alternatively, perhaps we should have a mode that does use -O1 or
+  //       even -O2 or above, as in theory any optimization could end up
+  //       mattering a lot here.
+  void doMinimalOpts(Function* func) {
+    PassRunner runner(getPassRunner());
+    runner.options.optimizeLevel = 1;
+    // Local subtyping is not run in -O1, but we really do want it here since
+    // the entire point is that parameters now have more refined types, which
+    // can lead to locals reading them being refinable as well.
+    runner.add("local-subtyping");
+    runner.addDefaultFunctionOptimizationPasses();
+    runner.setIsNested(true);
+    runner.runOnFunction(func);
+  }
+
+  // Maps [func name, param types] to the name of a new function whose params
+  // have those types.
+  //
+  // Note that this can contain funcParamMap{A, types} = A, that is, that maps
+  // a function name to itself. That indicates we found no benefit from
+  // refining with those particular types, and saves us from computing it again
+  // later on.
+  std::unordered_map<std::pair<Name, Type>, Name> funcParamMap;
+};
+
+} // anonymous namespace
+
+Pass* createMonomorphizePass() { return new Monomorphize(true); }
+
+Pass* createMonomorphizeAlwaysPass() { return new Monomorphize(false); }
+
+} // namespace wasm
diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp
new file mode 100644
index 0000000..527a158
--- /dev/null
+++ b/src/passes/MultiMemoryLowering.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Condensing a module with multiple memories into a module with a single memory
+// for browsers that don’t support multiple memories.
+//
+// This pass also disables multi-memories so that the target features section in
+// the emitted module does not report the use of MultiMemories. Disabling the
+// multi-memories feature also prevents later passes from adding additional
+// memories.
+//
+// Also worth noting that we are diverging from the spec with regards to
+// handling load and store instructions. We are not trapping if the offset +
+// write size is larger than the length of the memory's data. Warning:
+// out-of-bounds loads and stores can read junk out of or corrupt other
+// memories instead of trapping.
+
+#include "ir/module-utils.h"
+#include "ir/names.h"
+#include "wasm-builder.h"
+#include <pass.h>
+#include <wasm.h>
+
+namespace wasm {
+
+struct MultiMemoryLowering : public Pass {
+  Module* wasm = nullptr;
+  // The name of the single memory that exists after this pass is run
+  Name combinedMemory;
+  // The type of the single memory
+  Type pointerType;
+  // Used to indicate the type of the single memory when creating instructions
+  // (memory.grow, memory.size) for that memory
+  Builder::MemoryInfo memoryInfo;
+  // If the combined memory is shared
+  bool isShared;
+  // The initial page size of the combined memory
+  Address totalInitialPages;
+  // The max page size of the combined memory
+  Address totalMaxPages;
+  // There is no offset for the first memory, so offsetGlobalNames will always
+  // have a size that is one less than the count of memories at the time this
+  // pass is run. Use helper getOffsetGlobal(Index) to index the vector
+  // conveniently without having to manipulate the index directly
+  std::vector<Name> offsetGlobalNames;
+  // Maps from the name of the memory to its index as seen in the
+  // module->memories vector
+  std::unordered_map<Name, Index> memoryIdxMap;
+  // A vector of the memory size function names that were created proactively
+  // for each memory
+  std::vector<Name> memorySizeNames;
+  // A vector of the memory grow functions that were created proactively for
+  // each memory
+  std::vector<Name> memoryGrowNames;
+
+  void run(Module* module) override {
+    module->features.disable(FeatureSet::MultiMemories);
+
+    // If there are no memories or 1 memory, skip this pass
+    if (module->memories.size() <= 1) {
+      return;
+    }
+
+    this->wasm = module;
+
+    prepCombinedMemory();
+    addOffsetGlobals();
+    adjustActiveDataSegmentOffsets();
+    createMemorySizeFunctions();
+    createMemoryGrowFunctions();
+    removeExistingMemories();
+    addCombinedMemory();
+
+    struct Replacer : public WalkerPass<PostWalker<Replacer>> {
+      MultiMemoryLowering& parent;
+      Builder builder;
+      Replacer(MultiMemoryLowering& parent, Module& wasm)
+        : parent(parent), builder(wasm) {}
+      // Avoid visiting the custom functions added by the parent pass
+      // MultiMemoryLowering
+      void walkFunction(Function* func) {
+        for (Name funcName : parent.memorySizeNames) {
+          if (funcName == func->name) {
+            return;
+          }
+        }
+        for (Name funcName : parent.memoryGrowNames) {
+          if (funcName == func->name) {
+            return;
+          }
+        }
+        super::walkFunction(func);
+      }
+
+      void visitMemoryGrow(MemoryGrow* curr) {
+        auto idx = parent.memoryIdxMap.at(curr->memory);
+        Name funcName = parent.memoryGrowNames[idx];
+        replaceCurrent(builder.makeCall(funcName, {curr->delta}, curr->type));
+      }
+
+      void visitMemorySize(MemorySize* curr) {
+        auto idx = parent.memoryIdxMap.at(curr->memory);
+        Name funcName = parent.memorySizeNames[idx];
+        replaceCurrent(builder.makeCall(funcName, {}, curr->type));
+      }
+
+      // TODO: Add an option to add bounds checks.
+      void visitLoad(Load* curr) {
+        auto idx = parent.memoryIdxMap.at(curr->memory);
+        auto global = parent.getOffsetGlobal(idx);
+        curr->memory = parent.combinedMemory;
+        if (!global) {
+          return;
+        }
+        curr->ptr = builder.makeBinary(
+          Abstract::getBinary(parent.pointerType, Abstract::Add),
+          builder.makeGlobalGet(global, parent.pointerType),
+          curr->ptr);
+      }
+
+      // We diverge from the spec here and are not trapping if the offset + type
+      // / 8 is larger than the length of the memory's data. Warning,
+      // out-of-bounds loads and stores can read junk out of or corrupt other
+      // memories instead of trapping
+      void visitStore(Store* curr) {
+        auto idx = parent.memoryIdxMap.at(curr->memory);
+        auto global = parent.getOffsetGlobal(idx);
+        curr->memory = parent.combinedMemory;
+        if (!global) {
+          return;
+        }
+        curr->ptr = builder.makeBinary(
+          Abstract::getBinary(parent.pointerType, Abstract::Add),
+          builder.makeGlobalGet(global, parent.pointerType),
+          curr->ptr);
+      }
+    };
+    Replacer(*this, *wasm).run(getPassRunner(), wasm);
+  }
+
+  // Returns the global name for the given idx. There is no global for the first
+  // idx, so an empty name is returned
+  Name getOffsetGlobal(Index idx) {
+    // There is no offset global for the first memory
+    if (idx == 0) {
+      return Name();
+    }
+
+    // Since there is no offset global for the first memory, we need to
+    // subtract one when indexing into the offsetGlobalName vector
+    return offsetGlobalNames[idx - 1];
+  }
+
+  // Whether the idx represents the last memory. Since there is no offset global
+  // for the first memory, the last memory is represented by the size of
+  // offsetGlobalNames
+  bool isLastMemory(Index idx) { return idx == offsetGlobalNames.size(); }
+
+  void prepCombinedMemory() {
+    pointerType = wasm->memories[0]->indexType;
+    memoryInfo = pointerType == Type::i32 ? Builder::MemoryInfo::Memory32
+                                          : Builder::MemoryInfo::Memory64;
+    isShared = wasm->memories[0]->shared;
+    for (auto& memory : wasm->memories) {
+      // We are assuming that each memory is configured the same as the first
+      // and assert if any of the memories does not match this configuration
+      assert(memory->shared == isShared);
+      assert(memory->indexType == pointerType);
+
+      // Calculating the total initial and max page size for the combined memory
+      // by totaling the initial and max page sizes for the memories in the
+      // module
+      totalInitialPages = totalInitialPages + memory->initial;
+      if (memory->hasMax()) {
+        totalMaxPages = totalMaxPages + memory->max;
+      }
+    }
+    // Ensuring valid initial and max page sizes that do not exceed the number
+    // of pages addressable by the pointerType
+    Address maxSize =
+      pointerType == Type::i32 ? Memory::kMaxSize32 : Memory::kMaxSize64;
+    if (totalMaxPages > maxSize || totalMaxPages == 0) {
+      totalMaxPages = Memory::kUnlimitedSize;
+    }
+    if (totalInitialPages > totalMaxPages) {
+      totalInitialPages = totalMaxPages;
+    }
+
+    // Creating the combined memory name so we can reference the combined memory
+    // in subsequent instructions before it is added to the module
+    combinedMemory = Names::getValidMemoryName(*wasm, "combined_memory");
+  }
+
+  void addOffsetGlobals() {
+    auto addGlobal = [&](Name name, size_t offset) {
+      auto global = Builder::makeGlobal(
+        name,
+        pointerType,
+        Builder(*wasm).makeConst(Literal::makeFromInt64(offset, pointerType)),
+        Builder::Mutable);
+      wasm->addGlobal(std::move(global));
+    };
+
+    size_t offsetRunningTotal = 0;
+    for (Index i = 0; i < wasm->memories.size(); i++) {
+      auto& memory = wasm->memories[i];
+      memoryIdxMap[memory->name] = i;
+      // We don't need a page offset global for the first memory as it's always
+      // 0
+      if (i != 0) {
+        Name name = Names::getValidGlobalName(
+          *wasm, memory->name.toString() + "_byte_offset");
+        offsetGlobalNames.push_back(std::move(name));
+        addGlobal(name, offsetRunningTotal * Memory::kPageSize);
+      }
+      offsetRunningTotal += memory->initial;
+    }
+  }
+
+  void adjustActiveDataSegmentOffsets() {
+    Builder builder(*wasm);
+    ModuleUtils::iterActiveDataSegments(*wasm, [&](DataSegment* dataSegment) {
+      assert(dataSegment->offset->is<Const>() &&
+             "TODO: handle non-const segment offsets");
+      auto idx = memoryIdxMap.at(dataSegment->memory);
+      dataSegment->memory = combinedMemory;
+      // No need to update the offset of data segments for the first memory
+      if (idx != 0) {
+        auto offsetGlobalName = getOffsetGlobal(idx);
+        assert(wasm->features.hasExtendedConst());
+        dataSegment->offset = builder.makeBinary(
+          Abstract::getBinary(pointerType, Abstract::Add),
+          builder.makeGlobalGet(offsetGlobalName, pointerType),
+          dataSegment->offset);
+      }
+    });
+  }
+
+  void createMemorySizeFunctions() {
+    for (Index i = 0; i < wasm->memories.size(); i++) {
+      auto function = memorySize(i, wasm->memories[i]->name);
+      memorySizeNames.push_back(function->name);
+      wasm->addFunction(std::move(function));
+    }
+  }
+
+  void createMemoryGrowFunctions() {
+    for (Index i = 0; i < wasm->memories.size(); i++) {
+      auto function = memoryGrow(i, wasm->memories[i]->name);
+      memoryGrowNames.push_back(function->name);
+      wasm->addFunction(std::move(function));
+    }
+  }
+
+  // This function replaces memory.grow instruction calls in the wasm module.
+  // Because the multiple discrete memories are lowered into a single memory,
+  // we need to adjust offsets as a particular memory receives an
+  // instruction to grow.
+  std::unique_ptr<Function> memoryGrow(Index memIdx, Name memoryName) {
+    Builder builder(*wasm);
+    Name name = memoryName.toString() + "_grow";
+    Name functionName = Names::getValidFunctionName(*wasm, name);
+    auto function = Builder::makeFunction(
+      functionName, Signature(pointerType, pointerType), {});
+    function->setLocalName(0, "page_delta");
+    auto pageSizeConst = [&]() {
+      return builder.makeConst(Literal(Memory::kPageSize));
+    };
+    auto getOffsetDelta = [&]() {
+      return builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Mul),
+                                builder.makeLocalGet(0, pointerType),
+                                pageSizeConst());
+    };
+    auto getMoveSource = [&](Name global) {
+      return builder.makeGlobalGet(global, pointerType);
+    };
+    Expression* functionBody;
+    Index sizeLocal = -1;
+
+    Index returnLocal =
+      Builder::addVar(function.get(), "return_size", pointerType);
+    functionBody = builder.blockify(builder.makeLocalSet(
+      returnLocal, builder.makeCall(memorySizeNames[memIdx], {}, pointerType)));
+
+    if (!isLastMemory(memIdx)) {
+      sizeLocal = Builder::addVar(function.get(), "memory_size", pointerType);
+      functionBody = builder.blockify(
+        functionBody,
+        builder.makeLocalSet(
+          sizeLocal, builder.makeMemorySize(combinedMemory, memoryInfo)));
+    }
+
+    // Attempt to grow the combinedMemory. If -1 returns, enough memory could
+    // not be allocated, so return -1.
+    functionBody = builder.blockify(
+      functionBody,
+      builder.makeIf(
+        builder.makeBinary(
+          EqInt32,
+          builder.makeMemoryGrow(
+            builder.makeLocalGet(0, pointerType), combinedMemory, memoryInfo),
+          builder.makeConst(-1)),
+        builder.makeReturn(builder.makeConst(-1))));
+
+    // If we are not growing the last memory, then we need to copy data,
+    // shifting it over to accomodate the increase from page_delta
+    if (!isLastMemory(memIdx)) {
+      // This offset is the starting pt for copying
+      auto offsetGlobalName = getOffsetGlobal(memIdx + 1);
+      functionBody = builder.blockify(
+        functionBody,
+        builder.makeMemoryCopy(
+          // destination
+          builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Add),
+                             getMoveSource(offsetGlobalName),
+                             getOffsetDelta()),
+          // source
+          getMoveSource(offsetGlobalName),
+          // size
+          builder.makeBinary(
+            Abstract::getBinary(pointerType, Abstract::Sub),
+            builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Mul),
+                               builder.makeLocalGet(sizeLocal, pointerType),
+                               pageSizeConst()),
+            getMoveSource(offsetGlobalName)),
+          combinedMemory,
+          combinedMemory));
+    }
+
+    // Adjust the offsets of the globals impacted by the memory.grow call
+    for (Index i = memIdx; i < offsetGlobalNames.size(); i++) {
+      auto& offsetGlobalName = offsetGlobalNames[i];
+      functionBody = builder.blockify(
+        functionBody,
+        builder.makeGlobalSet(
+          offsetGlobalName,
+          builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Add),
+                             getMoveSource(offsetGlobalName),
+                             getOffsetDelta())));
+    }
+
+    functionBody = builder.blockify(
+      functionBody, builder.makeLocalGet(returnLocal, pointerType));
+
+    function->body = functionBody;
+    return function;
+  }
+
+  // This function replaces memory.size instructions with a function that can
+  // return the size of each memory as if each was discrete and separate.
+  std::unique_ptr<Function> memorySize(Index memIdx, Name memoryName) {
+    Builder builder(*wasm);
+    Name name = memoryName.toString() + "_size";
+    Name functionName = Names::getValidFunctionName(*wasm, name);
+    auto function = Builder::makeFunction(
+      functionName, Signature(Type::none, pointerType), {});
+    Expression* functionBody;
+    auto pageSizeConst = [&]() {
+      return builder.makeConst(Literal(Memory::kPageSize));
+    };
+    auto getOffsetInPageUnits = [&](Name global) {
+      return builder.makeBinary(
+        Abstract::getBinary(pointerType, Abstract::DivU),
+        builder.makeGlobalGet(global, pointerType),
+        pageSizeConst());
+    };
+
+    // offsetGlobalNames does not keep track of a global for the offset of
+    // wasm->memories[0] because it's always 0. As a result, the below
+    // calculations that involve offsetGlobalNames are intrinsically "offset".
+    // Thus, offsetGlobalNames[0] is the offset for wasm->memories[1] and
+    // the size of wasm->memories[0].
+    if (memIdx == 0) {
+      auto offsetGlobalName = getOffsetGlobal(1);
+      functionBody = builder.blockify(
+        builder.makeReturn(getOffsetInPageUnits(offsetGlobalName)));
+    } else if (isLastMemory(memIdx)) {
+      auto offsetGlobalName = getOffsetGlobal(memIdx);
+      functionBody = builder.blockify(builder.makeReturn(
+        builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Sub),
+                           builder.makeMemorySize(combinedMemory, memoryInfo),
+                           getOffsetInPageUnits(offsetGlobalName))));
+    } else {
+      auto offsetGlobalName = getOffsetGlobal(memIdx);
+      auto nextOffsetGlobalName = getOffsetGlobal(memIdx + 1);
+      functionBody = builder.blockify(builder.makeReturn(
+        builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Sub),
+                           getOffsetInPageUnits(nextOffsetGlobalName),
+                           getOffsetInPageUnits(offsetGlobalName))));
+    }
+
+    function->body = functionBody;
+    return function;
+  }
+
+  void removeExistingMemories() {
+    wasm->removeMemories([&](Memory* curr) { return true; });
+  }
+
+  void addCombinedMemory() {
+    auto memory = Builder::makeMemory(combinedMemory);
+    memory->shared = isShared;
+    memory->indexType = pointerType;
+    memory->initial = totalInitialPages;
+    memory->max = totalMaxPages;
+    wasm->addMemory(std::move(memory));
+  }
+};
+
+Pass* createMultiMemoryLoweringPass() { return new MultiMemoryLowering(); }
+
+} // namespace wasm
diff --git a/src/passes/NameList.cpp b/src/passes/NameList.cpp
index 1051a31..8556065 100644
--- a/src/passes/NameList.cpp
+++ b/src/passes/NameList.cpp
@@ -26,7 +26,9 @@
 namespace wasm {
 
 struct NameList : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  bool modifiesBinaryenIR() override { return false; }
+
+  void run(Module* module) override {
     ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) {
       std::cout << "    " << func->name << " : "
                 << Measurer::measure(func->body) << '\n';
diff --git a/src/passes/NameTypes.cpp b/src/passes/NameTypes.cpp
index b855e4c..fcf6df5 100644
--- a/src/passes/NameTypes.cpp
+++ b/src/passes/NameTypes.cpp
@@ -28,7 +28,9 @@ namespace wasm {
 static const size_t NameLenLimit = 20;
 
 struct NameTypes : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
     // Find all the types.
     std::vector<HeapType> types = ModuleUtils::collectHeapTypes(*module);
 
diff --git a/src/passes/OnceReduction.cpp b/src/passes/OnceReduction.cpp
index fd38aea..c1500f1 100644
--- a/src/passes/OnceReduction.cpp
+++ b/src/passes/OnceReduction.cpp
@@ -84,9 +84,13 @@ struct OptInfo {
 struct Scanner : public WalkerPass<PostWalker<Scanner>> {
   bool isFunctionParallel() override { return true; }
 
+  bool modifiesBinaryenIR() override { return false; }
+
   Scanner(OptInfo& optInfo) : optInfo(optInfo) {}
 
-  Scanner* create() override { return new Scanner(optInfo); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<Scanner>(optInfo);
+  }
 
   // All the globals we read from. Any read of a global prevents us from
   // optimizing, unless it is the single read at the top of an "only" function
@@ -217,7 +221,9 @@ struct Optimizer
 
   Optimizer(OptInfo& optInfo) : optInfo(optInfo) {}
 
-  Optimizer* create() override { return new Optimizer(optInfo); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<Optimizer>(optInfo);
+  }
 
   void visitGlobalSet(GlobalSet* curr) {
     if (currBasicBlock) {
@@ -342,7 +348,7 @@ private:
 } // anonymous namespace
 
 struct OnceReduction : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     OptInfo optInfo;
 
     // Fill out the initial data.
@@ -372,7 +378,7 @@ struct OnceReduction : public Pass {
     }
 
     // Scan the module to find out which globals and functions are "once".
-    Scanner(optInfo).run(runner, module);
+    Scanner(optInfo).run(getPassRunner(), module);
 
     // Combine the information. We found which globals appear to be "once", but
     // other information may have proven they are not so, in fact. Specifically,
@@ -417,7 +423,7 @@ struct OnceReduction : public Pass {
         optInfo.newOnceGlobalsSetInFuncs[func->name];
       }
 
-      Optimizer(optInfo).run(runner, module);
+      Optimizer(optInfo).run(getPassRunner(), module);
 
       optInfo.onceGlobalsSetInFuncs =
         std::move(optInfo.newOnceGlobalsSetInFuncs);
diff --git a/src/passes/OptimizeAddedConstants.cpp b/src/passes/OptimizeAddedConstants.cpp
index 0af23c1..aadff51 100644
--- a/src/passes/OptimizeAddedConstants.cpp
+++ b/src/passes/OptimizeAddedConstants.cpp
@@ -45,7 +45,9 @@ public:
                         T* curr,
                         Module* module,
                         LocalGraph* localGraph)
-    : parent(parent), curr(curr), module(module), localGraph(localGraph) {}
+    : parent(parent), curr(curr), module(module), localGraph(localGraph) {
+    memory64 = module->getMemory(curr->memory)->is64();
+  }
 
   // Tries to optimize, and returns whether we propagated a change.
   bool optimize() {
@@ -56,7 +58,7 @@ public:
       return false;
     }
     if (auto* add = curr->ptr->template dynCast<Binary>()) {
-      if (add->op == AddInt32) {
+      if (add->op == AddInt32 || add->op == AddInt64) {
         // Look for a constant on both sides.
         if (tryToOptimizeConstant(add->right, add->left) ||
             tryToOptimizeConstant(add->left, add->right)) {
@@ -110,6 +112,7 @@ private:
   T* curr;
   Module* module;
   LocalGraph* localGraph;
+  bool memory64;
 
   void optimizeConstantPointer() {
     // The constant and an offset are interchangeable:
@@ -123,11 +126,23 @@ private:
       // code may know that is valid, even if we can't. Only handle the
       // obviously valid case where an overflow can't occur.
       auto* c = curr->ptr->template cast<Const>();
-      uint32_t base = c->value.geti32();
-      uint32_t offset = curr->offset;
-      if (uint64_t(base) + uint64_t(offset) < (uint64_t(1) << 32)) {
-        c->value = c->value.add(Literal(uint32_t(curr->offset)));
-        curr->offset = 0;
+      if (memory64) {
+        uint64_t base = c->value.geti64();
+        uint64_t offset = curr->offset;
+
+        uint64_t max = std::numeric_limits<uint64_t>::max();
+        bool overflow = (base > max - offset);
+        if (!overflow) {
+          c->value = c->value.add(Literal(offset));
+          curr->offset = 0;
+        }
+      } else {
+        uint32_t base = c->value.geti32();
+        uint32_t offset = curr->offset;
+        if (uint64_t(base) + uint64_t(offset) < (uint64_t(1) << 32)) {
+          c->value = c->value.add(Literal(uint32_t(curr->offset)));
+          curr->offset = 0;
+        }
       }
     }
   }
@@ -222,9 +237,9 @@ private:
 
   // Sees if we can optimize a particular constant.
   Result canOptimizeConstant(Literal literal) {
-    auto value = literal.geti32();
+    uint64_t value = literal.getInteger();
     // Avoid uninteresting corner cases with peculiar offsets.
-    if (value >= 0 && value < PassOptions::LowMemoryBound) {
+    if (value < PassOptions::LowMemoryBound) {
       // The total offset must not allow reaching reasonable memory
       // by overflowing.
       auto total = curr->offset + value;
@@ -242,11 +257,16 @@ struct OptimizeAddedConstants
                  UnifiedExpressionVisitor<OptimizeAddedConstants>>> {
   bool isFunctionParallel() override { return true; }
 
+  // This pass operates on linear memory, and does not affect reference locals.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   bool propagate;
 
   OptimizeAddedConstants(bool propagate) : propagate(propagate) {}
 
-  Pass* create() override { return new OptimizeAddedConstants(propagate); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<OptimizeAddedConstants>(propagate);
+  }
 
   void visitLoad(Load* curr) {
     MemoryAccessOptimizer<OptimizeAddedConstants, Load> optimizer(
diff --git a/src/passes/OptimizeCasts.cpp b/src/passes/OptimizeCasts.cpp
new file mode 100644
index 0000000..8541101
--- /dev/null
+++ b/src/passes/OptimizeCasts.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Refine uses of locals where possible. For example, consider this:
+//
+//  (some.operation
+//    (ref.cast .. (local.get $ref))
+//    (local.get $ref)
+//  )
+//
+// The second use might as well use the refined/cast value as well:
+//
+//  (some.operation
+//    (local.tee $temp
+//      (ref.cast .. (local.get $ref))
+//    )
+//    (local.get $temp)
+//  )
+//
+// This change adds a local but it switches some local.gets to use a local of a
+// more refined type. That can help other optimizations later.
+//
+// An example of an important pattern this handles are itable calls:
+//
+//  (call_ref
+//    (ref.cast $actual.type
+//      (local.get $object)
+//    )
+//    (struct.get $vtable ..
+//      (ref.cast $vtable
+//        (struct.get $itable ..
+//          (local.get $object)
+//        )
+//      )
+//    )
+//  )
+//
+// We cast to the actual type for the |this| parameter, but we technically do
+// not need to do so for reading its itable - since the itable may be of a
+// generic type, and we cast the vtable afterwards anyhow. But since we cast
+// |this|, we can use the cast value for the itable get, which may then lead to
+// removing the vtable cast after we refine the itable type. And that can lead
+// to devirtualization later.
+//
+// Closely related things appear in other passes:
+//
+//  * SimplifyLocals will find locals already containing a more refined type and
+//    switch to them. RedundantSetElimination does the same across basic blocks.
+//    In theory one of them could be extended to also add new locals, and then
+//    they would be doing something similar to this pass.
+//  * LocalCSE finds repeated expressions and stores them in locals for use
+//    later. In theory that pass could be extended to look not for exact copies
+//    but for equivalent things through a cast, and then it would be doing
+//    something similar to this pass.
+//
+// However, while those other passes could be extended to cover what this pass
+// does, we will have further cast-specific optimizations to add, which make
+// sense in new pass anyhow, and things should be simpler overall to keep such
+// casts all in one pass, here.
+//
+// TODO: Move casts earlier in a basic block as well, at least in traps-never-
+//       happen mode where we can assume they never fail.
+// TODO: Look past individual basic blocks?
+// TODO: Look at LocalSet as well and not just Get. That would add some overlap
+//       with the other passes mentioned above, but once we do things like
+//       moving casts earlier as in the other TODO, we'd be doing uniquely
+//       useful things with LocalSet here.
+//
+
+#include "ir/linear-execution.h"
+#include "ir/properties.h"
+#include "ir/utils.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+// Find the best casted verisons of local.gets: other local.gets with the same
+// value, but cast to a more refined type.
+struct BestCastFinder : public LinearExecutionWalker<BestCastFinder> {
+
+  PassOptions options;
+
+  // Map local indices to the most refined downcastings of local.gets from those
+  // indices.
+  //
+  // This is tracked in each basic block, and cleared between them.
+  std::unordered_map<Index, Expression*> mostCastedGets;
+
+  // For each most-downcasted local.get, a vector of other local.gets that could
+  // be replaced with gets of the downcasted value.
+  //
+  // This is tracked until the end of the entire function, and contains the
+  // information we need to optimize later. That is, entries here are things we
+  // want to apply.
+  std::unordered_map<Expression*, std::vector<LocalGet*>> lessCastedGets;
+
+  static void doNoteNonLinear(BestCastFinder* self, Expression** currp) {
+    self->mostCastedGets.clear();
+  }
+
+  void visitLocalSet(LocalSet* curr) {
+    // Clear any information about this local; it has a new value here.
+    mostCastedGets.erase(curr->index);
+  }
+
+  void visitLocalGet(LocalGet* curr) {
+    auto iter = mostCastedGets.find(curr->index);
+    if (iter != mostCastedGets.end()) {
+      auto* bestCast = iter->second;
+      if (curr->type != bestCast->type &&
+          Type::isSubType(bestCast->type, curr->type)) {
+        // The best cast has a more refined type, note that we want to use it.
+        lessCastedGets[bestCast].push_back(curr);
+      }
+    }
+  }
+
+  void visitRefAs(RefAs* curr) { handleRefinement(curr); }
+
+  void visitRefCast(RefCast* curr) { handleRefinement(curr); }
+
+  void handleRefinement(Expression* curr) {
+    auto* fallthrough = Properties::getFallthrough(curr, options, *getModule());
+    if (auto* get = fallthrough->dynCast<LocalGet>()) {
+      auto*& bestCast = mostCastedGets[get->index];
+      if (!bestCast) {
+        // This is the first.
+        bestCast = curr;
+        return;
+      }
+
+      // See if we are better than the current best.
+      if (curr->type != bestCast->type &&
+          Type::isSubType(curr->type, bestCast->type)) {
+        bestCast = curr;
+      }
+    }
+  }
+};
+
+// Given a set of best casts, apply them: save each best cast in a local and use
+// it in the places that want to.
+//
+// It is simpler to do this in another pass after BestCastFinder so that we do
+// not need to worry about corner cases with invalidation of pointers in things
+// we've already walked past.
+struct FindingApplier : public PostWalker<FindingApplier> {
+  BestCastFinder& finder;
+
+  FindingApplier(BestCastFinder& finder) : finder(finder) {}
+
+  void visitRefAs(RefAs* curr) { handleRefinement(curr); }
+
+  void visitRefCast(RefCast* curr) { handleRefinement(curr); }
+
+  void handleRefinement(Expression* curr) {
+    auto iter = finder.lessCastedGets.find(curr);
+    if (iter == finder.lessCastedGets.end()) {
+      return;
+    }
+
+    // This expression was the best cast for some gets. Add a new local to
+    // store this value, then use it for the gets.
+    auto var = Builder::addVar(getFunction(), curr->type);
+    auto& gets = iter->second;
+    for (auto* get : gets) {
+      get->index = var;
+      get->type = curr->type;
+    }
+
+    // Replace ourselves with a tee.
+    replaceCurrent(Builder(*getModule()).makeLocalTee(var, curr, curr->type));
+  }
+};
+
+} // anonymous namespace
+
+struct OptimizeCasts : public WalkerPass<PostWalker<OptimizeCasts>> {
+  bool isFunctionParallel() override { return true; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<OptimizeCasts>();
+  }
+
+  void doWalkFunction(Function* func) {
+    if (!getModule()->features.hasGC()) {
+      return;
+    }
+
+    // First, find the best casts that we want to use.
+    BestCastFinder finder;
+    finder.options = getPassOptions();
+    finder.walkFunctionInModule(func, getModule());
+
+    if (finder.lessCastedGets.empty()) {
+      // Nothing to do.
+      return;
+    }
+
+    // Apply the requests: use the best casts.
+    FindingApplier applier(finder);
+    applier.walkFunctionInModule(func, getModule());
+
+    // LocalGet type changes must be propagated.
+    ReFinalize().walkFunctionInModule(func, getModule());
+  }
+};
+
+Pass* createOptimizeCastsPass() { return new OptimizeCasts(); }
+
+} // namespace wasm
diff --git a/src/passes/OptimizeForJS.cpp b/src/passes/OptimizeForJS.cpp
index 589374c..0f68412 100644
--- a/src/passes/OptimizeForJS.cpp
+++ b/src/passes/OptimizeForJS.cpp
@@ -28,7 +28,9 @@ namespace wasm {
 struct OptimizeForJSPass : public WalkerPass<PostWalker<OptimizeForJSPass>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new OptimizeForJSPass; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<OptimizeForJSPass>();
+  }
 
   void visitBinary(Binary* curr) {
     using namespace Abstract;
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 4b49921..f96d4d3 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -24,7 +24,9 @@
 
 #include <ir/abstract.h>
 #include <ir/bits.h>
+#include <ir/boolean.h>
 #include <ir/cost.h>
+#include <ir/drop.h>
 #include <ir/effects.h>
 #include <ir/eh-utils.h>
 #include <ir/find_all.h>
@@ -42,11 +44,20 @@
 #include <support/threads.h>
 #include <wasm.h>
 
+#include "call-utils.h"
+
 // TODO: Use the new sign-extension opcodes where appropriate. This needs to be
 // conditionalized on the availability of atomics.
 
 namespace wasm {
 
+static Index getBitsForType(Type type) {
+  if (!type.isNumber()) {
+    return -1;
+  }
+  return type.getByteSize() * 8;
+}
+
 // Useful information about locals
 struct LocalInfo {
   static const Index kUnknown = Index(-1);
@@ -119,20 +130,6 @@ struct LocalScanner : PostWalker<LocalScanner> {
   // define this for the templated getMaxBits method. we know nothing here yet
   // about locals, so return the maxes
   Index getMaxBitsForLocal(LocalGet* get) { return getBitsForType(get->type); }
-
-  Index getBitsForType(Type type) {
-    if (!type.isBasic()) {
-      return -1;
-    }
-    switch (type.getBasic()) {
-      case Type::i32:
-        return 32;
-      case Type::i64:
-        return 64;
-      default:
-        return -1;
-    }
-  }
 };
 
 namespace {
@@ -204,7 +201,9 @@ struct OptimizeInstructions
   : public WalkerPass<PostWalker<OptimizeInstructions>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new OptimizeInstructions; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<OptimizeInstructions>();
+  }
 
   bool fastMath;
 
@@ -234,9 +233,6 @@ struct OptimizeInstructions
       optimizer.walkFunction(func);
     }
 
-    // Some patterns create locals (like when we use getResultOfFirst), which we
-    // may need to fix up.
-    TypeUpdating::handleNonDefaultableLocals(func, *getModule());
     // Some patterns create blocks that can interfere 'catch' and 'pop', nesting
     // the 'pop' into a block making it invalid.
     EHUtils::handleBlockNestedPops(func, *getModule());
@@ -419,6 +415,32 @@ struct OptimizeInstructions
           }
         }
       }
+      {
+        // -x + y   ==>   y - x
+        //   where  x, y  are floating points
+        Expression *x, *y;
+        if (matches(curr, binary(Add, unary(Neg, any(&x)), any(&y))) &&
+            canReorder(x, y)) {
+          curr->op = Abstract::getBinary(curr->type, Sub);
+          curr->left = x;
+          std::swap(curr->left, curr->right);
+          return replaceCurrent(curr);
+        }
+      }
+      {
+        // x + (-y)   ==>   x - y
+        // x - (-y)   ==>   x + y
+        //   where  x, y  are floating points
+        Expression* y;
+        if (matches(curr, binary(Add, any(), unary(Neg, any(&y)))) ||
+            matches(curr, binary(Sub, any(), unary(Neg, any(&y))))) {
+          curr->op = Abstract::getBinary(
+            curr->type,
+            curr->op == Abstract::getBinary(curr->type, Add) ? Sub : Add);
+          curr->right = y;
+          return replaceCurrent(curr);
+        }
+      }
       {
         // -x * -y   ==>   x * y
         //   where  x, y  are integers
@@ -474,10 +496,28 @@ struct OptimizeInstructions
                 break;
             }
           }
+          // i32(x) << 24 >> 24   ==>   i32.extend8_s(x)
+          // i32(x) << 16 >> 16   ==>   i32.extend16_s(x)
+          if (matches(curr,
+                      binary(ShrSInt32,
+                             binary(ShlInt32, any(&x), i32(&c1)),
+                             i32(&c2))) &&
+              Bits::getEffectiveShifts(c1) == Bits::getEffectiveShifts(c2)) {
+            switch (32 - Bits::getEffectiveShifts(c1)) {
+              case 8:
+                return replaceCurrent(builder.makeUnary(ExtendS8Int32, x));
+              case 16:
+                return replaceCurrent(builder.makeUnary(ExtendS16Int32, x));
+              default:
+                break;
+            }
+          }
         }
       }
       {
         // unsigned(x) >= 0   =>   i32(1)
+        // TODO: Use getDroppedChildrenAndAppend() here, so we can optimize even
+        //       if pure.
         Const* c;
         Expression* x;
         if (matches(curr, binary(GeU, pure(&x), ival(&c))) &&
@@ -662,40 +702,8 @@ struct OptimizeInstructions
       if (auto* ret = optimizeWithConstantOnRight(curr)) {
         return replaceCurrent(ret);
       }
-      // the square of some operations can be merged
-      if (auto* left = curr->left->dynCast<Binary>()) {
-        if (left->op == curr->op) {
-          if (auto* leftRight = left->right->dynCast<Const>()) {
-            if (left->op == AndInt32 || left->op == AndInt64) {
-              leftRight->value = leftRight->value.and_(right->value);
-              return replaceCurrent(left);
-            } else if (left->op == OrInt32 || left->op == OrInt64) {
-              leftRight->value = leftRight->value.or_(right->value);
-              return replaceCurrent(left);
-            } else if (left->op == XorInt32 || left->op == XorInt64) {
-              leftRight->value = leftRight->value.xor_(right->value);
-              return replaceCurrent(left);
-            } else if (left->op == MulInt32 || left->op == MulInt64) {
-              leftRight->value = leftRight->value.mul(right->value);
-              return replaceCurrent(left);
-
-              // TODO:
-              // handle signed / unsigned divisions. They are more complex
-            } else if (left->op == ShlInt32 || left->op == ShrUInt32 ||
-                       left->op == ShrSInt32 || left->op == ShlInt64 ||
-                       left->op == ShrUInt64 || left->op == ShrSInt64) {
-              // shifts only use an effective amount from the constant, so
-              // adding must be done carefully
-              auto total = Bits::getEffectiveShifts(leftRight) +
-                           Bits::getEffectiveShifts(right);
-              if (total == Bits::getEffectiveShifts(total, right->type)) {
-                // no overflow, we can do this
-                leftRight->value = Literal::makeFromInt32(total, right->type);
-                return replaceCurrent(left);
-              } // TODO: handle overflows
-            }
-          }
-        }
+      if (auto* ret = optimizeDoubletonWithConstantOnRight(curr)) {
+        return replaceCurrent(ret);
       }
       if (right->type == Type::i32) {
         BinaryOp op;
@@ -868,15 +876,14 @@ struct OptimizeInstructions
         }
       }
       {
-        // i32.wrap_i64(i64.extend_i32_s(x))  =>  x
+        // i32.wrap_i64 can be removed if the operations inside it do not
+        // actually require 64 bits, e.g.:
+        //
         // i32.wrap_i64(i64.extend_i32_u(x))  =>  x
-        Unary* inner;
-        Expression* x;
-        if (matches(curr,
-                    unary(WrapInt64, unary(&inner, ExtendSInt32, any(&x)))) ||
-            matches(curr,
-                    unary(WrapInt64, unary(&inner, ExtendUInt32, any(&x))))) {
-          return replaceCurrent(x);
+        if (matches(curr, unary(WrapInt64, any()))) {
+          if (auto* ret = optimizeWrappedResult(curr)) {
+            return replaceCurrent(ret);
+          }
         }
       }
       {
@@ -887,10 +894,25 @@ struct OptimizeInstructions
         if (matches(curr, unary(EqZInt32, unary(&inner, WrapInt64, any(&x)))) &&
             Bits::getMaxBits(x, this) <= 32) {
           inner->op = EqZInt64;
-          inner->value = x;
           return replaceCurrent(inner);
         }
       }
+      {
+        // i32.eqz(i32.eqz(x))  =>  i32(x) != 0
+        // i32.eqz(i64.eqz(x))  =>  i64(x) != 0
+        //   iff shinkLevel == 0
+        // (1 instruction instead of 2, but 1 more byte)
+        if (getPassRunner()->options.shrinkLevel == 0) {
+          Expression* x;
+          if (matches(curr, unary(EqZInt32, unary(EqZ, any(&x))))) {
+            Builder builder(*getModule());
+            return replaceCurrent(builder.makeBinary(
+              getBinary(x->type, Ne),
+              x,
+              builder.makeConst(Literal::makeZero(x->type))));
+          }
+        }
+      }
       {
         // i64.extend_i32_s(i32.wrap_i64(x))  =>  x
         //   where maxBits(x) <= 31
@@ -912,12 +934,10 @@ struct OptimizeInstructions
       if (getModule()->features.hasSignExt()) {
         // i64.extend_i32_s(i32.wrap_i64(x))  =>  i64.extend32_s(x)
         Unary* inner;
-        Expression* x;
         if (matches(curr,
-                    unary(ExtendSInt32, unary(&inner, WrapInt64, any(&x))))) {
+                    unary(ExtendSInt32, unary(&inner, WrapInt64, any())))) {
           inner->op = ExtendS32Int64;
           inner->type = Type::i64;
-          inner->value = x;
           return replaceCurrent(inner);
         }
       }
@@ -1018,7 +1038,7 @@ struct OptimizeInstructions
       if (auto* binary = curr->value->dynCast<Binary>()) {
         if ((binary->op == Abstract::getBinary(binary->type, Abstract::Mul) ||
              binary->op == Abstract::getBinary(binary->type, Abstract::DivS)) &&
-            ExpressionAnalyzer::equal(binary->left, binary->right)) {
+            areConsecutiveInputsEqual(binary->left, binary->right)) {
           return replaceCurrent(binary);
         }
         // abs(0 - x)   ==>   abs(x),
@@ -1038,6 +1058,10 @@ struct OptimizeInstructions
     if (auto* ret = deduplicateUnary(curr)) {
       return replaceCurrent(ret);
     }
+
+    if (auto* ret = simplifyRoundingsAndConversions(curr)) {
+      return replaceCurrent(ret);
+    }
   }
 
   void visitSelect(Select* curr) {
@@ -1158,14 +1182,14 @@ struct OptimizeInstructions
     if (curr->type == Type::unreachable) {
       return;
     }
-    optimizeMemoryAccess(curr->ptr, curr->offset);
+    optimizeMemoryAccess(curr->ptr, curr->offset, curr->memory);
   }
 
   void visitStore(Store* curr) {
     if (curr->type == Type::unreachable) {
       return;
     }
-    optimizeMemoryAccess(curr->ptr, curr->offset);
+    optimizeMemoryAccess(curr->ptr, curr->offset, curr->memory);
     optimizeStoredValue(curr->value, curr->bytes);
     if (auto* unary = curr->value->dynCast<Unary>()) {
       if (unary->op == WrapInt64) {
@@ -1243,6 +1267,10 @@ struct OptimizeInstructions
   }
 
   void visitCallRef(CallRef* curr) {
+    skipNonNullCast(curr->target);
+    if (trapOnNull(curr, curr->target)) {
+      return;
+    }
     if (curr->target->type == Type::unreachable) {
       // The call_ref is not reached; leave this for DCE.
       return;
@@ -1334,14 +1362,208 @@ struct OptimizeInstructions
       curr->operands.back() = builder.makeBlock({set, drop, get});
       replaceCurrent(builder.makeCall(
         ref->func, curr->operands, curr->type, curr->isReturn));
+      return;
+    }
+
+    // If the target is a select of two different constants, we can emit an if
+    // over two direct calls.
+    if (auto* calls = CallUtils::convertToDirectCalls(
+          curr,
+          [](Expression* target) -> CallUtils::IndirectCallInfo {
+            if (auto* refFunc = target->dynCast<RefFunc>()) {
+              return CallUtils::Known{refFunc->func};
+            }
+            return CallUtils::Unknown{};
+          },
+          *getFunction(),
+          *getModule())) {
+      replaceCurrent(calls);
+    }
+  }
+
+  // Note on removing casts (which the following utilities, skipNonNullCast and
+  // skipCast do): removing a cast is potentially dangerous, as it removes
+  // information from the IR. For example:
+  //
+  //  (ref.is_func
+  //    (ref.as_func
+  //      (local.get $anyref)))
+  //
+  // The local has no useful type info here (it is anyref). The cast forces it
+  // to be a function, so we know that if we do not trap then the ref.is will
+  // definitely be 1. But if we removed the ref.as first (which we can do in
+  // traps-never-happen mode) then we'd not have the type info we need to
+  // optimize that way.
+  //
+  // To avoid such risks we should keep in mind the following:
+  //
+  //  * Before removing a cast we should use its type information in the best
+  //    way we can. Only after doing so should a cast be removed. In the exmaple
+  //    above, that means first seeing that the ref.is must return 1, and only
+  //    then possibly removing the ref.as.
+  //  * Do not remove a cast if removing it might remove useful information for
+  //    others. For example,
+  //
+  //      (ref.cast $A
+  //        (ref.as_non_null ..))
+  //
+  //    If we remove the inner cast then the outer cast becomes nullable. That
+  //    means we'd be throwing away useful information, which we should not do,
+  //    even in traps-never-happen mode and even if the wasm would validate
+  //    without the cast. Only if we saw that the parents of the outer cast
+  //    cannot benefit from non-nullability should we remove it.
+  //    Another example:
+  //
+  //      (struct.get $A 0
+  //        (ref.cast $B ..))
+  //
+  //    The cast only changes the type of the reference, which is consumed in
+  //    this expression and so we don't have more parents to consider. But it is
+  //    risky to remove this cast, since e.g. GUFA benefits from such info:
+  //    it tells GUFA that we are reading from a $B here, and not the supertype
+  //    $A. If $B may contain fewer values in field 0 than $A, then GUFA might
+  //    be able to optimize better with this cast. Now, in traps-never-happen
+  //    mode we can assume that only $B can arrive here, which means GUFA might
+  //    be able to infer that even without the cast - but it might not, if we
+  //    hit a limitation of GUFA. Some code patterns simply cannot be expected
+  //    to be always inferred, say if a data structure has a tagged variant:
+  //
+  //      {
+  //        tag: i32,
+  //        ref: anyref
+  //      }
+  //
+  //    Imagine that if tag == 0 then the reference always contains struct $A,
+  //    and if tag == 1 then it always contains a struct $B, and so forth. We
+  //    can't expect GUFA to figure out such invariants in general. But by
+  //    having casts in the right places we can help GUFA optimize:
+  //
+  //      (if
+  //        (tag == 1)
+  //        (struct.get $A 0
+  //          (ref.cast $B ..))
+  //
+  //    We know it must be a $B due to the tag. By keeping the cast there we can
+  //    make sure that optimizations can benefit from that.
+  //
+  //    Given the large amount of potential benefit we can get from a successful
+  //    optimization in GUFA, any reduction there may be a bad idea, so we
+  //    should be very careful and probably *not* remove such casts.
+
+  // If an instruction traps on a null input, there is no need for a
+  // ref.as_non_null on that input: we will trap either way (and the binaryen
+  // optimizer does not differentiate traps).
+  //
+  // See "notes on removing casts", above. However, in most cases removing a
+  // non-null cast is obviously safe to do, since we only remove one if another
+  // check will happen later.
+  void skipNonNullCast(Expression*& input) {
+    while (1) {
+      if (auto* as = input->dynCast<RefAs>()) {
+        if (as->op == RefAsNonNull) {
+          input = as->value;
+          continue;
+        }
+      }
+      break;
+    }
+  }
+
+  // As skipNonNullCast, but skips all casts if we can do so. This is useful in
+  // cases where we don't actually care about the type but just the value, that
+  // is, if casts of the type do not affect our behavior (which is the case in
+  // ref.eq for example).
+  //
+  // |requiredType| is the required supertype of the final output. We will not
+  // remove a cast that would leave something that would break that. If
+  // |requiredType| is not provided we will accept any type there.
+  //
+  // See "notes on removing casts", above, for when this is safe to do.
+  void skipCast(Expression*& input,
+                Type requiredType = Type(HeapType::any, Nullable)) {
+    // Traps-never-happen mode is a requirement for us to optimize here.
+    if (!getPassOptions().trapsNeverHappen) {
+      return;
+    }
+    while (1) {
+      if (auto* as = input->dynCast<RefAs>()) {
+        if (Type::isSubType(as->value->type, requiredType)) {
+          input = as->value;
+          continue;
+        }
+      } else if (auto* cast = input->dynCast<RefCast>()) {
+        if (Type::isSubType(cast->ref->type, requiredType)) {
+          input = cast->ref;
+          continue;
+        }
+      }
+      break;
+    }
+  }
+
+  // Appends a result after the dropped children, if we need them.
+  Expression* getDroppedChildrenAndAppend(Expression* curr,
+                                          Expression* result) {
+    return wasm::getDroppedChildrenAndAppend(
+      curr, *getModule(), getPassOptions(), result);
+  }
+
+  Expression* getDroppedChildrenAndAppend(Expression* curr, Literal value) {
+    auto* result = Builder(*getModule()).makeConst(value);
+    return getDroppedChildrenAndAppend(curr, result);
+  }
+
+  bool trapOnNull(Expression* curr, Expression* ref) {
+    if (ref->type.isNull()) {
+      replaceCurrent(getDroppedChildrenAndAppend(
+        curr, Builder(*getModule()).makeUnreachable()));
+      // Propagate the unreachability.
+      refinalize = true;
+      return true;
     }
+    return false;
   }
 
   void visitRefEq(RefEq* curr) {
+    // The types may prove that the same reference cannot appear on both sides.
+    auto leftType = curr->left->type;
+    auto rightType = curr->right->type;
+    if (leftType == Type::unreachable || rightType == Type::unreachable) {
+      // Leave this for DCE.
+      return;
+    }
+    auto leftHeapType = leftType.getHeapType();
+    auto rightHeapType = rightType.getHeapType();
+    auto leftIsHeapSubtype = HeapType::isSubType(leftHeapType, rightHeapType);
+    auto rightIsHeapSubtype = HeapType::isSubType(rightHeapType, leftHeapType);
+    if (!leftIsHeapSubtype && !rightIsHeapSubtype &&
+        (leftType.isNonNullable() || rightType.isNonNullable())) {
+      // The heap types have no intersection, so the only thing that can
+      // possibly appear on both sides is null, but one of the two is non-
+      // nullable, which rules that out. So there is no way that the same
+      // reference can appear on both sides.
+      replaceCurrent(
+        getDroppedChildrenAndAppend(curr, Literal::makeZero(Type::i32)));
+      return;
+    }
+
+    // Equality does not depend on the type, so casts may be removable.
+    //
+    // This is safe to do first because nothing farther down cares about the
+    // type, and we consume the two input references, so removing a cast could
+    // not help our parents (see "notes on removing casts").
+    Type nullableEq = Type(HeapType::eq, Nullable);
+    skipCast(curr->left, nullableEq);
+    skipCast(curr->right, nullableEq);
+
     // Identical references compare equal.
-    if (areConsecutiveInputsEqualAndRemovable(curr->left, curr->right)) {
+    // (Technically we do not need to check if the inputs are also foldable into
+    // a single one, but we do not have utility code to handle non-foldable
+    // cases yet; the foldable case we do handle is the common one of the first
+    // child being a tee and the second a get of that tee. TODO)
+    if (areConsecutiveInputsEqualAndFoldable(curr->left, curr->right)) {
       replaceCurrent(
-        Builder(*getModule()).makeConst(Literal::makeOne(Type::i32)));
+        getDroppedChildrenAndAppend(curr, Literal::makeOne(Type::i32)));
       return;
     }
 
@@ -1357,25 +1579,16 @@ struct OptimizeInstructions
     }
   }
 
-  // If an instruction traps on a null input, there is no need for a
-  // ref.as_non_null on that input: we will trap either way (and the binaryen
-  // optimizer does not differentiate traps).
-  void skipNonNullCast(Expression*& input) {
-    while (1) {
-      if (auto* as = input->dynCast<RefAs>()) {
-        if (as->op == RefAsNonNull) {
-          input = as->value;
-          continue;
-        }
-      }
-      break;
-    }
+  void visitStructGet(StructGet* curr) {
+    skipNonNullCast(curr->ref);
+    trapOnNull(curr, curr->ref);
   }
 
-  void visitStructGet(StructGet* curr) { skipNonNullCast(curr->ref); }
-
   void visitStructSet(StructSet* curr) {
     skipNonNullCast(curr->ref);
+    if (trapOnNull(curr, curr->ref)) {
+      return;
+    }
 
     if (curr->ref->type != Type::unreachable && curr->value->type.isInteger()) {
       const auto& fields = curr->ref->type.getHeapType().getStruct().fields;
@@ -1523,10 +1736,16 @@ struct OptimizeInstructions
     return true;
   }
 
-  void visitArrayGet(ArrayGet* curr) { skipNonNullCast(curr->ref); }
+  void visitArrayGet(ArrayGet* curr) {
+    skipNonNullCast(curr->ref);
+    trapOnNull(curr, curr->ref);
+  }
 
   void visitArraySet(ArraySet* curr) {
     skipNonNullCast(curr->ref);
+    if (trapOnNull(curr, curr->ref)) {
+      return;
+    }
 
     if (curr->ref->type != Type::unreachable && curr->value->type.isInteger()) {
       auto element = curr->ref->type.getHeapType().getArray().element;
@@ -1534,11 +1753,15 @@ struct OptimizeInstructions
     }
   }
 
-  void visitArrayLen(ArrayLen* curr) { skipNonNullCast(curr->ref); }
+  void visitArrayLen(ArrayLen* curr) {
+    skipNonNullCast(curr->ref);
+    trapOnNull(curr, curr->ref);
+  }
 
   void visitArrayCopy(ArrayCopy* curr) {
     skipNonNullCast(curr->destRef);
     skipNonNullCast(curr->srcRef);
+    trapOnNull(curr, curr->destRef) || trapOnNull(curr, curr->srcRef);
   }
 
   bool canBeCastTo(HeapType a, HeapType b) {
@@ -1546,7 +1769,13 @@ struct OptimizeInstructions
   }
 
   void visitRefCast(RefCast* curr) {
-    if (curr->type == Type::unreachable) {
+    // Note we must check the ref's type here and not our own, since we only
+    // refinalize at the end, which means our type may not have been updated yet
+    // after a change in the child.
+    // TODO: we could update unreachability up the stack perhaps, or just move
+    //       all patterns that can add unreachability to a pass that does so
+    //       already like vacuum or dce.
+    if (curr->ref->type == Type::unreachable) {
       return;
     }
 
@@ -1556,7 +1785,7 @@ struct OptimizeInstructions
     auto fallthrough =
       Properties::getFallthrough(curr->ref, getPassOptions(), *getModule());
 
-    auto intendedType = curr->getIntendedType();
+    auto intendedType = curr->intendedType;
 
     // If the value is a null, it will just flow through, and we do not need
     // the cast. However, if that would change the type, then things are less
@@ -1567,13 +1796,8 @@ struct OptimizeInstructions
       // Replace the expression with drops of the inputs, and a null. Note
       // that we provide a null of the previous type, so that we do not alter
       // the type received by our parent.
-      std::vector<Expression*> items;
-      items.push_back(builder.makeDrop(curr->ref));
-      if (curr->rtt) {
-        items.push_back(builder.makeDrop(curr->rtt));
-      }
-      items.push_back(builder.makeRefNull(intendedType));
-      Expression* rep = builder.makeBlock(items);
+      Expression* rep = builder.makeSequence(builder.makeDrop(curr->ref),
+                                             builder.makeRefNull(intendedType));
       if (curr->ref->type.isNonNullable()) {
         // Avoid a type change by forcing to be non-nullable. In practice,
         // this would have trapped before we get here, so this is just for
@@ -1588,22 +1812,17 @@ struct OptimizeInstructions
     }
 
     // For the cast to be able to succeed, the value being cast must be a
-    // subtype of the desired type, as RTT subtyping is a subset of static
-    // subtyping. For example, trying to cast an array to a struct would be
-    // incompatible.
+    // subtype of the desired type. For example, trying to cast an array to a
+    // struct would be incompatible.
     if (!canBeCastTo(curr->ref->type.getHeapType(), intendedType)) {
       // This cast cannot succeed. If the input is not a null, it will
       // definitely trap.
       if (fallthrough->type.isNonNullable()) {
         // Make sure to emit a block with the same type as us; leave updating
         // types for other passes.
-        std::vector<Expression*> items;
-        items.push_back(builder.makeDrop(curr->ref));
-        if (curr->rtt) {
-          items.push_back(builder.makeDrop(curr->rtt));
-        }
-        items.push_back(builder.makeUnreachable());
-        replaceCurrent(builder.makeBlock(items, curr->type));
+        replaceCurrent(builder.makeBlock(
+          {builder.makeDrop(curr->ref), builder.makeUnreachable()},
+          curr->type));
         return;
       }
       // Otherwise, we are not sure what it is, and need to wait for runtime
@@ -1611,46 +1830,29 @@ struct OptimizeInstructions
       // we can see the value is definitely a null at compile time, earlier.)
     }
 
-    if (passOptions.ignoreImplicitTraps || passOptions.trapsNeverHappen ||
-        !curr->rtt) {
-      // Aside from the issue of type incompatibility as mentioned above, the
-      // cast can trap if the types *are* compatible but it happens to be the
-      // case at runtime that the value is not of the desired subtype. If we
-      // do not consider such traps possible, we can ignore that. (Note,
-      // though, that we cannot do this if we cannot replace the current type
-      // with the reference's type.) We can also do this if this is a static
-      // cast: in that case, all we need to know about are the types.
-      if (HeapType::isSubType(curr->ref->type.getHeapType(), intendedType)) {
-        if (curr->rtt) {
-          replaceCurrent(getResultOfFirst(curr->ref,
-                                          builder.makeDrop(curr->rtt),
-                                          getFunction(),
-                                          getModule(),
-                                          passOptions));
-        } else {
-          replaceCurrent(curr->ref);
+    // Check whether the cast will definitely succeed.
+    if (HeapType::isSubType(curr->ref->type.getHeapType(), intendedType)) {
+      replaceCurrent(curr->ref);
 
-          // We must refinalize here, as we may be returning a more specific
-          // type, which can alter the parent. For example:
-          //
-          //  (struct.get $parent 0
-          //   (ref.cast_static $parent
-          //    (local.get $child)
-          //   )
-          //  )
-          //
-          // Try to cast a $child to its parent, $parent. That always works,
-          // so the cast can be removed.
-          // Then once the cast is removed, the outer struct.get
-          // will have a reference with a different type, making it a
-          // (struct.get $child ..) instead of $parent.
-          // But if $parent and $child have different types on field 0 (the
-          // child may have a more refined one) then the struct.get must be
-          // refinalized so the IR node has the expected type.
-          refinalize = true;
-        }
-        return;
-      }
+      // We must refinalize here, as we may be returning a more specific
+      // type, which can alter the parent. For example:
+      //
+      //  (struct.get $parent 0
+      //   (ref.cast_static $parent
+      //    (local.get $child)
+      //   )
+      //  )
+      //
+      // Try to cast a $child to its parent, $parent. That always works,
+      // so the cast can be removed.
+      // Then once the cast is removed, the outer struct.get
+      // will have a reference with a different type, making it a
+      // (struct.get $child ..) instead of $parent.
+      // But if $parent and $child have different types on field 0 (the
+      // child may have a more refined one) then the struct.get must be
+      // refinalized so the IR node has the expected type.
+      refinalize = true;
+      return;
     }
 
     // Repeated identical ref.cast operations are unnecessary. First, find the
@@ -1669,51 +1871,41 @@ struct OptimizeInstructions
       }
     }
     if (auto* child = ref->dynCast<RefCast>()) {
-      if (curr->rtt && child->rtt) {
-        // Check if the casts are identical.
-        if (ExpressionAnalyzer::equal(curr->rtt, child->rtt) &&
-            !EffectAnalyzer(passOptions, *getModule(), curr->rtt)
-               .hasSideEffects()) {
-          replaceCurrent(curr->ref);
+      // Repeated casts can be removed, leaving just the most demanding of
+      // them.
+      auto childIntendedType = child->intendedType;
+      if (HeapType::isSubType(intendedType, childIntendedType)) {
+        // Skip the child.
+        if (curr->ref == child) {
+          curr->ref = child->ref;
           return;
+        } else {
+          // The child is not the direct child of the parent, but it is a
+          // fallthrough value, for example,
+          //
+          //  (ref.cast parent
+          //   (block
+          //    .. other code ..
+          //    (ref.cast child)))
+          //
+          // In this case it isn't obvious that we can remove the child, as
+          // doing so might require updating the types of the things in the
+          // middle - and in fact the sole purpose of the child may be to get
+          // a proper type for validation to work. Do nothing in this case,
+          // and hope that other opts will help here (for example,
+          // trapsNeverHappen will help if the code validates without the
+          // child).
         }
-      } else if (!curr->rtt && !child->rtt) {
-        // Repeated static casts can be removed, leaving just the most demanding
-        // of them.
-        auto childIntendedType = child->getIntendedType();
-        if (HeapType::isSubType(intendedType, childIntendedType)) {
-          // Skip the child.
-          if (curr->ref == child) {
-            curr->ref = child->ref;
-            return;
-          } else {
-            // The child is not the direct child of the parent, but it is a
-            // fallthrough value, for example,
-            //
-            //  (ref.cast parent
-            //   (block
-            //    .. other code ..
-            //    (ref.cast child)))
-            //
-            // In this case it isn't obvious that we can remove the child, as
-            // doing so might require updating the types of the things in the
-            // middle - and in fact the sole purpose of the child may be to get
-            // a proper type for validation to work. Do nothing in this case,
-            // and hope that other opts will help here (for example,
-            // trapsNeverHappen will help if the code validates without the
-            // child).
-          }
-        } else if (!canBeCastTo(intendedType, childIntendedType)) {
-          // The types are not compatible, so if the input is not null, this
-          // will trap.
-          if (!curr->type.isNullable()) {
-            // Make sure to emit a block with the same type as us; leave
-            // updating types for other passes.
-            replaceCurrent(builder.makeBlock(
-              {builder.makeDrop(curr->ref), builder.makeUnreachable()},
-              curr->type));
-            return;
-          }
+      } else if (!canBeCastTo(intendedType, childIntendedType)) {
+        // The types are not compatible, so if the input is not null, this
+        // will trap.
+        if (!curr->type.isNullable()) {
+          // Make sure to emit a block with the same type as us; leave
+          // updating types for other passes.
+          replaceCurrent(builder.makeBlock(
+            {builder.makeDrop(curr->ref), builder.makeUnreachable()},
+            curr->type));
+          return;
         }
       }
     }
@@ -1756,22 +1948,17 @@ struct OptimizeInstructions
     Builder builder(*getModule());
 
     auto refType = curr->ref->type.getHeapType();
-    auto intendedType = curr->getIntendedType();
+    auto intendedType = curr->intendedType;
 
     // See above in RefCast.
     if (!canBeCastTo(refType, intendedType)) {
       // This test cannot succeed, and will definitely return 0.
-      std::vector<Expression*> items;
-      items.push_back(builder.makeDrop(curr->ref));
-      if (curr->rtt) {
-        items.push_back(builder.makeDrop(curr->rtt));
-      }
-      items.push_back(builder.makeConst(int32_t(0)));
-      replaceCurrent(builder.makeBlock(items));
+      replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
+                                          builder.makeConst(int32_t(0))));
       return;
     }
 
-    if (!curr->rtt && curr->ref->type.isNonNullable() &&
+    if (curr->ref->type.isNonNullable() &&
         HeapType::isSubType(refType, intendedType)) {
       // This static test will definitely succeed.
       replaceCurrent(builder.makeBlock(
@@ -1802,6 +1989,11 @@ struct OptimizeInstructions
         replaceCurrent(builder.makeSequence(
           builder.makeDrop(curr->value),
           builder.makeConst(Literal::makeZero(Type::i32))));
+      } else {
+        // See the comment on the other call to this lower down. Because of that
+        // other code path we run this optimization at the end (though in this
+        // code path it would be fine either way).
+        skipCast(curr->value);
       }
       return;
     }
@@ -1841,6 +2033,12 @@ struct OptimizeInstructions
         }
       }
     }
+
+    // What the reference points to does not depend on the type, so casts
+    // may be removable. Do this right before returning because removing a
+    // cast may remove info that we could have used to optimize, see
+    // "notes on removing casts".
+    skipCast(curr->value);
   }
 
   void visitRefAs(RefAs* curr) {
@@ -1848,6 +2046,12 @@ struct OptimizeInstructions
       return;
     }
 
+    if (curr->op == ExternExternalize || curr->op == ExternInternalize) {
+      // We can't optimize these. Even removing a non-null cast is not valid as
+      // they allow nulls to filter through, unlike other RefAs*
+      return;
+    }
+
     skipNonNullCast(curr->value);
 
     // Check if the type is the kind we are checking for.
@@ -1892,13 +2096,14 @@ private:
   // simple peephole optimizations - all we care about is a single instruction
   // at a time, and its inputs).
   //
-  // This also checks that the inputs are removable.
+  // This also checks that the inputs are removable (but we do not assume the
+  // caller will always remove them).
   bool areConsecutiveInputsEqualAndRemovable(Expression* left,
                                              Expression* right) {
     // First, check for side effects. If there are any, then we can't even
     // assume things like local.get's of the same index being identical. (It is
-    // also ok to have side effects here, if we can remove them, as we are also
-    // checking if we can remove the two inputs anyhow.)
+    // also ok to have removable side effects here, see the function
+    // description.)
     auto& passOptions = getPassOptions();
     if (EffectAnalyzer(passOptions, *getModule(), left)
           .hasUnremovableSideEffects() ||
@@ -1921,9 +2126,13 @@ private:
     return true;
   }
 
-  // Check if two consecutive inputs to an instruction are equal and can be
-  // folded into the first of the two. This identifies reads from the same local
-  // variable when one of them is a "tee" operation.
+  // Check if two consecutive inputs to an instruction are equal and can also be
+  // folded into the first of the two (but we do not assume the caller will
+  // always fold them). This is similar to areConsecutiveInputsEqualAndRemovable
+  // but also identifies reads from the same local variable when the first of
+  // them is a "tee" operation and the second is a get (in which case, it is
+  // fine to remove the get, but not the tee).
+  //
   // The inputs here must be consecutive, but it is also ok to have code with no
   // side effects at all in the middle. For example, a Const in between is ok.
   bool areConsecutiveInputsEqualAndFoldable(Expression* left,
@@ -1940,6 +2149,13 @@ private:
     return areConsecutiveInputsEqualAndRemovable(left, right);
   }
 
+  // Similar to areConsecutiveInputsEqualAndFoldable, but only checks that they
+  // are equal (and not that they are foldable).
+  bool areConsecutiveInputsEqual(Expression* left, Expression* right) {
+    // TODO: optimize cases that must be equal but are *not* foldable.
+    return areConsecutiveInputsEqualAndFoldable(left, right);
+  }
+
   // Canonicalizing the order of a symmetric binary helps us
   // write more concise pattern matching code elsewhere.
   void canonicalize(Binary* binary) {
@@ -2011,6 +2227,67 @@ private:
         c->value = Literal::makeZero(c->type);
         return;
       }
+      // Prefer compare to signed min (s_min) instead of s_min + 1.
+      // (signed)x < s_min + 1   ==>   x == s_min
+      if (binary->op == LtSInt32 && c->value.geti32() == INT32_MIN + 1) {
+        binary->op = EqInt32;
+        c->value = Literal::makeSignedMin(Type::i32);
+        return;
+      }
+      if (binary->op == LtSInt64 && c->value.geti64() == INT64_MIN + 1) {
+        binary->op = EqInt64;
+        c->value = Literal::makeSignedMin(Type::i64);
+        return;
+      }
+      // (signed)x >= s_min + 1   ==>   x != s_min
+      if (binary->op == GeSInt32 && c->value.geti32() == INT32_MIN + 1) {
+        binary->op = NeInt32;
+        c->value = Literal::makeSignedMin(Type::i32);
+        return;
+      }
+      if (binary->op == GeSInt64 && c->value.geti64() == INT64_MIN + 1) {
+        binary->op = NeInt64;
+        c->value = Literal::makeSignedMin(Type::i64);
+        return;
+      }
+      // Prefer compare to signed max (s_max) instead of s_max - 1.
+      // (signed)x > s_max - 1   ==>   x == s_max
+      if (binary->op == GtSInt32 && c->value.geti32() == INT32_MAX - 1) {
+        binary->op = EqInt32;
+        c->value = Literal::makeSignedMax(Type::i32);
+        return;
+      }
+      if (binary->op == GtSInt64 && c->value.geti64() == INT64_MAX - 1) {
+        binary->op = EqInt64;
+        c->value = Literal::makeSignedMax(Type::i64);
+        return;
+      }
+      // (signed)x <= s_max - 1   ==>   x != s_max
+      if (binary->op == LeSInt32 && c->value.geti32() == INT32_MAX - 1) {
+        binary->op = NeInt32;
+        c->value = Literal::makeSignedMax(Type::i32);
+        return;
+      }
+      if (binary->op == LeSInt64 && c->value.geti64() == INT64_MAX - 1) {
+        binary->op = NeInt64;
+        c->value = Literal::makeSignedMax(Type::i64);
+        return;
+      }
+      // Prefer compare to unsigned max (u_max) instead of u_max - 1.
+      // (unsigned)x <= u_max - 1   ==>   x != u_max
+      if (binary->op == Abstract::getBinary(c->type, Abstract::LeU) &&
+          c->value.getInteger() == (int64_t)(UINT64_MAX - 1)) {
+        binary->op = Abstract::getBinary(c->type, Abstract::Ne);
+        c->value = Literal::makeUnsignedMax(c->type);
+        return;
+      }
+      // (unsigned)x > u_max - 1   ==>   x == u_max
+      if (binary->op == Abstract::getBinary(c->type, Abstract::GtU) &&
+          c->value.getInteger() == (int64_t)(UINT64_MAX - 1)) {
+        binary->op = Abstract::getBinary(c->type, Abstract::Eq);
+        c->value = Literal::makeUnsignedMax(c->type);
+        return;
+      }
       return;
     }
     // Prefer a get on the right.
@@ -2162,21 +2439,6 @@ private:
       // Don't bother when `ifFalse` isn't pure - we would need to reverse the
       // order using a temp local, which would be bad
     }
-    {
-      // Flip select to remove eqz if we can reorder
-      Select* s;
-      Expression *ifTrue, *ifFalse, *c;
-      if (matches(
-            curr,
-            select(
-              &s, any(&ifTrue), any(&ifFalse), unary(EqZInt32, any(&c)))) &&
-          canReorder(ifTrue, ifFalse)) {
-        s->ifTrue = ifFalse;
-        s->ifFalse = ifTrue;
-        s->condition = c;
-        return s;
-      }
-    }
     {
       // TODO: Remove this after landing SCCP pass. See: #4161
 
@@ -2229,6 +2491,45 @@ private:
         return curr->type == Type::i64 ? builder.makeUnary(ExtendUInt32, c) : c;
       }
     }
+    // Flip the arms if doing so might help later optimizations here.
+    if (auto* binary = curr->condition->dynCast<Binary>()) {
+      auto inv = invertBinaryOp(binary->op);
+      if (inv != InvalidBinary) {
+        // For invertible binary operations, we prefer to have non-zero values
+        // in the ifTrue, and zero values in the ifFalse, due to the
+        // optimization right after us. Even if this does not help there, it is
+        // a nice canonicalization. (To ensure convergence - that we don't keep
+        // doing work each time we get here - do nothing if both are zero, or
+        // if both are nonzero.)
+        Const* c;
+        if ((matches(curr->ifTrue, ival(0)) &&
+             !matches(curr->ifFalse, ival(0))) ||
+            (!matches(curr->ifTrue, ival()) &&
+             matches(curr->ifFalse, ival(&c)) && !c->value.isZero())) {
+          binary->op = inv;
+          std::swap(curr->ifTrue, curr->ifFalse);
+        }
+      }
+    }
+    if (curr->type == Type::i32 &&
+        Bits::getMaxBits(curr->condition, this) <= 1 &&
+        Bits::getMaxBits(curr->ifTrue, this) <= 1 &&
+        Bits::getMaxBits(curr->ifFalse, this) <= 1) {
+      // The condition and both arms are i32 booleans, which allows us to do
+      // boolean optimizations.
+      Expression* x;
+      Expression* y;
+
+      // x ? y : 0   ==>   x & y
+      if (matches(curr, select(any(&y), ival(0), any(&x)))) {
+        return builder.makeBinary(AndInt32, y, x);
+      }
+
+      // x ? 1 : y   ==>   x | y
+      if (matches(curr, select(ival(1), any(&y), any(&x)))) {
+        return builder.makeBinary(OrInt32, y, x);
+      }
+    }
     {
       // Simplify x < 0 ? -1 : 1 or x >= 0 ? 1 : -1 to
       // i32(x) >> 31 | 1
@@ -2252,23 +2553,19 @@ private:
         }
       }
     }
-    if (curr->type == Type::i32 &&
-        Bits::getMaxBits(curr->condition, this) <= 1 &&
-        Bits::getMaxBits(curr->ifTrue, this) <= 1 &&
-        Bits::getMaxBits(curr->ifFalse, this) <= 1) {
-      // The condition and both arms are i32 booleans, which allows us to do
-      // boolean optimizations.
-      Expression* x;
-      Expression* y;
-
-      // x ? y : 0   ==>   x & y
-      if (matches(curr, select(any(&y), ival(0), any(&x)))) {
-        return builder.makeBinary(AndInt32, y, x);
-      }
-
-      // x ? 1 : y   ==>   x | y
-      if (matches(curr, select(ival(1), any(&y), any(&x)))) {
-        return builder.makeBinary(OrInt32, y, x);
+    {
+      // Flip select to remove eqz if we can reorder
+      Select* s;
+      Expression *ifTrue, *ifFalse, *c;
+      if (matches(
+            curr,
+            select(
+              &s, any(&ifTrue), any(&ifFalse), unary(EqZInt32, any(&c)))) &&
+          canReorder(ifTrue, ifFalse)) {
+        s->ifTrue = ifFalse;
+        s->ifFalse = ifTrue;
+        s->condition = c;
+        return s;
       }
     }
     {
@@ -2457,6 +2754,118 @@ private:
       builder.makeConst(Literal::makeFromInt64(constant, walked->type)));
   }
 
+  // Given an i64.wrap operation, see if we can remove it. If all the things
+  // being operated on behave the same with or without wrapping, then we don't
+  // need to go to 64 bits at all, e.g.:
+  //
+  //  int32_t(int64_t(x))               => x                 (extend, then wrap)
+  //  int32_t(int64_t(x) + int64_t(10)) => x + int32_t(10)            (also add)
+  //
+  Expression* optimizeWrappedResult(Unary* wrap) {
+    assert(wrap->op == WrapInt64);
+
+    // Core processing logic. This goes through the children, in one of two
+    // modes:
+    //  * Scan: Find if there is anything we can't handle. Sets |canOptimize|
+    //    with what it finds.
+    //  * Optimize: Given we can handle everything, update things.
+    enum Mode { Scan, Optimize };
+    bool canOptimize = true;
+    auto processChildren = [&](Mode mode) {
+      // Use a simple stack as we go through the children. We use ** as we need
+      // to replace children for some optimizations.
+      SmallVector<Expression**, 2> stack;
+      stack.emplace_back(&wrap->value);
+
+      while (!stack.empty() && canOptimize) {
+        auto* currp = stack.back();
+        stack.pop_back();
+        auto* curr = *currp;
+        if (curr->type == Type::unreachable) {
+          // Leave unreachability for other passes.
+          canOptimize = false;
+          return;
+        } else if (auto* c = curr->dynCast<Const>()) {
+          // A i64 const can be handled by just turning it into an i32.
+          if (mode == Optimize) {
+            c->value = Literal(int32_t(c->value.getInteger()));
+            c->type = Type::i32;
+          }
+        } else if (auto* unary = curr->dynCast<Unary>()) {
+          switch (unary->op) {
+            case ExtendSInt32:
+            case ExtendUInt32: {
+              // Note that there is nothing to push to the stack here: the child
+              // is 32-bit already, so we can stop looking. We just need to skip
+              // the extend operation.
+              if (mode == Optimize) {
+                *currp = unary->value;
+              }
+              break;
+            }
+            default: {
+              // TODO: handle more cases here and below,
+              //       https://github.com/WebAssembly/binaryen/issues/5004
+              canOptimize = false;
+              return;
+            }
+          }
+        } else if (auto* binary = curr->dynCast<Binary>()) {
+          // Turn the binary into a 32-bit one, if we can.
+          switch (binary->op) {
+            case AddInt64:
+            case SubInt64:
+            case MulInt64: {
+              // We can optimize these.
+              break;
+            }
+            default: {
+              canOptimize = false;
+              return;
+            }
+          }
+          if (mode == Optimize) {
+            switch (binary->op) {
+              case AddInt64: {
+                binary->op = AddInt32;
+                break;
+              }
+              case SubInt64: {
+                binary->op = SubInt32;
+                break;
+              }
+              case MulInt64: {
+                binary->op = MulInt32;
+                break;
+              }
+              default: {
+                WASM_UNREACHABLE("bad op");
+              }
+            }
+            // All things we can optimize change the type to i32.
+            binary->type = Type::i32;
+          }
+          stack.push_back(&binary->left);
+          stack.push_back(&binary->right);
+        } else {
+          // Anything else makes us give up.
+          canOptimize = false;
+          return;
+        }
+      }
+    };
+
+    processChildren(Scan);
+    if (!canOptimize) {
+      return nullptr;
+    }
+
+    // Optimize, and return the optimized results (in which we no longer need
+    // the wrap operation itself).
+    processChildren(Optimize);
+    return wrap->value;
+  }
+
   //   expensive1 | expensive2 can be turned into expensive1 ? 1 : expensive2,
   //   and expensive | cheap     can be turned into cheap     ? 1 : expensive,
   // so that we can avoid one expensive computation, if it has no side effects.
@@ -2782,7 +3191,7 @@ private:
   }
 
   // fold constant factors into the offset
-  void optimizeMemoryAccess(Expression*& ptr, Address& offset) {
+  void optimizeMemoryAccess(Expression*& ptr, Address& offset, Name memory) {
     // ptr may be a const, but it isn't worth folding that in (we still have a
     // const); in fact, it's better to do the opposite for gzip purposes as well
     // as for readability.
@@ -2790,7 +3199,8 @@ private:
     if (last) {
       uint64_t value64 = last->value.getInteger();
       uint64_t offset64 = offset;
-      if (getModule()->memory.is64()) {
+      auto mem = getModule()->getMemory(memory);
+      if (mem->is64()) {
         last->value = Literal(int64_t(value64 + offset64));
         offset = 0;
       } else {
@@ -3046,7 +3456,7 @@ private:
           binary(DivSInt64, any(), i64(std::numeric_limits<int64_t>::min())))) {
       curr->op = EqInt64;
       curr->type = Type::i32;
-      return Builder(*getModule()).makeUnary(ExtendUInt32, curr);
+      return builder.makeUnary(ExtendUInt32, curr);
     }
     // (unsigned)x < 0   ==>   i32(0)
     if (matches(curr, binary(LtU, pure(&left), ival(0)))) {
@@ -3168,20 +3578,6 @@ private:
         return curr;
       }
     }
-    {
-      double value;
-      if (matches(curr, binary(Sub, any(), fval(&value))) && value == 0.0) {
-        // x - (-0.0)   ==>   x + 0.0
-        if (std::signbit(value)) {
-          curr->op = Abstract::getBinary(type, Add);
-          right->value = right->value.neg();
-          return curr;
-        } else if (fastMath) {
-          // x - 0.0   ==>   x
-          return curr->left;
-        }
-      }
-    }
     {
       // x * 2.0  ==>  x + x
       // but we apply this only for simple expressions like
@@ -3231,6 +3627,242 @@ private:
         return left;
       }
     }
+    {
+      //   x !=  NaN   ==>   1
+      //   x <=> NaN   ==>   0
+      //   x op  NaN'  ==>   NaN',  iff `op` != `copysign` and `x` != C
+      Const* c;
+      Binary* bin;
+      Expression* x;
+      if (matches(curr, binary(&bin, pure(&x), fval(&c))) &&
+          std::isnan(c->value.getFloat()) &&
+          bin->op != getBinary(x->type, CopySign)) {
+        if (bin->isRelational()) {
+          // reuse "c" (nan) constant
+          c->type = Type::i32;
+          if (bin->op == getBinary(x->type, Ne)) {
+            // x != NaN  ==>  1
+            c->value = Literal::makeOne(Type::i32);
+          } else {
+            // x == NaN,
+            // x >  NaN,
+            // x <= NaN
+            // x .. NaN  ==>  0
+            c->value = Literal::makeZero(Type::i32);
+          }
+          return c;
+        }
+        // propagate NaN of RHS but canonicalize it
+        c->value = Literal::standardizeNaN(c->value);
+        return c;
+      }
+    }
+    return nullptr;
+  }
+
+  // Returns true if the given binary operation can overflow. If we can't be
+  // sure either way, we return true, assuming the worst.
+  bool canOverflow(Binary* binary) {
+    using namespace Abstract;
+
+    // If we know nothing about a limit on the amount of bits on either side,
+    // give up.
+    auto typeMaxBits = getBitsForType(binary->type);
+    auto leftMaxBits = Bits::getMaxBits(binary->left, this);
+    auto rightMaxBits = Bits::getMaxBits(binary->right, this);
+    if (std::max(leftMaxBits, rightMaxBits) == typeMaxBits) {
+      return true;
+    }
+
+    if (binary->op == getBinary(binary->type, Add)) {
+      // Proof this cannot overflow:
+      //
+      // left + right <  2^leftMaxBits + 2^rightMaxBits          (1)
+      //              <= 2^(typeMaxBits-1) + 2^(typeMaxBits-1)   (2)
+      //              =  2^typeMaxBits                           (3)
+      //
+      // (1) By the definition of the max bits (e.g. an int32 has 32 max bits,
+      //     and its max value is 2^32 - 1, which is < 2^32).
+      // (2) By the above checks and early returns.
+      // (3) 2^x + 2^x === 2*2^x === 2^(x+1)
+      return false;
+    }
+
+    // TODO subtraction etc.
+    return true;
+  }
+
+  // Folding two expressions into one with similar operations and
+  // constants on RHSs
+  Expression* optimizeDoubletonWithConstantOnRight(Binary* curr) {
+    using namespace Match;
+    using namespace Abstract;
+    {
+      Binary* inner;
+      Const *c1, *c2 = curr->right->cast<Const>();
+      if (matches(curr->left, binary(&inner, any(), ival(&c1))) &&
+          inner->op == curr->op) {
+        Type type = inner->type;
+        BinaryOp op = inner->op;
+        // (x & C1) & C2   =>   x & (C1 & C2)
+        if (op == getBinary(type, And)) {
+          c1->value = c1->value.and_(c2->value);
+          return inner;
+        }
+        // (x | C1) | C2   =>   x | (C1 | C2)
+        if (op == getBinary(type, Or)) {
+          c1->value = c1->value.or_(c2->value);
+          return inner;
+        }
+        // (x ^ C1) ^ C2   =>   x ^ (C1 ^ C2)
+        if (op == getBinary(type, Xor)) {
+          c1->value = c1->value.xor_(c2->value);
+          return inner;
+        }
+        // (x * C1) * C2   =>   x * (C1 * C2)
+        if (op == getBinary(type, Mul)) {
+          c1->value = c1->value.mul(c2->value);
+          return inner;
+        }
+        // TODO:
+        // handle signed / unsigned divisions. They are more complex
+
+        // (x <<>> C1) <<>> C2   =>   x <<>> (C1 + C2)
+        if (hasAnyShift(op)) {
+          // shifts only use an effective amount from the constant, so
+          // adding must be done carefully
+          auto total =
+            Bits::getEffectiveShifts(c1) + Bits::getEffectiveShifts(c2);
+          auto effectiveTotal = Bits::getEffectiveShifts(total, c1->type);
+          if (total == effectiveTotal) {
+            // no overflow, we can do this
+            c1->value = Literal::makeFromInt32(total, c1->type);
+            return inner;
+          } else {
+            // overflow. Handle different scenarious
+            if (hasAnyRotateShift(op)) {
+              // overflow always accepted in rotation shifts
+              c1->value = Literal::makeFromInt32(effectiveTotal, c1->type);
+              return inner;
+            }
+            // handle overflows for general shifts
+            //   x << C1 << C2    =>   0 or { drop(x), 0 }
+            //   x >>> C1 >>> C2  =>   0 or { drop(x), 0 }
+            // iff `C1 + C2` -> overflows
+            if ((op == getBinary(type, Shl) || op == getBinary(type, ShrU))) {
+              auto* x = inner->left;
+              c1->value = Literal::makeZero(c1->type);
+              if (!effects(x).hasSideEffects()) {
+                //  =>  0
+                return c1;
+              } else {
+                //  =>  { drop(x), 0 }
+                Builder builder(*getModule());
+                return builder.makeBlock({builder.makeDrop(x), c1});
+              }
+            }
+            //   i32(x) >> C1 >> C2   =>   x >> 31
+            //   i64(x) >> C1 >> C2   =>   x >> 63
+            // iff `C1 + C2` -> overflows
+            if (op == getBinary(type, ShrS)) {
+              c1->value = Literal::makeFromInt32(c1->type.getByteSize() * 8 - 1,
+                                                 c1->type);
+              return inner;
+            }
+          }
+        }
+      }
+    }
+    {
+      // (x << C1) * C2   =>   x * (C2 << C1)
+      Binary* inner;
+      Const *c1, *c2;
+      if (matches(
+            curr,
+            binary(Mul, binary(&inner, Shl, any(), ival(&c1)), ival(&c2)))) {
+        inner->op = getBinary(inner->type, Mul);
+        c1->value = c2->value.shl(c1->value);
+        return inner;
+      }
+    }
+    {
+      // (x * C1) << C2   =>   x * (C1 << C2)
+      Binary* inner;
+      Const *c1, *c2;
+      if (matches(
+            curr,
+            binary(Shl, binary(&inner, Mul, any(), ival(&c1)), ival(&c2)))) {
+        c1->value = c1->value.shl(c2->value);
+        return inner;
+      }
+    }
+    {
+      // TODO: Add cancelation for some large constants when shrinkLevel > 0
+      // in FinalOptimizer.
+
+      // (x >> C)  << C   =>   x & -(1 << C)
+      // (x >>> C) << C   =>   x & -(1 << C)
+      Binary* inner;
+      Const *c1, *c2;
+      if (matches(curr,
+                  binary(Shl, binary(&inner, any(), ival(&c1)), ival(&c2))) &&
+          (inner->op == getBinary(inner->type, ShrS) ||
+           inner->op == getBinary(inner->type, ShrU)) &&
+          Bits::getEffectiveShifts(c1) == Bits::getEffectiveShifts(c2)) {
+        auto type = c1->type;
+        if (type == Type::i32) {
+          c1->value = Literal::makeFromInt32(
+            -(1U << Bits::getEffectiveShifts(c1)), Type::i32);
+        } else {
+          c1->value = Literal::makeFromInt64(
+            -(1ULL << Bits::getEffectiveShifts(c1)), Type::i64);
+        }
+        inner->op = getBinary(type, And);
+        return inner;
+      }
+    }
+    {
+      // TODO: Add cancelation for some large constants when shrinkLevel > 0
+      // in FinalOptimizer.
+
+      // (x << C) >>> C   =>   x & (-1 >>> C)
+      // (x << C) >> C    =>   skip
+      Binary* inner;
+      Const *c1, *c2;
+      if (matches(
+            curr,
+            binary(ShrU, binary(&inner, Shl, any(), ival(&c1)), ival(&c2))) &&
+          Bits::getEffectiveShifts(c1) == Bits::getEffectiveShifts(c2)) {
+        auto type = c1->type;
+        if (type == Type::i32) {
+          c1->value = Literal::makeFromInt32(
+            -1U >> Bits::getEffectiveShifts(c1), Type::i32);
+        } else {
+          c1->value = Literal::makeFromInt64(
+            -1ULL >> Bits::getEffectiveShifts(c1), Type::i64);
+        }
+        inner->op = getBinary(type, And);
+        return inner;
+      }
+    }
+    {
+      // TODO: Add canonicalization rotr to rotl and remove these rules.
+      // rotl(rotr(x, C1), C2)   =>   rotr(x, C1 - C2)
+      // rotr(rotl(x, C1), C2)   =>   rotl(x, C1 - C2)
+      Binary* inner;
+      Const *c1, *c2;
+      if (matches(
+            curr,
+            binary(RotL, binary(&inner, RotR, any(), ival(&c1)), ival(&c2))) ||
+          matches(
+            curr,
+            binary(RotR, binary(&inner, RotL, any(), ival(&c1)), ival(&c2)))) {
+        auto diff = Bits::getEffectiveShifts(c1) - Bits::getEffectiveShifts(c2);
+        c1->value = Literal::makeFromInt32(
+          Bits::getEffectiveShifts(diff, c2->type), c2->type);
+        return inner;
+      }
+    }
     return nullptr;
   }
 
@@ -3293,6 +3925,9 @@ private:
 
   // TODO: templatize on type?
   Expression* optimizeRelational(Binary* curr) {
+    using namespace Abstract;
+    using namespace Match;
+
     auto type = curr->right->type;
     if (curr->left->type.isInteger()) {
       if (curr->op == Abstract::getBinary(type, Abstract::Eq) ||
@@ -3326,9 +3961,6 @@ private:
       // unsigned(x - y) > 0    =>   x != y
       // unsigned(x - y) <= 0   =>   x == y
       {
-        using namespace Abstract;
-        using namespace Match;
-
         Binary* inner;
         // unsigned(x - y) > 0    =>   x != y
         if (matches(curr,
@@ -3359,6 +3991,176 @@ private:
           return curr;
         }
       }
+
+      // x + C1 > C2   ==>  x > (C2-C1)      if no overflowing, C2 >= C1
+      // x + C1 > C2   ==>  x + (C1-C2) > 0  if no overflowing, C2 <  C1
+      // And similarly for other relational operations on integers with a "+"
+      // on the left.
+      {
+        Binary* add;
+        Const* c1;
+        Const* c2;
+        if ((matches(curr,
+                     binary(binary(&add, Add, any(), ival(&c1)), ival(&c2))) ||
+             matches(curr,
+                     binary(binary(&add, Add, any(), ival(&c1)), ival(&c2)))) &&
+            !canOverflow(add)) {
+          if (c2->value.geU(c1->value).getInteger()) {
+            // This is the first line above, we turn into x > (C2-C1)
+            c2->value = c2->value.sub(c1->value);
+            curr->left = add->left;
+            return curr;
+          }
+          // This is the second line above, we turn into x + (C1-C2) > 0. Other
+          // optimizations can often kick in later. However, we must rule out
+          // the case where C2 is already 0 (as then we would not actually
+          // change anything, and we could infinite loop).
+          auto zero = Literal::makeZero(c2->type);
+          if (c2->value != zero) {
+            c1->value = c1->value.sub(c2->value);
+            c2->value = zero;
+            return curr;
+          }
+        }
+      }
+
+      // Comparisons can sometimes be simplified depending on the number of
+      // bits, e.g.  (unsigned)x > y  must be true if x has strictly more bits.
+      // A common case is a constant on the right, e.g. (x & 255) < 256 must be
+      // true.
+      // TODO: use getMinBits in more places, see ideas in
+      //       https://github.com/WebAssembly/binaryen/issues/2898
+      {
+        // Check if there is a nontrivial amount of bits on the left, which may
+        // provide enough to optimize.
+        auto leftMaxBits = Bits::getMaxBits(curr->left, this);
+        auto type = curr->left->type;
+        if (leftMaxBits < getBitsForType(type)) {
+          using namespace Abstract;
+          auto rightMinBits = Bits::getMinBits(curr->right);
+          auto rightIsNegative = rightMinBits == getBitsForType(type);
+          if (leftMaxBits < rightMinBits) {
+            // There are not enough bits on the left for it to be equal to the
+            // right, making various comparisons obviously false:
+            //             x == y
+            //   (unsigned)x >  y
+            //   (unsigned)x >= y
+            // and the same for signed, if y does not have the sign bit set
+            // (in that case, the comparison is effectively unsigned).
+            //
+            // TODO: In addition to leftMaxBits < rightMinBits, we could
+            //       handle the reverse, and also special cases like all bits
+            //       being 1 on the right, things like (x & 255) <= 255  ->  1
+            if (curr->op == Abstract::getBinary(type, Eq) ||
+                curr->op == Abstract::getBinary(type, GtU) ||
+                curr->op == Abstract::getBinary(type, GeU) ||
+                (!rightIsNegative &&
+                 (curr->op == Abstract::getBinary(type, GtS) ||
+                  curr->op == Abstract::getBinary(type, GeS)))) {
+              return getDroppedChildrenAndAppend(curr,
+                                                 Literal::makeZero(Type::i32));
+            }
+
+            // And some are obviously true:
+            //             x != y
+            //   (unsigned)x <  y
+            //   (unsigned)x <= y
+            // and likewise for signed, as above.
+            if (curr->op == Abstract::getBinary(type, Ne) ||
+                curr->op == Abstract::getBinary(type, LtU) ||
+                curr->op == Abstract::getBinary(type, LeU) ||
+                (!rightIsNegative &&
+                 (curr->op == Abstract::getBinary(type, LtS) ||
+                  curr->op == Abstract::getBinary(type, LeS)))) {
+              return getDroppedChildrenAndAppend(curr,
+                                                 Literal::makeOne(Type::i32));
+            }
+
+            // For truly signed comparisons, where y's sign bit is set, we can
+            // also infer some things, since we know y is signed but x is not
+            // (since x does not have enough bits for the sign bit to be set).
+            if (rightIsNegative) {
+              //   (signed, non-negative)x >  (negative)y   =>   1
+              //   (signed, non-negative)x >= (negative)y   =>   1
+              if (curr->op == Abstract::getBinary(type, GtS) ||
+                  curr->op == Abstract::getBinary(type, GeS)) {
+                return getDroppedChildrenAndAppend(curr,
+                                                   Literal::makeOne(Type::i32));
+              }
+              //   (signed, non-negative)x <  (negative)y   =>   0
+              //   (signed, non-negative)x <= (negative)y   =>   0
+              if (curr->op == Abstract::getBinary(type, LtS) ||
+                  curr->op == Abstract::getBinary(type, LeS)) {
+                return getDroppedChildrenAndAppend(
+                  curr, Literal::makeZero(Type::i32));
+              }
+            }
+          }
+        }
+      }
+    }
+    return nullptr;
+  }
+
+  Expression* simplifyRoundingsAndConversions(Unary* curr) {
+    using namespace Abstract;
+    using namespace Match;
+
+    switch (curr->op) {
+      case TruncSFloat64ToInt32:
+      case TruncSatSFloat64ToInt32: {
+        // i32 -> f64 -> i32 rountripping optimization:
+        //   i32.trunc(_sat)_f64_s(f64.convert_i32_s(x))  ==>  x
+        Expression* x;
+        if (matches(curr->value, unary(ConvertSInt32ToFloat64, any(&x)))) {
+          return x;
+        }
+        break;
+      }
+      case TruncUFloat64ToInt32:
+      case TruncSatUFloat64ToInt32: {
+        // u32 -> f64 -> u32 rountripping optimization:
+        //   i32.trunc(_sat)_f64_u(f64.convert_i32_u(x))  ==>  x
+        Expression* x;
+        if (matches(curr->value, unary(ConvertUInt32ToFloat64, any(&x)))) {
+          return x;
+        }
+        break;
+      }
+      case CeilFloat32:
+      case CeilFloat64:
+      case FloorFloat32:
+      case FloorFloat64:
+      case TruncFloat32:
+      case TruncFloat64:
+      case NearestFloat32:
+      case NearestFloat64: {
+        // Rounding after integer to float conversion may be skipped
+        //   ceil(float(int(x)))     ==>  float(int(x))
+        //   floor(float(int(x)))    ==>  float(int(x))
+        //   trunc(float(int(x)))    ==>  float(int(x))
+        //   nearest(float(int(x)))  ==>  float(int(x))
+        Unary* inner;
+        if (matches(curr->value, unary(&inner, any()))) {
+          switch (inner->op) {
+            case ConvertSInt32ToFloat32:
+            case ConvertSInt32ToFloat64:
+            case ConvertUInt32ToFloat32:
+            case ConvertUInt32ToFloat64:
+            case ConvertSInt64ToFloat32:
+            case ConvertSInt64ToFloat64:
+            case ConvertUInt64ToFloat32:
+            case ConvertUInt64ToFloat64: {
+              return inner;
+            }
+            default: {
+            }
+          }
+        }
+        break;
+      }
+      default: {
+      }
     }
     return nullptr;
   }
@@ -3516,7 +4318,7 @@ private:
     auto& options = getPassOptions();
 
     if (options.ignoreImplicitTraps || options.trapsNeverHappen) {
-      if (ExpressionAnalyzer::equal(memCopy->dest, memCopy->source)) {
+      if (areConsecutiveInputsEqual(memCopy->dest, memCopy->source)) {
         // memory.copy(x, x, sz)  ==>  {drop(x), drop(x), drop(sz)}
         Builder builder(*getModule());
         return builder.makeBlock({builder.makeDrop(memCopy->dest),
@@ -3542,36 +4344,53 @@ private:
         case 1:
         case 2:
         case 4: {
-          return builder.makeStore(
-            bytes, // bytes
-            0,     // offset
-            1,     // align
-            memCopy->dest,
-            builder.makeLoad(bytes, false, 0, 1, memCopy->source, Type::i32),
-            Type::i32);
+          return builder.makeStore(bytes, // bytes
+                                   0,     // offset
+                                   1,     // align
+                                   memCopy->dest,
+                                   builder.makeLoad(bytes,
+                                                    false,
+                                                    0,
+                                                    1,
+                                                    memCopy->source,
+                                                    Type::i32,
+                                                    memCopy->sourceMemory),
+                                   Type::i32,
+                                   memCopy->destMemory);
         }
         case 8: {
-          return builder.makeStore(
-            bytes, // bytes
-            0,     // offset
-            1,     // align
-            memCopy->dest,
-            builder.makeLoad(bytes, false, 0, 1, memCopy->source, Type::i64),
-            Type::i64);
+          return builder.makeStore(bytes, // bytes
+                                   0,     // offset
+                                   1,     // align
+                                   memCopy->dest,
+                                   builder.makeLoad(bytes,
+                                                    false,
+                                                    0,
+                                                    1,
+                                                    memCopy->source,
+                                                    Type::i64,
+                                                    memCopy->sourceMemory),
+                                   Type::i64,
+                                   memCopy->destMemory);
         }
         case 16: {
           if (options.shrinkLevel == 0) {
             // This adds an extra 2 bytes so apply it only for
             // minimal shrink level
             if (getModule()->features.hasSIMD()) {
-              return builder.makeStore(
-                bytes, // bytes
-                0,     // offset
-                1,     // align
-                memCopy->dest,
-                builder.makeLoad(
-                  bytes, false, 0, 1, memCopy->source, Type::v128),
-                Type::v128);
+              return builder.makeStore(bytes, // bytes
+                                       0,     // offset
+                                       1,     // align
+                                       memCopy->dest,
+                                       builder.makeLoad(bytes,
+                                                        false,
+                                                        0,
+                                                        1,
+                                                        memCopy->source,
+                                                        Type::v128,
+                                                        memCopy->sourceMemory),
+                                       Type::v128,
+                                       memCopy->destMemory);
             }
           }
           break;
@@ -3618,7 +4437,8 @@ private:
                                    align,
                                    memFill->dest,
                                    builder.makeConst<uint32_t>(value),
-                                   Type::i32);
+                                   Type::i32,
+                                   memFill->memory);
         }
         case 2: {
           return builder.makeStore(2,
@@ -3626,7 +4446,8 @@ private:
                                    align,
                                    memFill->dest,
                                    builder.makeConst<uint32_t>(value * 0x0101U),
-                                   Type::i32);
+                                   Type::i32,
+                                   memFill->memory);
         }
         case 4: {
           // transform only when "value" or shrinkLevel equal to zero due to
@@ -3638,7 +4459,8 @@ private:
               align,
               memFill->dest,
               builder.makeConst<uint32_t>(value * 0x01010101U),
-              Type::i32);
+              Type::i32,
+              memFill->memory);
           }
           break;
         }
@@ -3652,7 +4474,8 @@ private:
               align,
               memFill->dest,
               builder.makeConst<uint64_t>(value * 0x0101010101010101ULL),
-              Type::i64);
+              Type::i64,
+              memFill->memory);
           }
           break;
         }
@@ -3666,7 +4489,8 @@ private:
                                        align,
                                        memFill->dest,
                                        builder.makeConst<uint8_t[16]>(values),
-                                       Type::v128);
+                                       Type::v128,
+                                       memFill->memory);
             } else {
               // { i64.store(d, C', 0), i64.store(d, C', 8) }
               auto destType = memFill->dest->type;
@@ -3678,14 +4502,16 @@ private:
                   align,
                   builder.makeLocalTee(tempLocal, memFill->dest, destType),
                   builder.makeConst<uint64_t>(value * 0x0101010101010101ULL),
-                  Type::i64),
+                  Type::i64,
+                  memFill->memory),
                 builder.makeStore(
                   8,
                   offset + 8,
                   align,
                   builder.makeLocalGet(tempLocal, destType),
                   builder.makeConst<uint64_t>(value * 0x0101010101010101ULL),
-                  Type::i64),
+                  Type::i64,
+                  memFill->memory),
               });
             }
           }
@@ -3697,8 +4523,13 @@ private:
     }
     // memory.fill(d, v, 1)  ==>  store8(d, v)
     if (bytes == 1LL) {
-      return builder.makeStore(
-        1, offset, align, memFill->dest, memFill->value, Type::i32);
+      return builder.makeStore(1,
+                               offset,
+                               align,
+                               memFill->dest,
+                               memFill->value,
+                               Type::i32,
+                               memFill->memory);
     }
 
     return nullptr;
@@ -3746,8 +4577,9 @@ private:
     }
   }
 
+  // Invert (negate) the opcode, so that it has the exact negative meaning as it
+  // had before.
   BinaryOp invertBinaryOp(BinaryOp op) {
-    // use de-morgan's laws
     switch (op) {
       case EqInt32:
         return NeInt32;
@@ -3806,6 +4638,9 @@ private:
     }
   }
 
+  // Change the opcode so it is correct after reversing the operands. That is,
+  // we had  X OP  Y  and we need OP' so that this is equivalent to that:
+  //         Y OP' X
   BinaryOp reverseRelationalOp(BinaryOp op) {
     switch (op) {
       case EqInt32:
@@ -3927,6 +4762,11 @@ private:
       return true;
     }
     switch (binary->op) {
+      case SubFloat32:
+      case SubFloat64: {
+        // Should apply  x - C  ->  x + (-C)
+        return binary->right->is<Const>();
+      }
       case AddFloat32:
       case MulFloat32:
       case AddFloat64:
diff --git a/src/passes/PickLoadSigns.cpp b/src/passes/PickLoadSigns.cpp
index f55013f..d30e0a3 100644
--- a/src/passes/PickLoadSigns.cpp
+++ b/src/passes/PickLoadSigns.cpp
@@ -28,7 +28,9 @@ namespace wasm {
 struct PickLoadSigns : public WalkerPass<ExpressionStackWalker<PickLoadSigns>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new PickLoadSigns; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<PickLoadSigns>();
+  }
 
   struct Usage {
     Index signedUsages = 0;
diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp
index f787fb9..e47b135 100644
--- a/src/passes/Poppify.cpp
+++ b/src/passes/Poppify.cpp
@@ -87,8 +87,8 @@ namespace {
 
 // Generate names for the elements of tuple globals
 Name getGlobalElem(Module* module, Name global, Index i) {
-  return Names::getValidGlobalName(
-    *module, std::string(global.c_str()) + '$' + std::to_string(i));
+  return Names::getValidGlobalName(*module,
+                                   global.toString() + '$' + std::to_string(i));
 }
 
 struct Poppifier : BinaryenIRWriter<Poppifier> {
@@ -447,21 +447,22 @@ void Poppifier::poppify(Expression* expr) {
 
 class PoppifyFunctionsPass : public Pass {
   bool isFunctionParallel() override { return true; }
-  void
-  runOnFunction(PassRunner* runner, Module* module, Function* func) override {
+  void runOnFunction(Module* module, Function* func) override {
     if (func->profile != IRProfile::Poppy) {
       Poppifier(func, module).write();
       func->profile = IRProfile::Poppy;
     }
   }
-  Pass* create() override { return new PoppifyFunctionsPass; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<PoppifyFunctionsPass>();
+  }
 };
 
 } // anonymous namespace
 
 class PoppifyPass : public Pass {
-  void run(PassRunner* runner, Module* module) {
-    PassRunner subRunner(runner);
+  void run(Module* module) {
+    PassRunner subRunner(getPassRunner());
     subRunner.add(std::make_unique<PoppifyFunctionsPass>());
     // TODO: Enable this once it handles Poppy blocks correctly
     // subRunner.add(std::make_unique<ReFinalize>());
diff --git a/src/passes/PostEmscripten.cpp b/src/passes/PostEmscripten.cpp
index f3ad357..86b7b8b 100644
--- a/src/passes/PostEmscripten.cpp
+++ b/src/passes/PostEmscripten.cpp
@@ -41,19 +41,209 @@ static bool isInvoke(Function* F) {
   return F->imported() && F->module == ENV && F->base.startsWith("invoke_");
 }
 
+struct SegmentRemover : WalkerPass<PostWalker<SegmentRemover>> {
+  SegmentRemover(Index segment) : segment(segment) {}
+
+  bool isFunctionParallel() override { return true; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<SegmentRemover>(segment);
+  }
+
+  void visitMemoryInit(MemoryInit* curr) {
+    if (segment == curr->segment) {
+      Builder builder(*getModule());
+      replaceCurrent(builder.blockify(builder.makeDrop(curr->dest),
+                                      builder.makeDrop(curr->offset),
+                                      builder.makeDrop(curr->size)));
+    }
+  }
+
+  void visitDataDrop(DataDrop* curr) {
+    if (segment == curr->segment) {
+      Builder builder(*getModule());
+      replaceCurrent(builder.makeNop());
+    }
+  }
+
+  Index segment;
+};
+
+static void calcSegmentOffsets(Module& wasm,
+                               std::vector<Address>& segmentOffsets) {
+  const Address UNKNOWN_OFFSET(uint32_t(-1));
+
+  std::unordered_map<Index, Address> passiveOffsets;
+  if (wasm.features.hasBulkMemory()) {
+    // Fetch passive segment offsets out of memory.init instructions
+    struct OffsetSearcher : PostWalker<OffsetSearcher> {
+      std::unordered_map<Index, Address>& offsets;
+      OffsetSearcher(std::unordered_map<unsigned, Address>& offsets)
+        : offsets(offsets) {}
+      void visitMemoryInit(MemoryInit* curr) {
+        // The desitination of the memory.init is either a constant
+        // or the result of an addition with __memory_base in the
+        // case of PIC code.
+        auto* dest = curr->dest->dynCast<Const>();
+        if (!dest) {
+          auto* add = curr->dest->dynCast<Binary>();
+          if (!add) {
+            return;
+          }
+          dest = add->left->dynCast<Const>();
+          if (!dest) {
+            return;
+          }
+        }
+        auto it = offsets.find(curr->segment);
+        if (it != offsets.end()) {
+          Fatal() << "Cannot get offset of passive segment initialized "
+                     "multiple times";
+        }
+        offsets[curr->segment] = dest->value.getInteger();
+      }
+    } searcher(passiveOffsets);
+    searcher.walkModule(&wasm);
+  }
+  for (unsigned i = 0; i < wasm.dataSegments.size(); ++i) {
+    auto& segment = wasm.dataSegments[i];
+    if (segment->isPassive) {
+      auto it = passiveOffsets.find(i);
+      if (it != passiveOffsets.end()) {
+        segmentOffsets.push_back(it->second);
+      } else {
+        // This was a non-constant offset (perhaps TLS)
+        segmentOffsets.push_back(UNKNOWN_OFFSET);
+      }
+    } else if (auto* addrConst = segment->offset->dynCast<Const>()) {
+      auto address = addrConst->value.getUnsigned();
+      segmentOffsets.push_back(address);
+    } else {
+      // TODO(sbc): Wasm shared libraries have data segments with non-const
+      // offset.
+      segmentOffsets.push_back(0);
+    }
+  }
+}
+
+static void removeSegment(Module& wasm, Index segment) {
+  PassRunner runner(&wasm);
+  SegmentRemover(segment).run(&runner, &wasm);
+  // Resize the segment to zero.  In theory we should completely remove it
+  // but that would mean re-numbering the segments that follow which is
+  // non-trivial.
+  wasm.dataSegments[segment]->data.resize(0);
+}
+
+static Address getExportedAddress(Module& wasm, Export* export_) {
+  Global* g = wasm.getGlobal(export_->value);
+  auto* addrConst = g->init->dynCast<Const>();
+  return addrConst->value.getUnsigned();
+}
+
+static void removeData(Module& wasm,
+                       const std::vector<Address>& segmentOffsets,
+                       Name start_sym,
+                       Name end_sym) {
+  Export* start = wasm.getExportOrNull(start_sym);
+  Export* end = wasm.getExportOrNull(end_sym);
+  if (!start && !end) {
+    BYN_TRACE("removeData: start/stop symbols not found (" << start_sym << ", "
+                                                           << end_sym << ")\n");
+    return;
+  }
+
+  if (!start || !end) {
+    Fatal() << "Found only one of " << start_sym << " and " << end_sym;
+  }
+
+  Address startAddress = getExportedAddress(wasm, start);
+  Address endAddress = getExportedAddress(wasm, end);
+  for (Index i = 0; i < wasm.dataSegments.size(); i++) {
+    Address segmentStart = segmentOffsets[i];
+    size_t segmentSize = wasm.dataSegments[i]->data.size();
+    if (segmentStart <= startAddress &&
+        segmentStart + segmentSize >= endAddress) {
+
+      if (segmentStart == startAddress &&
+          segmentStart + segmentSize == endAddress) {
+        BYN_TRACE("removeData: removing whole segment\n");
+        removeSegment(wasm, i);
+      } else {
+        // If we can't remove the whole segment then just set the string
+        // data to zero.
+        BYN_TRACE("removeData: removing part of segment\n");
+        size_t segmentOffset = startAddress - segmentStart;
+        char* startElem = &wasm.dataSegments[i]->data[segmentOffset];
+        memset(startElem, 0, endAddress - startAddress);
+      }
+      return;
+    }
+  }
+  Fatal() << "Segment data not found between symbols " << start_sym << " ("
+          << startAddress << ") and " << end_sym << " (" << endAddress << ")";
+}
+
+IString EM_JS_PREFIX("__em_js__");
+IString EM_JS_DEPS_PREFIX("__em_lib_deps_");
+
+struct EmJsWalker : public PostWalker<EmJsWalker> {
+  std::vector<Export> toRemove;
+
+  void visitExport(Export* curr) {
+    if (curr->name.startsWith(EM_JS_PREFIX)) {
+      toRemove.push_back(*curr);
+    }
+    if (curr->name.startsWith(EM_JS_DEPS_PREFIX)) {
+      toRemove.push_back(*curr);
+    }
+  }
+};
+
 } // namespace
 
 struct PostEmscripten : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
+    removeExports(*module);
+    removeEmJsExports(*module);
     // Optimize exceptions
-    optimizeExceptions(runner, module);
+    optimizeExceptions(module);
+  }
+
+  void removeExports(Module& module) {
+    std::vector<Address> segmentOffsets; // segment index => address offset
+    calcSegmentOffsets(module, segmentOffsets);
+
+    removeData(module, segmentOffsets, "__start_em_asm", "__stop_em_asm");
+    removeData(module, segmentOffsets, "__start_em_js", "__stop_em_js");
+    removeData(
+      module, segmentOffsets, "__start_em_lib_deps", "__stop_em_lib_deps");
+    module.removeExport("__start_em_asm");
+    module.removeExport("__stop_em_asm");
+    module.removeExport("__start_em_js");
+    module.removeExport("__stop_em_js");
+    module.removeExport("__start_em_lib_deps");
+    module.removeExport("__stop_em_lib_deps");
+  }
+
+  void removeEmJsExports(Module& module) {
+    EmJsWalker walker;
+    walker.walkModule(&module);
+    for (const Export& exp : walker.toRemove) {
+      if (exp.kind == ExternalKind::Function) {
+        module.removeFunction(exp.value);
+      } else {
+        module.removeGlobal(exp.value);
+      }
+      module.removeExport(exp.name);
+    }
   }
 
   // Optimize exceptions (and setjmp) by removing unnecessary invoke* calls.
   // An invoke is a call to JS with a function pointer; JS does a try-catch
   // and calls the pointer, catching and reporting any error. If we know no
   // exception will be thrown, we can simply skip the invoke.
-  void optimizeExceptions(PassRunner* runner, Module* module) {
+  void optimizeExceptions(Module* module) {
     // First, check if this code even uses invokes.
     bool hasInvokes = false;
     for (auto& imp : module->functions) {
@@ -97,7 +287,9 @@ struct PostEmscripten : public Pass {
     struct OptimizeInvokes : public WalkerPass<PostWalker<OptimizeInvokes>> {
       bool isFunctionParallel() override { return true; }
 
-      Pass* create() override { return new OptimizeInvokes(map, flatTable); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<OptimizeInvokes>(map, flatTable);
+      }
 
       std::map<Function*, Info>& map;
       TableUtils::FlatTable& flatTable;
@@ -137,7 +329,7 @@ struct PostEmscripten : public Pass {
         }
       }
     };
-    OptimizeInvokes(analyzer.map, flatTable).run(runner, module);
+    OptimizeInvokes(analyzer.map, flatTable).run(getPassRunner(), module);
   }
 };
 
diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp
index f599990..c90fdf1 100644
--- a/src/passes/Precompute.cpp
+++ b/src/passes/Precompute.cpp
@@ -130,7 +130,7 @@ public:
   }
   Flow visitStructSet(StructSet* curr) { return Flow(NONCONSTANT_FLOW); }
   Flow visitStructGet(StructGet* curr) {
-    if (curr->ref->type != Type::unreachable) {
+    if (curr->ref->type != Type::unreachable && !curr->ref->type.isNull()) {
       // If this field is immutable then we may be able to precompute this, as
       // if we also created the data in this function (or it was created in an
       // immutable global) then we know the value in the field. If it is
@@ -164,7 +164,7 @@ public:
   }
   Flow visitArraySet(ArraySet* curr) { return Flow(NONCONSTANT_FLOW); }
   Flow visitArrayGet(ArrayGet* curr) {
-    if (curr->ref->type != Type::unreachable) {
+    if (curr->ref->type != Type::unreachable && !curr->ref->type.isNull()) {
       // See above with struct.get
       auto element = curr->ref->type.getHeapType().getArray().element;
       if (element.mutable_ == Immutable) {
@@ -190,7 +190,7 @@ public:
     } else {
       *canonical = *newGCData;
     }
-    return Literal(canonical, curr->type);
+    return Literal(canonical, curr->type.getHeapType());
   }
 };
 
@@ -199,7 +199,9 @@ struct Precompute
       PostWalker<Precompute, UnifiedExpressionVisitor<Precompute>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new Precompute(propagate); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<Precompute>(propagate);
+  }
 
   bool propagate = false;
 
@@ -249,7 +251,8 @@ struct Precompute
             curr->finalize();
             return;
           }
-        } else if (singleValue.type == Type::funcref) {
+        } else if (singleValue.type.isRef() &&
+                   singleValue.type.getHeapType() == HeapType::func) {
           if (auto* r = curr->value->template dynCast<RefFunc>()) {
             r->func = singleValue.getFunc();
             r->finalize();
@@ -513,9 +516,8 @@ private:
     if (type.isRef()) {
       return false;
     }
-    // For now, don't try to precompute an Rtt. TODO figure out when that would
-    // be safe and useful.
-    return !type.isRtt();
+
+    return true;
   }
 };
 
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index ce70f50..6dbd54e 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -50,8 +50,13 @@ bool isFullForced() {
 }
 
 std::ostream& printName(Name name, std::ostream& o) {
-  // we need to quote names if they have tricky chars
-  if (!name.str || !strpbrk(name.str, "()")) {
+  assert(name && "Cannot print an empty name");
+  // We need to quote names if they have tricky chars.
+  // TODO: This is not spec-compliant since the spec does not support quoted
+  // identifiers and has a limited set of valid idchars. We need a more robust
+  // escaping scheme here. Reusing `printEscapedString` is not sufficient,
+  // either.
+  if (name.str.find_first_of("()") == std::string_view::npos) {
     o << '$' << name.str;
   } else {
     o << "\"$" << name.str << '"';
@@ -59,7 +64,15 @@ std::ostream& printName(Name name, std::ostream& o) {
   return o;
 }
 
-static std::ostream& printLocal(Index index, Function* func, std::ostream& o) {
+std::ostream& printMemoryName(Name name, std::ostream& o, Module* wasm) {
+  if (!wasm || wasm->memories.size() > 1) {
+    o << ' ';
+    printName(name, o);
+  }
+  return o;
+}
+
+std::ostream& printLocal(Index index, Function* func, std::ostream& o) {
   Name name;
   if (func) {
     name = func->getLocalNameOrDefault(index);
@@ -70,7 +83,59 @@ static std::ostream& printLocal(Index index, Function* func, std::ostream& o) {
   return printName(name, o);
 }
 
-namespace {
+bool maybePrintRefShorthand(std::ostream& o, Type type) {
+  if (!type.isRef()) {
+    return false;
+  }
+  auto heapType = type.getHeapType();
+  if (heapType.isBasic() && type.isNullable()) {
+    switch (heapType.getBasic()) {
+      case HeapType::ext:
+        o << "externref";
+        return true;
+      case HeapType::func:
+        o << "funcref";
+        return true;
+      case HeapType::any:
+        o << "anyref";
+        return true;
+      case HeapType::eq:
+        o << "eqref";
+        return true;
+      case HeapType::i31:
+        o << "i31ref";
+        return true;
+      case HeapType::data:
+        o << "dataref";
+        return true;
+      case HeapType::array:
+        o << "arrayref";
+        return true;
+      case HeapType::string:
+        o << "stringref";
+        return true;
+      case HeapType::stringview_wtf8:
+        o << "stringview_wtf8";
+        return true;
+      case HeapType::stringview_wtf16:
+        o << "stringview_wtf16";
+        return true;
+      case HeapType::stringview_iter:
+        o << "stringview_iter";
+        return true;
+      case HeapType::none:
+        o << "nullref";
+        return true;
+      case HeapType::noext:
+        o << "nullexternref";
+        return true;
+      case HeapType::nofunc:
+        o << "nullfuncref";
+        return true;
+    }
+  }
+  return false;
+}
 
 // Helper for printing the name of a type. This output is guaranteed to not
 // contain spaces.
@@ -96,7 +161,6 @@ struct TypeNamePrinter {
   void print(const Signature& sig);
   void print(const Struct& struct_);
   void print(const Array& array);
-  void print(const Rtt& rtt);
 
   // FIXME: This hard limit on how many times we call print() avoids extremely
   //        large outputs, which can be inconveniently large in some cases, but
@@ -123,16 +187,16 @@ void TypeNamePrinter::print(Type type) {
     os << type;
   } else if (type.isTuple()) {
     print(type.getTuple());
-  } else if (type.isRtt()) {
-    print(type.getRtt());
   } else if (type.isRef()) {
-    os << "ref";
-    if (type.isNullable()) {
-      os << "?";
+    if (!maybePrintRefShorthand(os, type)) {
+      os << "ref";
+      if (type.isNullable()) {
+        os << "?";
+      }
+      os << '|';
+      print(type.getHeapType());
+      os << '|';
     }
-    os << '|';
-    print(type.getHeapType());
-    os << '|';
   } else {
     WASM_UNREACHABLE("unexpected type");
   }
@@ -243,17 +307,7 @@ void TypeNamePrinter::print(const Array& array) {
   os << ']';
 }
 
-void TypeNamePrinter::print(const Rtt& rtt) {
-  os << "rtt_";
-  if (rtt.hasDepth()) {
-    os << rtt.depth << '_';
-  }
-  print(rtt.heapType);
-}
-
-} // anonymous namespace
-
-static std::ostream& printType(std::ostream& o, Type type, Module* wasm) {
+std::ostream& printType(std::ostream& o, Type type, Module* wasm) {
   if (type.isBasic()) {
     o << type;
   } else if (type.isTuple()) {
@@ -265,37 +319,30 @@ static std::ostream& printType(std::ostream& o, Type type, Module* wasm) {
       sep = " ";
     }
     o << ')';
-  } else if (type.isRtt()) {
-    auto rtt = type.getRtt();
-    o << "(rtt ";
-    if (rtt.hasDepth()) {
-      o << rtt.depth << ' ';
-    }
-    TypeNamePrinter(o, wasm).print(rtt.heapType);
-    o << ')';
-  } else if (type.isRef() && !type.isBasic()) {
-    o << "(ref ";
-    if (type.isNullable()) {
-      o << "null ";
+  } else if (type.isRef()) {
+    if (!maybePrintRefShorthand(o, type)) {
+      o << "(ref ";
+      if (type.isNullable()) {
+        o << "null ";
+      }
+      TypeNamePrinter(o, wasm).print(type.getHeapType());
+      o << ')';
     }
-    TypeNamePrinter(o, wasm).print(type.getHeapType());
-    o << ')';
   } else {
     WASM_UNREACHABLE("unexpected type");
   }
   return o;
 }
 
-static std::ostream&
-printHeapType(std::ostream& o, HeapType type, Module* wasm) {
+std::ostream& printHeapType(std::ostream& o, HeapType type, Module* wasm) {
   TypeNamePrinter(o, wasm).print(type);
   return o;
 }
 
-static std::ostream& printPrefixedTypes(std::ostream& o,
-                                        const char* prefix,
-                                        Type type,
-                                        Module* wasm) {
+std::ostream& printPrefixedTypes(std::ostream& o,
+                                 const char* prefix,
+                                 Type type,
+                                 Module* wasm) {
   o << '(' << prefix;
   if (type == Type::none) {
     return o << ')';
@@ -315,11 +362,11 @@ static std::ostream& printPrefixedTypes(std::ostream& o,
   return o;
 }
 
-static std::ostream& printResultType(std::ostream& o, Type type, Module* wasm) {
+std::ostream& printResultType(std::ostream& o, Type type, Module* wasm) {
   return printPrefixedTypes(o, "result", type, wasm);
 }
 
-static std::ostream& printParamType(std::ostream& o, Type type, Module* wasm) {
+std::ostream& printParamType(std::ostream& o, Type type, Module* wasm) {
   return printPrefixedTypes(o, "param", type, wasm);
 }
 
@@ -344,6 +391,40 @@ void processFieldName(Module* wasm, HeapType type, Index index, T func) {
   func(Name());
 }
 
+std::ostream& printEscapedString(std::ostream& os, std::string_view str) {
+  os << '"';
+  for (unsigned char c : str) {
+    switch (c) {
+      case '\t':
+        os << "\\t";
+        break;
+      case '\n':
+        os << "\\n";
+        break;
+      case '\r':
+        os << "\\r";
+        break;
+      case '"':
+        os << "\\\"";
+        break;
+      case '\'':
+        os << "\\'";
+        break;
+      case '\\':
+        os << "\\\\";
+        break;
+      default: {
+        if (c >= 32 && c < 127) {
+          os << c;
+        } else {
+          os << std::hex << '\\' << (c / 16) << (c % 16) << std::dec;
+        }
+      }
+    }
+  }
+  return os << '"';
+}
+
 } // anonymous namespace
 
 // Printing "unreachable" as a instruction prefix type is not valid in wasm text
@@ -480,6 +561,7 @@ struct PrintExpressionContents
       o << (curr->signed_ ? "_s" : "_u");
     }
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -505,6 +587,7 @@ struct PrintExpressionContents
       }
     }
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -555,6 +638,7 @@ struct PrintExpressionContents
       o << "_u";
     }
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -568,6 +652,7 @@ struct PrintExpressionContents
       o << "_u";
     }
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -578,12 +663,14 @@ struct PrintExpressionContents
     assert(type == Type::i32 || type == Type::i64);
     o << "memory.atomic.wait" << (type == Type::i32 ? "32" : "64");
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
   }
   void visitAtomicNotify(AtomicNotify* curr) {
     printMedium(o, "memory.atomic.notify");
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -686,9 +773,6 @@ struct PrintExpressionContents
       case DotI8x16I7x16AddSToVecI32x4:
         o << "i32x4.dot_i8x16_i7x16_add_s";
         break;
-      case DotI8x16I7x16AddUToVecI32x4:
-        o << "i32x4.dot_i8x16_i7x16_add_u";
-        break;
     }
     restoreNormalColor(o);
   }
@@ -775,6 +859,7 @@ struct PrintExpressionContents
         break;
     }
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -811,6 +896,7 @@ struct PrintExpressionContents
         break;
     }
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     if (curr->offset) {
       o << " offset=" << curr->offset;
     }
@@ -823,6 +909,7 @@ struct PrintExpressionContents
     prepareColor(o);
     o << "memory.init";
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
     o << ' ' << curr->segment;
   }
   void visitDataDrop(DataDrop* curr) {
@@ -835,11 +922,14 @@ struct PrintExpressionContents
     prepareColor(o);
     o << "memory.copy";
     restoreNormalColor(o);
+    printMemoryName(curr->destMemory, o, wasm);
+    printMemoryName(curr->sourceMemory, o, wasm);
   }
   void visitMemoryFill(MemoryFill* curr) {
     prepareColor(o);
     o << "memory.fill";
     restoreNormalColor(o);
+    printMemoryName(curr->memory, o, wasm);
   }
   void visitConst(Const* curr) {
     o << curr->value.type << ".const " << curr->value;
@@ -1863,9 +1953,6 @@ struct PrintExpressionContents
       case DotI8x16I7x16SToVecI16x8:
         o << "i16x8.dot_i8x16_i7x16_s";
         break;
-      case DotI8x16I7x16UToVecI16x8:
-        o << "i16x8.dot_i8x16_i7x16_u";
-        break;
 
       case InvalidBinary:
         WASM_UNREACHABLE("unvalid binary operator");
@@ -1882,8 +1969,14 @@ struct PrintExpressionContents
   }
   void visitDrop(Drop* curr) { printMedium(o, "drop"); }
   void visitReturn(Return* curr) { printMedium(o, "return"); }
-  void visitMemorySize(MemorySize* curr) { printMedium(o, "memory.size"); }
-  void visitMemoryGrow(MemoryGrow* curr) { printMedium(o, "memory.grow"); }
+  void visitMemorySize(MemorySize* curr) {
+    printMedium(o, "memory.size");
+    printMemoryName(curr->memory, o, wasm);
+  }
+  void visitMemoryGrow(MemoryGrow* curr) {
+    printMedium(o, "memory.grow");
+    printMemoryName(curr->memory, o, wasm);
+  }
   void visitRefNull(RefNull* curr) {
     printMedium(o, "ref.null ");
     printHeapType(o, curr->type.getHeapType(), wasm);
@@ -1965,33 +2058,50 @@ struct PrintExpressionContents
   void visitI31Get(I31Get* curr) {
     printMedium(o, curr->signed_ ? "i31.get_s" : "i31.get_u");
   }
+
+  // If we cannot print a valid unreachable instruction (say, a struct.get,
+  // where if the ref is unreachable, we don't know what heap type to print),
+  // then print the children in a block, which is good enough as this
+  // instruction is never reached anyhow.
+  //
+  // This function checks if the input is in fact unreachable, and if so, begins
+  // to emit a replacement for it and returns true.
+  bool printUnreachableReplacement(Expression* curr) {
+    if (curr->type == Type::unreachable) {
+      printMedium(o, "block");
+      return true;
+    }
+    return false;
+  }
+  bool printUnreachableOrNullReplacement(Expression* curr) {
+    if (curr->type == Type::unreachable || curr->type.isNull()) {
+      printMedium(o, "block");
+      return true;
+    }
+    return false;
+  }
+
   void visitCallRef(CallRef* curr) {
-    if (curr->isReturn) {
-      printMedium(o, "return_call_ref");
-    } else {
-      printMedium(o, "call_ref");
+    // TODO: Workaround if target has bottom type.
+    if (printUnreachableOrNullReplacement(curr->target)) {
+      return;
     }
+    printMedium(o, curr->isReturn ? "return_call_ref " : "call_ref ");
+    printHeapType(o, curr->target->type.getHeapType(), wasm);
   }
   void visitRefTest(RefTest* curr) {
-    if (curr->rtt) {
-      printMedium(o, "ref.test");
-    } else {
-      printMedium(o, "ref.test_static ");
-      printHeapType(o, curr->intendedType, wasm);
-    }
+    printMedium(o, "ref.test_static ");
+    printHeapType(o, curr->intendedType, wasm);
   }
   void visitRefCast(RefCast* curr) {
-    if (curr->rtt) {
-      printMedium(o, "ref.cast");
+    if (curr->safety == RefCast::Unsafe) {
+      printMedium(o, "ref.cast_nop_static ");
     } else {
-      if (curr->safety == RefCast::Unsafe) {
-        printMedium(o, "ref.cast_nop_static ");
-      } else {
-        printMedium(o, "ref.cast_static ");
-      }
-      printHeapType(o, curr->intendedType, wasm);
+      printMedium(o, "ref.cast_static ");
     }
+    printHeapType(o, curr->intendedType, wasm);
   }
+
   void visitBrOn(BrOn* curr) {
     switch (curr->op) {
       case BrOnNull:
@@ -2001,27 +2111,17 @@ struct PrintExpressionContents
         printMedium(o, "br_on_non_null ");
         break;
       case BrOnCast:
-        if (curr->rtt) {
-          printMedium(o, "br_on_cast ");
-        } else {
-          printMedium(o, "br_on_cast_static ");
-          printName(curr->name, o);
-          o << ' ';
-          printHeapType(o, curr->intendedType, wasm);
-          return;
-        }
-        break;
+        printMedium(o, "br_on_cast_static ");
+        printName(curr->name, o);
+        o << ' ';
+        printHeapType(o, curr->intendedType, wasm);
+        return;
       case BrOnCastFail:
-        if (curr->rtt) {
-          printMedium(o, "br_on_cast_fail ");
-        } else {
-          printMedium(o, "br_on_cast_static_fail ");
-          printName(curr->name, o);
-          o << ' ';
-          printHeapType(o, curr->intendedType, wasm);
-          return;
-        }
-        break;
+        printMedium(o, "br_on_cast_static_fail ");
+        printName(curr->name, o);
+        o << ' ';
+        printHeapType(o, curr->intendedType, wasm);
+        return;
       case BrOnFunc:
         printMedium(o, "br_on_func ");
         break;
@@ -2045,34 +2145,6 @@ struct PrintExpressionContents
     }
     printName(curr->name, o);
   }
-  void visitRttCanon(RttCanon* curr) {
-    printMedium(o, "rtt.canon ");
-    TypeNamePrinter(o, wasm).print(curr->type.getRtt().heapType);
-  }
-  void visitRttSub(RttSub* curr) {
-    if (curr->fresh) {
-      printMedium(o, "rtt.fresh_sub ");
-    } else {
-      printMedium(o, "rtt.sub ");
-    }
-    TypeNamePrinter(o, wasm).print(curr->type.getRtt().heapType);
-  }
-
-  // If we cannot print a valid unreachable instruction (say, a struct.get,
-  // where if the ref is unreachable, we don't know what heap type to print),
-  // then print the children in a block, which is good enough as this
-  // instruction is never reached anyhow.
-  //
-  // This function checks if the input is in fact unreachable, and if so, begins
-  // to emit a replacement for it and returns true.
-  bool printUnreachableReplacement(Expression* curr) {
-    if (curr->type == Type::unreachable) {
-      printMedium(o, "block");
-      return true;
-    }
-    return false;
-  }
-
   void visitStructNew(StructNew* curr) {
     if (printUnreachableReplacement(curr)) {
       return;
@@ -2081,13 +2153,9 @@ struct PrintExpressionContents
     if (curr->isWithDefault()) {
       printMedium(o, "_default");
     }
-    if (curr->rtt) {
-      printMedium(o, "_with_rtt");
-    }
     o << ' ';
     TypeNamePrinter(o, wasm).print(curr->type.getHeapType());
   }
-
   void printFieldName(HeapType type, Index index) {
     processFieldName(wasm, type, index, [&](Name name) {
       if (name.is()) {
@@ -2098,7 +2166,7 @@ struct PrintExpressionContents
     });
   }
   void visitStructGet(StructGet* curr) {
-    if (printUnreachableReplacement(curr->ref)) {
+    if (printUnreachableOrNullReplacement(curr->ref)) {
       return;
     }
     auto heapType = curr->ref->type.getHeapType();
@@ -2117,7 +2185,7 @@ struct PrintExpressionContents
     printFieldName(heapType, curr->index);
   }
   void visitStructSet(StructSet* curr) {
-    if (printUnreachableReplacement(curr->ref)) {
+    if (printUnreachableOrNullReplacement(curr->ref)) {
       return;
     }
     printMedium(o, "struct.set ");
@@ -2134,25 +2202,39 @@ struct PrintExpressionContents
     if (curr->isWithDefault()) {
       printMedium(o, "_default");
     }
-    if (curr->rtt) {
-      printMedium(o, "_with_rtt");
+    o << ' ';
+    TypeNamePrinter(o, wasm).print(curr->type.getHeapType());
+  }
+  void visitArrayNewSeg(ArrayNewSeg* curr) {
+    if (printUnreachableReplacement(curr)) {
+      return;
+    }
+    printMedium(o, "array.new_");
+    switch (curr->op) {
+      case NewData:
+        printMedium(o, "data");
+
+        break;
+      case NewElem:
+        printMedium(o, "elem");
+        break;
+      default:
+        WASM_UNREACHABLE("unexpected op");
     }
     o << ' ';
     TypeNamePrinter(o, wasm).print(curr->type.getHeapType());
+    o << ' ' << curr->segment;
   }
   void visitArrayInit(ArrayInit* curr) {
     if (printUnreachableReplacement(curr)) {
       return;
     }
-    printMedium(o, "array.init");
-    if (!curr->rtt) {
-      printMedium(o, "_static");
-    }
+    printMedium(o, "array.init_static");
     o << ' ';
     TypeNamePrinter(o, wasm).print(curr->type.getHeapType());
   }
   void visitArrayGet(ArrayGet* curr) {
-    if (printUnreachableReplacement(curr->ref)) {
+    if (printUnreachableOrNullReplacement(curr->ref)) {
       return;
     }
     const auto& element = curr->ref->type.getHeapType().getArray().element;
@@ -2168,22 +2250,16 @@ struct PrintExpressionContents
     TypeNamePrinter(o, wasm).print(curr->ref->type.getHeapType());
   }
   void visitArraySet(ArraySet* curr) {
-    if (printUnreachableReplacement(curr->ref)) {
+    if (printUnreachableOrNullReplacement(curr->ref)) {
       return;
     }
     printMedium(o, "array.set ");
     TypeNamePrinter(o, wasm).print(curr->ref->type.getHeapType());
   }
-  void visitArrayLen(ArrayLen* curr) {
-    if (printUnreachableReplacement(curr->ref)) {
-      return;
-    }
-    printMedium(o, "array.len ");
-    TypeNamePrinter(o, wasm).print(curr->ref->type.getHeapType());
-  }
+  void visitArrayLen(ArrayLen* curr) { printMedium(o, "array.len"); }
   void visitArrayCopy(ArrayCopy* curr) {
-    if (printUnreachableReplacement(curr->srcRef) ||
-        printUnreachableReplacement(curr->destRef)) {
+    if (printUnreachableOrNullReplacement(curr->srcRef) ||
+        printUnreachableOrNullReplacement(curr->destRef)) {
       return;
     }
     printMedium(o, "array.copy ");
@@ -2205,10 +2281,150 @@ struct PrintExpressionContents
       case RefAsI31:
         printMedium(o, "ref.as_i31");
         break;
+      case ExternInternalize:
+        printMedium(o, "extern.internalize");
+        break;
+      case ExternExternalize:
+        printMedium(o, "extern.externalize");
+        break;
       default:
         WASM_UNREACHABLE("invalid ref.is_*");
     }
   }
+  void visitStringNew(StringNew* curr) {
+    switch (curr->op) {
+      case StringNewUTF8:
+        printMedium(o, "string.new_wtf8 utf8");
+        break;
+      case StringNewWTF8:
+        printMedium(o, "string.new_wtf8 wtf8");
+        break;
+      case StringNewReplace:
+        printMedium(o, "string.new_wtf8 replace");
+        break;
+      case StringNewWTF16:
+        printMedium(o, "string.new_wtf16");
+        break;
+      case StringNewUTF8Array:
+        printMedium(o, "string.new_wtf8_array utf8");
+        break;
+      case StringNewWTF8Array:
+        printMedium(o, "string.new_wtf8_array wtf8");
+        break;
+      case StringNewReplaceArray:
+        printMedium(o, "string.new_wtf8_array replace");
+        break;
+      case StringNewWTF16Array:
+        printMedium(o, "string.new_wtf16_array");
+        break;
+      default:
+        WASM_UNREACHABLE("invalid string.new*");
+    }
+  }
+  void visitStringConst(StringConst* curr) {
+    printMedium(o, "string.const ");
+    printEscapedString(o, curr->string.str);
+  }
+  void visitStringMeasure(StringMeasure* curr) {
+    switch (curr->op) {
+      case StringMeasureUTF8:
+        printMedium(o, "string.measure_wtf8 utf8");
+        break;
+      case StringMeasureWTF8:
+        printMedium(o, "string.measure_wtf8 wtf8");
+        break;
+      case StringMeasureWTF16:
+        printMedium(o, "string.measure_wtf16");
+        break;
+      case StringMeasureIsUSV:
+        printMedium(o, "string.is_usv_sequence");
+        break;
+      case StringMeasureWTF16View:
+        printMedium(o, "stringview_wtf16.length");
+        break;
+      default:
+        WASM_UNREACHABLE("invalid string.measure*");
+    }
+  }
+  void visitStringEncode(StringEncode* curr) {
+    switch (curr->op) {
+      case StringEncodeUTF8:
+        printMedium(o, "string.encode_wtf8 utf8");
+        break;
+      case StringEncodeWTF8:
+        printMedium(o, "string.encode_wtf8 wtf8");
+        break;
+      case StringEncodeWTF16:
+        printMedium(o, "string.encode_wtf16");
+        break;
+      case StringEncodeUTF8Array:
+        printMedium(o, "string.encode_wtf8_array utf8");
+        break;
+      case StringEncodeWTF8Array:
+        printMedium(o, "string.encode_wtf8_array wtf8");
+        break;
+      case StringEncodeWTF16Array:
+        printMedium(o, "string.encode_wtf16_array");
+        break;
+      default:
+        WASM_UNREACHABLE("invalid string.encode*");
+    }
+  }
+  void visitStringConcat(StringConcat* curr) {
+    printMedium(o, "string.concat");
+  }
+  void visitStringEq(StringEq* curr) { printMedium(o, "string.eq"); }
+  void visitStringAs(StringAs* curr) {
+    switch (curr->op) {
+      case StringAsWTF8:
+        printMedium(o, "string.as_wtf8");
+        break;
+      case StringAsWTF16:
+        printMedium(o, "string.as_wtf16");
+        break;
+      case StringAsIter:
+        printMedium(o, "string.as_iter");
+        break;
+      default:
+        WASM_UNREACHABLE("invalid string.as*");
+    }
+  }
+  void visitStringWTF8Advance(StringWTF8Advance* curr) {
+    printMedium(o, "stringview_wtf8.advance");
+  }
+  void visitStringWTF16Get(StringWTF16Get* curr) {
+    printMedium(o, "stringview_wtf16.get_codeunit");
+  }
+  void visitStringIterNext(StringIterNext* curr) {
+    printMedium(o, "stringview_iter.next");
+  }
+  void visitStringIterMove(StringIterMove* curr) {
+    switch (curr->op) {
+      case StringIterMoveAdvance:
+        printMedium(o, "stringview_iter.advance");
+        break;
+      case StringIterMoveRewind:
+        printMedium(o, "stringview_iter.rewind");
+        break;
+      default:
+        WASM_UNREACHABLE("invalid string.move*");
+    }
+  }
+  void visitStringSliceWTF(StringSliceWTF* curr) {
+    switch (curr->op) {
+      case StringSliceWTF8:
+        printMedium(o, "stringview_wtf8.slice");
+        break;
+      case StringSliceWTF16:
+        printMedium(o, "stringview_wtf16.slice");
+        break;
+      default:
+        WASM_UNREACHABLE("invalid string.slice*");
+    }
+  }
+  void visitStringSliceIter(StringSliceIter* curr) {
+    printMedium(o, "stringview_iter.slice");
+  }
 };
 
 // Prints an expression in s-expr format, including both the
@@ -2396,7 +2612,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
       }
     }
 
-    int startControlFlowDepth = controlFlowDepth;
     controlFlowDepth += stack.size();
     auto* top = stack.back();
     while (stack.size() > 0) {
@@ -2419,6 +2634,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
         }
         printFullLine(list[i]);
       }
+      controlFlowDepth--;
     }
     decIndent();
     if (full) {
@@ -2427,7 +2643,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
         o << ' ' << curr->name;
       }
     }
-    controlFlowDepth = startControlFlowDepth;
   }
   void visitIf(If* curr) {
     controlFlowDepth++;
@@ -2567,28 +2782,44 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
       drop.value = child;
       printFullLine(&drop);
     }
+    Unreachable unreachable;
+    printFullLine(&unreachable);
     decIndent();
   }
+  // This must be used for the same Expressions that use
+  // PrintExpressionContents::printUnreachableOrNullReplacement.
+  void maybePrintUnreachableOrNullReplacement(Expression* curr, Type type) {
+    if (type.isNull()) {
+      type = Type::unreachable;
+    }
+    maybePrintUnreachableReplacement(curr, type);
+  }
+  void visitCallRef(CallRef* curr) {
+    maybePrintUnreachableOrNullReplacement(curr, curr->target->type);
+  }
   void visitStructNew(StructNew* curr) {
     maybePrintUnreachableReplacement(curr, curr->type);
   }
   void visitStructSet(StructSet* curr) {
-    maybePrintUnreachableReplacement(curr, curr->ref->type);
+    maybePrintUnreachableOrNullReplacement(curr, curr->ref->type);
   }
   void visitStructGet(StructGet* curr) {
-    maybePrintUnreachableReplacement(curr, curr->ref->type);
+    maybePrintUnreachableOrNullReplacement(curr, curr->ref->type);
   }
   void visitArrayNew(ArrayNew* curr) {
     maybePrintUnreachableReplacement(curr, curr->type);
   }
+  void visitArrayNewSeg(ArrayNewSeg* curr) {
+    maybePrintUnreachableReplacement(curr, curr->type);
+  }
   void visitArrayInit(ArrayInit* curr) {
     maybePrintUnreachableReplacement(curr, curr->type);
   }
   void visitArraySet(ArraySet* curr) {
-    maybePrintUnreachableReplacement(curr, curr->ref->type);
+    maybePrintUnreachableOrNullReplacement(curr, curr->ref->type);
   }
   void visitArrayGet(ArrayGet* curr) {
-    maybePrintUnreachableReplacement(curr, curr->ref->type);
+    maybePrintUnreachableOrNullReplacement(curr, curr->ref->type);
   }
   // Module-level visitors
   void printSupertypeOr(HeapType curr, std::string noSuper) {
@@ -2715,7 +2946,8 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
   void visitExport(Export* curr) {
     o << '(';
     printMedium(o, "export ");
-    printText(o, curr->name.str) << " (";
+    // TODO: Escape the string properly.
+    printText(o, curr->name.str.data()) << " (";
     switch (curr->kind) {
       case ExternalKind::Function:
         o << "func";
@@ -2740,8 +2972,9 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
   }
   void emitImportHeader(Importable* curr) {
     printMedium(o, "import ");
-    printText(o, curr->module.str) << ' ';
-    printText(o, curr->base.str) << ' ';
+    // TODO: Escape the strings properly and use std::string_view.
+    printText(o, curr->module.str.data()) << ' ';
+    printText(o, curr->base.str.data()) << ' ';
   }
   void visitGlobal(Global* curr) {
     if (curr->imported()) {
@@ -2998,71 +3231,32 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
     o << ")";
   }
   void visitMemory(Memory* curr) {
-    if (!curr->exists) {
-      return;
-    }
     if (curr->imported()) {
       doIndent(o, indent);
       o << '(';
       emitImportHeader(curr);
-      printMemoryHeader(&currModule->memory);
+      printMemoryHeader(curr);
       o << ')' << maybeNewLine;
     } else {
       doIndent(o, indent);
       printMemoryHeader(curr);
       o << '\n';
     }
-    for (auto segment : curr->segments) {
-      doIndent(o, indent);
-      o << '(';
-      printMajor(o, "data ");
-      if (segment.name.is()) {
-        printName(segment.name, o);
-        o << ' ';
-      }
-      if (!segment.isPassive) {
-        visit(segment.offset);
-        o << ' ';
-      }
-      o << "\"";
-      for (size_t i = 0; i < segment.data.size(); i++) {
-        unsigned char c = segment.data[i];
-        switch (c) {
-          case '\n':
-            o << "\\n";
-            break;
-          case '\r':
-            o << "\\0d";
-            break;
-          case '\t':
-            o << "\\t";
-            break;
-          case '\f':
-            o << "\\0c";
-            break;
-          case '\b':
-            o << "\\08";
-            break;
-          case '\\':
-            o << "\\\\";
-            break;
-          case '"':
-            o << "\\\"";
-            break;
-          case '\'':
-            o << "\\'";
-            break;
-          default: {
-            if (c >= 32 && c < 127) {
-              o << c;
-            } else {
-              o << std::hex << '\\' << (c / 16) << (c % 16) << std::dec;
-            }
-          }
-        }
-      }
-      o << "\")" << maybeNewLine;
+  }
+  void visitDataSegment(DataSegment* curr) {
+    doIndent(o, indent);
+    o << '(';
+    printMajor(o, "data ");
+    if (curr->hasExplicitName) {
+      printName(curr->name, o);
+      o << ' ';
+    }
+    if (!curr->isPassive) {
+      visit(curr->offset);
+      o << ' ';
     }
+    printEscapedString(o, {curr->data.data(), curr->data.size()});
+    o << ')' << maybeNewLine;
   }
   void printDylinkSection(const std::unique_ptr<DylinkSection>& dylinkSection) {
     doIndent(o, indent) << ";; dylink section\n";
@@ -3113,7 +3307,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
         nontrivialGroup = currGroup->size() > 1;
         if (nontrivialGroup) {
           doIndent(o, indent);
-          o << "(rec ";
+          o << "(rec";
           incIndent();
         }
       }
@@ -3140,6 +3334,9 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
       *curr, [&](Global* global) { visitGlobal(global); });
     ModuleUtils::iterDefinedMemories(
       *curr, [&](Memory* memory) { visitMemory(memory); });
+    for (auto& segment : curr->dataSegments) {
+      visitDataSegment(segment.get());
+    }
     ModuleUtils::iterDefinedTables(*curr,
                                    [&](Table* table) { visitTable(table); });
     for (auto& segment : curr->elementSegments) {
@@ -3219,9 +3416,9 @@ public:
 
   bool modifiesBinaryenIR() override { return false; }
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     PrintSExpression print(o);
-    print.setDebugInfo(runner->options.debugInfo);
+    print.setDebugInfo(getPassOptions().debugInfo);
     print.visitModule(module);
   }
 };
@@ -3235,10 +3432,10 @@ public:
   MinifiedPrinter() = default;
   MinifiedPrinter(std::ostream* o) : Printer(o) {}
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     PrintSExpression print(o);
     print.setMinify(true);
-    print.setDebugInfo(runner->options.debugInfo);
+    print.setDebugInfo(getPassOptions().debugInfo);
     print.visitModule(module);
   }
 };
@@ -3252,10 +3449,11 @@ public:
   FullPrinter() = default;
   FullPrinter(std::ostream* o) : Printer(o) {}
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     PrintSExpression print(o);
     print.setFull(true);
-    print.setDebugInfo(runner->options.debugInfo);
+    print.setDebugInfo(getPassOptions().debugInfo);
+    print.currModule = module;
     print.visitModule(module);
   }
 };
@@ -3269,10 +3467,11 @@ public:
   PrintStackIR() = default;
   PrintStackIR(std::ostream* o) : Printer(o) {}
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     PrintSExpression print(o);
-    print.setDebugInfo(runner->options.debugInfo);
+    print.setDebugInfo(getPassOptions().debugInfo);
     print.setStackIR(true);
+    print.currModule = module;
     print.visitModule(module);
   }
 };
@@ -3347,11 +3546,7 @@ printStackInst(StackInst* inst, std::ostream& o, Function* func) {
 static std::ostream&
 printStackIR(StackIR* ir, std::ostream& o, Function* func) {
   size_t indent = func ? 2 : 0;
-  auto doIndent = [&indent, &o]() {
-    for (size_t j = 0; j < indent; j++) {
-      o << ' ';
-    }
-  };
+  auto doIndent = [&]() { o << std::string(indent, ' '); };
 
   int controlFlowDepth = 0;
   // Stack to track indices of catches within a try
@@ -3436,19 +3631,35 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
       default:
         WASM_UNREACHABLE("unexpeted op");
     }
-    std::cout << '\n';
+    o << '\n';
   }
   assert(controlFlowDepth == 0);
   return o;
 }
 
+std::ostream& printStackIR(std::ostream& o, Module* module, bool optimize) {
+  wasm::PassRunner runner(module);
+  runner.add("generate-stack-ir");
+  if (optimize) {
+    runner.add("optimize-stack-ir");
+  }
+  runner.add(std::make_unique<PrintStackIR>(&o));
+  runner.run();
+  return o;
+}
+
 } // namespace wasm
 
 namespace std {
 
 std::ostream& operator<<(std::ostream& o, wasm::Module& module) {
   wasm::PassRunner runner(&module);
-  wasm::Printer(&o).run(&runner, &module);
+  wasm::Printer printer(&o);
+  // Do not use runner.run(), since that will cause an infinite recursion in
+  // BINARYEN_PASS_DEBUG=3, which prints modules (using this function) as part
+  // of running passes.
+  printer.setPassRunner(&runner);
+  printer.run(&module);
   return o;
 }
 
diff --git a/src/passes/PrintCallGraph.cpp b/src/passes/PrintCallGraph.cpp
index 79938e6..6022c87 100644
--- a/src/passes/PrintCallGraph.cpp
+++ b/src/passes/PrintCallGraph.cpp
@@ -33,7 +33,7 @@ namespace wasm {
 struct PrintCallGraph : public Pass {
   bool modifiesBinaryenIR() override { return false; }
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     std::ostream& o = std::cout;
     o << "digraph call {\n"
          "  rankdir = LR;\n"
diff --git a/src/passes/PrintFeatures.cpp b/src/passes/PrintFeatures.cpp
index 53ef667..4b7e201 100644
--- a/src/passes/PrintFeatures.cpp
+++ b/src/passes/PrintFeatures.cpp
@@ -25,7 +25,9 @@
 namespace wasm {
 
 struct PrintFeatures : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  bool modifiesBinaryenIR() override { return false; }
+
+  void run(Module* module) override {
     module->features.iterFeatures([](FeatureSet::Feature f) {
       std::cout << "--enable-" << FeatureSet::toString(f) << std::endl;
     });
diff --git a/src/passes/PrintFunctionMap.cpp b/src/passes/PrintFunctionMap.cpp
index 325f4e4..08f5a35 100644
--- a/src/passes/PrintFunctionMap.cpp
+++ b/src/passes/PrintFunctionMap.cpp
@@ -34,10 +34,10 @@ namespace wasm {
 struct PrintFunctionMap : public Pass {
   bool modifiesBinaryenIR() override { return false; }
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     // If an argument is provided, write to that file; otherwise write to
     // stdout.
-    auto outFile = runner->options.getArgumentOrDefault("symbolmap", "");
+    auto outFile = getPassOptions().getArgumentOrDefault("symbolmap", "");
     Output output(outFile, Flags::Text);
     auto& o = output.getStream();
     Index i = 0;
diff --git a/src/passes/ReReloop.cpp b/src/passes/ReReloop.cpp
index 4499bb0..1e03900 100644
--- a/src/passes/ReReloop.cpp
+++ b/src/passes/ReReloop.cpp
@@ -36,7 +36,9 @@ namespace wasm {
 struct ReReloop final : public Pass {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new ReReloop; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<ReReloop>();
+  }
 
   std::unique_ptr<CFG::Relooper> relooper;
   std::unique_ptr<Builder> builder;
@@ -100,7 +102,7 @@ struct ReReloop final : public Pass {
     virtual void run() { WASM_UNREACHABLE("unimpl"); }
   };
 
-  typedef std::shared_ptr<Task> TaskPtr;
+  using TaskPtr = std::shared_ptr<Task>;
   std::vector<TaskPtr> stack;
 
   struct TriageTask final : public Task {
@@ -291,9 +293,7 @@ struct ReReloop final : public Pass {
     // TODO: optimize with this?
   }
 
-  void runOnFunction(PassRunner* runner,
-                     Module* module,
-                     Function* function) override {
+  void runOnFunction(Module* module, Function* function) override {
     Flat::verifyFlatness(function);
 
     // since control flow is flattened, this is pretty simple
diff --git a/src/passes/RedundantSetElimination.cpp b/src/passes/RedundantSetElimination.cpp
index 90925ed..4f82d51 100644
--- a/src/passes/RedundantSetElimination.cpp
+++ b/src/passes/RedundantSetElimination.cpp
@@ -39,6 +39,7 @@
 #include <ir/properties.h>
 #include <ir/utils.h>
 #include <pass.h>
+#include <support/small_set.h>
 #include <support/unique_deferring_queue.h>
 #include <wasm-builder.h>
 #include <wasm.h>
@@ -54,7 +55,7 @@ namespace {
 // information in a basic block
 struct Info {
   LocalValues start, end; // the local values at the start and end of the block
-  std::vector<Expression**> setps;
+  std::vector<Expression**> items;
 };
 
 struct RedundantSetElimination
@@ -63,16 +64,27 @@ struct RedundantSetElimination
                                 Info>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new RedundantSetElimination(); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<RedundantSetElimination>();
+  }
 
   Index numLocals;
 
+  // In rare cases we make a change to a type that requires a refinalize.
+  bool refinalize = false;
+
   // cfg traversal work
 
+  static void doVisitLocalGet(RedundantSetElimination* self,
+                              Expression** currp) {
+    if (self->currBasicBlock) {
+      self->currBasicBlock->contents.items.push_back(currp);
+    }
+  }
   static void doVisitLocalSet(RedundantSetElimination* self,
                               Expression** currp) {
     if (self->currBasicBlock) {
-      self->currBasicBlock->contents.setps.push_back(currp);
+      self->currBasicBlock->contents.items.push_back(currp);
     }
   }
 
@@ -93,7 +105,11 @@ struct RedundantSetElimination
     // flow values across blocks
     flowValues(func);
     // remove redundant sets
-    optimize();
+    optimize(func);
+
+    if (refinalize) {
+      ReFinalize().walkFunctionInModule(func, this->getModule());
+    }
   }
 
   // Use a value numbering for the values of expressions.
@@ -286,10 +302,13 @@ struct RedundantSetElimination
       // flow values through it, then add those we can reach if they need an
       // update.
       auto currValues = curr->contents.start; // we'll modify this as we go
-      auto& setps = curr->contents.setps;
-      for (auto** setp : setps) {
-        auto* set = (*setp)->cast<LocalSet>();
-        currValues[set->index] = getValue(set->value, currValues);
+      auto& items = curr->contents.items;
+      for (auto** item : items) {
+        if (auto* set = (*item)->dynCast<LocalSet>()) {
+          auto* value = Properties::getFallthrough(
+            set->value, getPassOptions(), *getModule());
+          currValues[set->index] = getValue(value, currValues);
+        }
       }
       if (currValues == curr->contents.end) {
         // nothing changed, so no more work to do
@@ -317,36 +336,108 @@ struct RedundantSetElimination
   }
 
   // optimizing
-  void optimize() {
+  void optimize(Function* func) {
+    // Find which locals are refinable, that is, that when we see a global.get
+    // of them we may consider switching to another local index that has the
+    // same value but in a refined type. Computing which locals are relevant for
+    // that optimization is efficient because it avoids a bunch of work below
+    // for hashing numbers etc.
+    std::vector<bool> isRefinable(numLocals, false);
+    for (Index i = 0; i < numLocals; i++) {
+      // TODO: we could also note which locals have "maximal" types, where no
+      //       other local is a refinement of them
+      if (func->getLocalType(i).isRef()) {
+        isRefinable[i] = true;
+      }
+    }
+
     // in each block, run the values through the sets,
     // and remove redundant sets when we see them
     for (auto& block : basicBlocks) {
       auto currValues = block->contents.start; // we'll modify this as we go
-      auto& setps = block->contents.setps;
-      for (auto** setp : setps) {
-        auto* set = (*setp)->cast<LocalSet>();
-        auto oldValue = currValues[set->index];
-        auto newValue = getValue(set->value, currValues);
-        auto index = set->index;
-        if (newValue == oldValue) {
-          remove(setp);
-          continue; // no more work to do
+      auto& items = block->contents.items;
+
+      // Set up the equivalences at the beginning of the block. We'll update
+      // them as we go, so we can use them at any point in the middle. This data
+      // structure maps a value number to the local indexes that have that
+      // value.
+      //
+      // Note that the set here must be ordered to avoid nondeterminism when
+      // picking between multiple equally-good indexes (we'll pick the first in
+      // the iteration, which will have the lowest index).
+      std::unordered_map<Index, SmallSet<Index, 3>> valueToLocals;
+      assert(currValues.size() == numLocals);
+      for (Index i = 0; i < numLocals; i++) {
+        if (isRefinable[i]) {
+          valueToLocals[currValues[i]].insert(i);
+        }
+      }
+
+      for (auto** item : items) {
+        if (auto* set = (*item)->dynCast<LocalSet>()) {
+          auto oldValue = currValues[set->index];
+          auto* value = Properties::getFallthrough(
+            set->value, getPassOptions(), *getModule());
+          auto newValue = getValue(value, currValues);
+          auto index = set->index;
+          if (newValue == oldValue) {
+            remove(item);
+          } else {
+            // update for later steps
+            currValues[index] = newValue;
+            if (isRefinable[index]) {
+              valueToLocals[oldValue].erase(index);
+              valueToLocals[newValue].insert(index);
+            }
+          }
+          continue;
+        }
+
+        // For gets, see if there is another index with that value, of a more
+        // refined type.
+        auto* get = (*item)->dynCast<LocalGet>();
+        if (!isRefinable[get->index]) {
+          continue;
+        }
+
+        for (auto i : valueToLocals[getValue(get, currValues)]) {
+          auto currType = func->getLocalType(get->index);
+          auto possibleType = func->getLocalType(i);
+          if (possibleType != currType &&
+              Type::isSubType(possibleType, currType)) {
+            // We found an improvement!
+            get->index = i;
+            get->type = possibleType;
+            refinalize = true;
+          }
         }
-        // update for later steps
-        currValues[index] = newValue;
       }
     }
   }
 
-  void remove(Expression** setp) {
-    auto* set = (*setp)->cast<LocalSet>();
+  void remove(Expression** item) {
+    auto* set = (*item)->cast<LocalSet>();
     auto* value = set->value;
     if (!set->isTee()) {
       auto* drop = ExpressionManipulator::convert<LocalSet, Drop>(set);
       drop->value = value;
       drop->finalize();
     } else {
-      *setp = value;
+      // If we are replacing the set with something of a more specific type,
+      // then we need to refinalize, for example:
+      //
+      //  (struct.get $X 0
+      //    (local.tee $x
+      //      (..something of type $Y, a subtype of $X..)
+      //    )
+      //  )
+      //
+      // After the replacement the struct.get will read from $Y, whose field may
+      // have a more refined type.
+      if (value->type != set->type) {
+        refinalize = true;
+      }
+      *item = value;
     }
   }
 
@@ -364,8 +455,8 @@ struct RedundantSetElimination
       std::cout << "  start[" << i << "] = " << block->contents.start[i]
                 << '\n';
     }
-    for (auto** setp : block->contents.setps) {
-      std::cout << "  " << *setp << '\n';
+    for (auto** item : block->contents.items) {
+      std::cout << "  " << *item << '\n';
     }
     std::cout << "====\n";
   }
diff --git a/src/passes/RemoveMemory.cpp b/src/passes/RemoveMemory.cpp
index 399a329..1520be1 100644
--- a/src/passes/RemoveMemory.cpp
+++ b/src/passes/RemoveMemory.cpp
@@ -24,8 +24,8 @@
 namespace wasm {
 
 struct RemoveMemory : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    module->memory.segments.clear();
+  void run(Module* module) override {
+    module->removeDataSegments([&](DataSegment* curr) { return true; });
   }
 };
 
diff --git a/src/passes/RemoveNonJSOps.cpp b/src/passes/RemoveNonJSOps.cpp
index 1c112e7..227046b 100644
--- a/src/passes/RemoveNonJSOps.cpp
+++ b/src/passes/RemoveNonJSOps.cpp
@@ -56,7 +56,9 @@ struct RemoveNonJSOpsPass : public WalkerPass<PostWalker<RemoveNonJSOpsPass>> {
 
   bool isFunctionParallel() override { return false; }
 
-  Pass* create() override { return new RemoveNonJSOpsPass; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<RemoveNonJSOpsPass>();
+  }
 
   void doWalkModule(Module* module) {
     // Intrinsics may use scratch memory, ensure it.
@@ -122,7 +124,7 @@ struct RemoveNonJSOpsPass : public WalkerPass<PostWalker<RemoveNonJSOpsPass>> {
     }
 
     // Intrinsics may use memory, so ensure the module has one.
-    MemoryUtils::ensureExists(module->memory);
+    MemoryUtils::ensureExists(module);
 
     // Add missing globals
     for (auto& [name, type] : neededImportedGlobals) {
@@ -339,7 +341,9 @@ struct StubUnsupportedJSOpsPass
   : public WalkerPass<PostWalker<StubUnsupportedJSOpsPass>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new StubUnsupportedJSOpsPass; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<StubUnsupportedJSOpsPass>();
+  }
 
   void visitUnary(Unary* curr) {
     switch (curr->op) {
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp
index f383f66..f465d46 100644
--- a/src/passes/RemoveUnusedBrs.cpp
+++ b/src/passes/RemoveUnusedBrs.cpp
@@ -85,6 +85,9 @@ static bool canTurnIfIntoBrIf(Expression* ifCondition,
 // It can be tuned more later.
 const Index TooCostlyToRunUnconditionally = 9;
 
+static_assert(TooCostlyToRunUnconditionally < CostAnalyzer::Unacceptable,
+              "We never run code unconditionally if it has unacceptable cost");
+
 // Check if it is not worth it to run code unconditionally. This
 // assumes we are trying to run two expressions where previously
 // only one of the two might have executed. We assume here that
@@ -114,11 +117,13 @@ static bool tooCostlyToRunUnconditionally(const PassOptions& passOptions,
 struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new RemoveUnusedBrs; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<RemoveUnusedBrs>();
+  }
 
   bool anotherCycle;
 
-  typedef std::vector<Expression**> Flows;
+  using Flows = std::vector<Expression**>;
 
   // list of breaks that are currently flowing. if they reach their target
   // without interference, they can be removed (or their value forwarded TODO)
@@ -200,11 +205,14 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
         if (skip > 0) {
           flows.resize(size - skip);
         }
-        // drop a nop at the end of a block, which prevents a value flowing
-        while (list.size() > 0 && list.back()->is<Nop>()) {
-          list.resize(list.size() - 1);
-          self->anotherCycle = true;
-        }
+      }
+      // Drop a nop at the end of a block, which prevents a value flowing. Note
+      // that this is worth doing regardless of whether we have a name on this
+      // block or not (which the if right above us checks) - such a nop is
+      // always unneeded and can limit later optimizations.
+      while (list.size() > 0 && list.back()->is<Nop>()) {
+        list.resize(list.size() - 1);
+        self->anotherCycle = true;
       }
       // A value flowing is only valid if it is a value that the block actually
       // flows out. If it is never reached, it does not flow out, and may be
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp
index 686a950..7feda6f 100644
--- a/src/passes/RemoveUnusedModuleElements.cpp
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -32,12 +32,14 @@
 
 namespace wasm {
 
+// TODO: Add data segment, multiple memories (#5224)
 enum class ModuleElementKind { Function, Global, Tag, Table, ElementSegment };
 
-typedef std::pair<ModuleElementKind, Name> ModuleElement;
+using ModuleElement = std::pair<ModuleElementKind, Name>;
 
 // Finds reachabilities
 // TODO: use Effects to determine if a memory is used
+// This pass does not have multi-memories support
 
 struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
   Module* module;
@@ -70,9 +72,9 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
     : module(module) {
     queue = roots;
     // Globals used in memory/table init expressions are also roots
-    for (auto& segment : module->memory.segments) {
-      if (!segment.isPassive) {
-        walk(segment.offset);
+    for (auto& segment : module->dataSegments) {
+      if (!segment->isPassive) {
+        walk(segment->offset);
       }
     }
     for (auto& segment : module->elementSegments) {
@@ -132,7 +134,9 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
       // handle this automatically by the reference flowing out to an import,
       // which is what binaryen intrinsics look like. For now, to support use
       // cases of a closed world but that also use this intrinsic, handle the
-      // intrinsic specifically here.
+      // intrinsic specifically here. (Without that, the closed world assumption
+      // makes us ignore the function ref that flows to an import, so we are not
+      // aware that it is actually called.)
       auto* target = curr->operands.back();
       if (auto* refFunc = target->dynCast<RefFunc>()) {
         // We can see exactly where this goes.
@@ -192,7 +196,10 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
   void visitAtomicNotify(AtomicNotify* curr) { usesMemory = true; }
   void visitAtomicFence(AtomicFence* curr) { usesMemory = true; }
   void visitMemoryInit(MemoryInit* curr) { usesMemory = true; }
-  void visitDataDrop(DataDrop* curr) { usesMemory = true; }
+  void visitDataDrop(DataDrop* curr) {
+    // TODO: Replace this with a use of a data segment (#5224).
+    usesMemory = true;
+  }
   void visitMemoryCopy(MemoryCopy* curr) { usesMemory = true; }
   void visitMemoryFill(MemoryFill* curr) { usesMemory = true; }
   void visitMemorySize(MemorySize* curr) { usesMemory = true; }
@@ -224,22 +231,39 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
       maybeAdd(ModuleElement(ModuleElementKind::Tag, tag));
     }
   }
+  void visitArrayNewSeg(ArrayNewSeg* curr) {
+    switch (curr->op) {
+      case NewData:
+        // TODO: Replace this with a use of the specific data segment (#5224).
+        usesMemory = true;
+        return;
+      case NewElem:
+        auto segment = module->elementSegments[curr->segment]->name;
+        maybeAdd(ModuleElement(ModuleElementKind::ElementSegment, segment));
+        return;
+    }
+    WASM_UNREACHABLE("unexpected op");
+  }
 };
 
 struct RemoveUnusedModuleElements : public Pass {
+  // This pass only removes module elements, it never modifies function
+  // contents.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   bool rootAllFunctions;
 
   RemoveUnusedModuleElements(bool rootAllFunctions)
     : rootAllFunctions(rootAllFunctions) {}
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     std::vector<ModuleElement> roots;
     // Module start is a root.
     if (module->start.is()) {
       auto startFunction = module->getFunction(module->start);
       // Can be skipped if the start function is empty.
       if (!startFunction->imported() && startFunction->body->is<Nop>()) {
-        module->start.clear();
+        module->start = Name{};
       } else {
         roots.emplace_back(ModuleElementKind::Function, module->start);
       }
@@ -279,7 +303,7 @@ struct RemoveUnusedModuleElements : public Pass {
     }
     // Check for special imports, which are roots.
     bool importsMemory = false;
-    if (module->memory.imported()) {
+    if (!module->memories.empty() && module->memories[0]->imported()) {
       importsMemory = true;
     }
     // For now, all functions that can be called indirectly are marked as roots.
@@ -340,8 +364,7 @@ struct RemoveUnusedModuleElements : public Pass {
                ModuleElement(ModuleElementKind::Tag, curr->name)) == 0;
     });
     module->removeElementSegments([&](ElementSegment* curr) {
-      return curr->data.empty() ||
-             analyzer.reachable.count(ModuleElement(
+      return analyzer.reachable.count(ModuleElement(
                ModuleElementKind::ElementSegment, curr->name)) == 0;
     });
     // Since we've removed all empty element segments, here we mark all tables
@@ -365,13 +388,10 @@ struct RemoveUnusedModuleElements : public Pass {
       if (!importsMemory) {
         // The memory is unobservable to the outside, we can remove the
         // contents.
-        module->memory.segments.clear();
+        module->removeDataSegments([&](DataSegment* curr) { return true; });
       }
-      if (module->memory.segments.empty()) {
-        module->memory.exists = false;
-        module->memory.module = module->memory.base = Name();
-        module->memory.initial = 0;
-        module->memory.max = 0;
+      if (module->dataSegments.empty() && !module->memories.empty()) {
+        module->removeMemory(module->memories[0]->name);
       }
     }
   }
diff --git a/src/passes/RemoveUnusedNames.cpp b/src/passes/RemoveUnusedNames.cpp
index a08cb5e..e7cc4ae 100644
--- a/src/passes/RemoveUnusedNames.cpp
+++ b/src/passes/RemoveUnusedNames.cpp
@@ -31,7 +31,13 @@ struct RemoveUnusedNames
                                  UnifiedExpressionVisitor<RemoveUnusedNames>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new RemoveUnusedNames; }
+  // This pass only removes names, which can only help validation (as blocks
+  // without names are ignored, see the README section on non-nullable locals).
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<RemoveUnusedNames>();
+  }
 
   // We maintain a list of branches that we saw in children, then when we reach
   // a parent block, we know if it was branched to
diff --git a/src/passes/ReorderFunctions.cpp b/src/passes/ReorderFunctions.cpp
index 3268939..eaff3ac 100644
--- a/src/passes/ReorderFunctions.cpp
+++ b/src/passes/ReorderFunctions.cpp
@@ -35,14 +35,18 @@
 
 namespace wasm {
 
-typedef std::unordered_map<Name, std::atomic<Index>> NameCountMap;
+using NameCountMap = std::unordered_map<Name, std::atomic<Index>>;
 
 struct CallCountScanner : public WalkerPass<PostWalker<CallCountScanner>> {
   bool isFunctionParallel() override { return true; }
 
+  bool modifiesBinaryenIR() override { return false; }
+
   CallCountScanner(NameCountMap* counts) : counts(counts) {}
 
-  CallCountScanner* create() override { return new CallCountScanner(counts); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<CallCountScanner>(counts);
+  }
 
   void visitCall(Call* curr) {
     // can't add a new element in parallel
@@ -55,7 +59,10 @@ private:
 };
 
 struct ReorderFunctions : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  // Only reorders functions, does not change their contents.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
     NameCountMap counts;
     // fill in info, as we operate on it in parallel (each function to its own
     // entry)
@@ -63,7 +70,7 @@ struct ReorderFunctions : public Pass {
       counts[func->name];
     }
     // find counts on function calls
-    CallCountScanner(&counts).run(runner, module);
+    CallCountScanner(&counts).run(getPassRunner(), module);
     // find counts on global usages
     if (module->start.is()) {
       counts[module->start]++;
@@ -79,7 +86,7 @@ struct ReorderFunctions : public Pass {
               [&counts](const std::unique_ptr<Function>& a,
                         const std::unique_ptr<Function>& b) -> bool {
                 if (counts[a->name] == counts[b->name]) {
-                  return strcmp(a->name.str, b->name.str) > 0;
+                  return a->name > b->name;
                 }
                 return counts[a->name] > counts[b->name];
               });
diff --git a/src/passes/ReorderGlobals.cpp b/src/passes/ReorderGlobals.cpp
new file mode 100644
index 0000000..d1c26b2
--- /dev/null
+++ b/src/passes/ReorderGlobals.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Sorts globals by their static use count. This helps reduce the size of wasm
+// binaries because fewer bytes are needed to encode references to frequently
+// used globals.
+//
+
+#include "memory"
+
+#include "ir/find_all.h"
+#include "pass.h"
+#include "support/topological_sort.h"
+#include "wasm.h"
+
+namespace wasm {
+
+using NameCountMap = std::unordered_map<Name, std::atomic<Index>>;
+
+struct UseCountScanner : public WalkerPass<PostWalker<UseCountScanner>> {
+  bool isFunctionParallel() override { return true; }
+
+  bool modifiesBinaryenIR() override { return false; }
+
+  UseCountScanner(NameCountMap& counts) : counts(counts) {}
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<UseCountScanner>(counts);
+  }
+
+  void visitGlobalGet(GlobalGet* curr) {
+    // We can't add a new element to the map in parallel.
+    assert(counts.count(curr->name) > 0);
+    counts[curr->name]++;
+  }
+  void visitGlobalSet(GlobalSet* curr) {
+    assert(counts.count(curr->name) > 0);
+    counts[curr->name]++;
+  }
+
+private:
+  NameCountMap& counts;
+};
+
+struct ReorderGlobals : public Pass {
+  // Whether to always reorder globals, even if there are very few and the
+  // benefit is minor. That is useful for testing.
+  bool always;
+
+  ReorderGlobals(bool always) : always(always) {}
+
+  void run(Module* module) override {
+    if (module->globals.size() < 128 && !always) {
+      // The module has so few globals that they all fit in a single-byte U32LEB
+      // value, so no reordering we can do can actually decrease code size. Note
+      // that this is the common case with wasm MVP modules where the only
+      // globals are typically the stack pointer and perhaps a handful of others
+      // (however, features like wasm GC there may be a great many globals).
+      return;
+    }
+
+    NameCountMap counts;
+    // Fill in info, as we'll operate on it in parallel.
+    for (auto& global : module->globals) {
+      counts[global->name];
+    }
+
+    // Count uses.
+    UseCountScanner scanner(counts);
+    scanner.run(getPassRunner(), module);
+    scanner.runOnModuleCode(getPassRunner(), module);
+
+    // Do a toplogical sort to ensure we keep dependencies before the things
+    // that need them. For example, if $b's definition depends on $a then $b
+    // must appear later:
+    //
+    //   (global $a i32 (i32.const 10))
+    //   (global $b i32 (global.get $a)) ;; $b depends on $a
+    //
+    struct DependencySort : TopologicalSort<Name, DependencySort> {
+      Module& wasm;
+      const NameCountMap& counts;
+
+      std::unordered_map<Name, std::vector<Name>> deps;
+
+      DependencySort(Module& wasm, const NameCountMap& counts)
+        : wasm(wasm), counts(counts) {
+        // Sort a list of global names by their counts.
+        auto sort = [&](std::vector<Name>& globals) {
+          std::stable_sort(
+            globals.begin(), globals.end(), [&](const Name& a, const Name& b) {
+              return counts.at(a) < counts.at(b);
+            });
+        };
+
+        // Sort the globals.
+        std::vector<Name> sortedNames;
+        for (auto& global : wasm.globals) {
+          sortedNames.push_back(global->name);
+        }
+        sort(sortedNames);
+
+        // Everything is a root (we need to emit all globals).
+        for (auto global : sortedNames) {
+          push(global);
+        }
+
+        // The dependencies are the globals referred to.
+        for (auto& global : wasm.globals) {
+          if (global->imported()) {
+            continue;
+          }
+          std::vector<Name> vec;
+          for (auto* get : FindAll<GlobalGet>(global->init).list) {
+            vec.push_back(get->name);
+          }
+          sort(vec);
+          deps[global->name] = std::move(vec);
+        }
+      }
+
+      void pushPredecessors(Name global) {
+        for (auto pred : deps[global]) {
+          push(pred);
+        }
+      }
+    };
+
+    std::unordered_map<Name, Index> sortedIndexes;
+    for (auto global : DependencySort(*module, counts)) {
+      auto index = sortedIndexes.size();
+      sortedIndexes[global] = index;
+    }
+
+    std::sort(
+      module->globals.begin(),
+      module->globals.end(),
+      [&](const std::unique_ptr<Global>& a, const std::unique_ptr<Global>& b) {
+        return sortedIndexes[a->name] < sortedIndexes[b->name];
+      });
+
+    module->updateMaps();
+  }
+};
+
+Pass* createReorderGlobalsPass() { return new ReorderGlobals(false); }
+
+Pass* createReorderGlobalsAlwaysPass() { return new ReorderGlobals(true); }
+
+} // namespace wasm
diff --git a/src/passes/ReorderLocals.cpp b/src/passes/ReorderLocals.cpp
index 7b2de0d..d931cca 100644
--- a/src/passes/ReorderLocals.cpp
+++ b/src/passes/ReorderLocals.cpp
@@ -32,7 +32,12 @@ namespace wasm {
 struct ReorderLocals : public WalkerPass<PostWalker<ReorderLocals>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new ReorderLocals; }
+  // Sorting and removing unused locals cannot affect validation.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<ReorderLocals>();
+  }
 
   // local index => times it is used
   std::vector<Index> counts;
diff --git a/src/passes/RoundTrip.cpp b/src/passes/RoundTrip.cpp
index f2bdffe..0e289d5 100644
--- a/src/passes/RoundTrip.cpp
+++ b/src/passes/RoundTrip.cpp
@@ -28,7 +28,7 @@
 namespace wasm {
 
 struct RoundTrip : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     BufferWithRandomAccess buffer;
     // Save features, which would not otherwise make it through a round trip if
     // the target features section has been stripped. We also need them in order
@@ -39,7 +39,7 @@ struct RoundTrip : public Pass {
     ModuleUtils::clearModule(*module);
     auto input = buffer.getAsChars();
     WasmBinaryBuilder parser(*module, features, input);
-    parser.setDWARF(runner->options.debugInfo);
+    parser.setDWARF(getPassOptions().debugInfo);
     try {
       parser.read();
     } catch (ParseException& p) {
diff --git a/src/passes/SSAify.cpp b/src/passes/SSAify.cpp
index 07867b3..3d9b8bd 100644
--- a/src/passes/SSAify.cpp
+++ b/src/passes/SSAify.cpp
@@ -53,7 +53,6 @@
 #include "ir/find_all.h"
 #include "ir/literal-utils.h"
 #include "ir/local-graph.h"
-#include "ir/type-updating.h"
 #include "pass.h"
 #include "support/permutations.h"
 #include "wasm-builder.h"
@@ -74,7 +73,9 @@ struct SSAify : public Pass {
   // FIXME DWARF updating does not handle local changes yet.
   bool invalidatesDWARF() override { return true; }
 
-  Pass* create() override { return new SSAify(allowMerges); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<SSAify>(allowMerges);
+  }
 
   SSAify(bool allowMerges) : allowMerges(allowMerges) {}
 
@@ -85,8 +86,7 @@ struct SSAify : public Pass {
   // things we add to the function prologue
   std::vector<Expression*> functionPrepends;
 
-  void
-  runOnFunction(PassRunner* runner, Module* module_, Function* func_) override {
+  void runOnFunction(Module* module_, Function* func_) override {
     module = module_;
     func = func_;
     LocalGraph graph(func);
@@ -99,8 +99,6 @@ struct SSAify : public Pass {
     computeGetsAndPhis(graph);
     // add prepends to function
     addPrepends();
-    // Handle non-nullability in new locals we added.
-    TypeUpdating::handleNonDefaultableLocals(func, *module);
   }
 
   void createNewIndexes(LocalGraph& graph) {
@@ -140,10 +138,14 @@ struct SSAify : public Pass {
           // no set, assign param or zero
           if (func->isParam(get->index)) {
             // leave it, it's fine
-          } else {
+          } else if (LiteralUtils::canMakeZero(get->type)) {
             // zero it out
             (*graph.locations[get]) =
               LiteralUtils::makeZero(get->type, *module);
+          } else {
+            // No zero exists here, so this is a nondefaultable type. The
+            // default won't be used anyhow, so this value does not really
+            // matter and we have nothing to do.
           }
         }
         continue;
diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp
index 068c8ef..0e9e8ae 100644
--- a/src/passes/SafeHeap.cpp
+++ b/src/passes/SafeHeap.cpp
@@ -69,8 +69,8 @@ struct AccessInstrumenter : public WalkerPass<PostWalker<AccessInstrumenter>> {
 
   bool isFunctionParallel() override { return true; }
 
-  AccessInstrumenter* create() override {
-    return new AccessInstrumenter(ignoreFunctions);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<AccessInstrumenter>(ignoreFunctions);
   }
 
   AccessInstrumenter(std::set<Name> ignoreFunctions)
@@ -82,10 +82,11 @@ struct AccessInstrumenter : public WalkerPass<PostWalker<AccessInstrumenter>> {
       return;
     }
     Builder builder(*getModule());
-    replaceCurrent(
-      builder.makeCall(getLoadName(curr),
-                       {curr->ptr, builder.makeConstPtr(curr->offset.addr)},
-                       curr->type));
+    auto memory = getModule()->getMemory(curr->memory);
+    replaceCurrent(builder.makeCall(
+      getLoadName(curr),
+      {curr->ptr, builder.makeConstPtr(curr->offset.addr, memory->indexType)},
+      curr->type));
   }
 
   void visitStore(Store* curr) {
@@ -94,9 +95,12 @@ struct AccessInstrumenter : public WalkerPass<PostWalker<AccessInstrumenter>> {
       return;
     }
     Builder builder(*getModule());
+    auto memory = getModule()->getMemory(curr->memory);
     replaceCurrent(builder.makeCall(
       getStoreName(curr),
-      {curr->ptr, builder.makeConstPtr(curr->offset.addr), curr->value},
+      {curr->ptr,
+       builder.makeConstPtr(curr->offset.addr, memory->indexType),
+       curr->value},
       Type::none));
   }
 };
@@ -127,10 +131,9 @@ static std::set<Name> findCalledFunctions(Module* module, Name startFunc) {
 }
 
 struct SafeHeap : public Pass {
-  PassOptions options;
 
-  void run(PassRunner* runner, Module* module) override {
-    options = runner->options;
+  void run(Module* module) override {
+    assert(!module->memories.empty());
     // add imports
     addImports(module);
     // instrument loads and stores
@@ -142,7 +145,7 @@ struct SafeHeap : public Pass {
     // not available until after it has run.
     std::set<Name> ignoreFunctions = findCalledFunctions(module, module->start);
     ignoreFunctions.insert(getSbrkPtr);
-    AccessInstrumenter(ignoreFunctions).run(runner, module);
+    AccessInstrumenter(ignoreFunctions).run(getPassRunner(), module);
     // add helper checking funcs and imports
     addGlobals(module, module->features);
   }
@@ -151,7 +154,7 @@ struct SafeHeap : public Pass {
 
   void addImports(Module* module) {
     ImportInfo info(*module);
-    auto indexType = module->memory.indexType;
+    auto indexType = module->memories[0]->indexType;
     if (auto* existing = info.getImportedFunction(ENV, GET_SBRK_PTR)) {
       getSbrkPtr = existing->name;
     } else if (auto* existing = module->getExportOrNull(GET_SBRK_PTR)) {
@@ -202,6 +205,7 @@ struct SafeHeap : public Pass {
         continue;
       }
       load.type = type;
+      load.memory = module->memories[0]->name;
       for (Index bytes : {1, 2, 4, 8, 16}) {
         load.bytes = bytes;
         if (bytes > type.getByteSize() || (type == Type::f32 && bytes != 4) ||
@@ -221,8 +225,9 @@ struct SafeHeap : public Pass {
             }
             for (auto isAtomic : {true, false}) {
               load.isAtomic = isAtomic;
-              if (isAtomic && !isPossibleAtomicOperation(
-                                align, bytes, module->memory.shared, type)) {
+              if (isAtomic &&
+                  !isPossibleAtomicOperation(
+                    align, bytes, module->memories[0]->shared, type)) {
                 continue;
               }
               addLoadFunc(load, module);
@@ -240,6 +245,7 @@ struct SafeHeap : public Pass {
       }
       store.valueType = valueType;
       store.type = Type::none;
+      store.memory = module->memories[0]->name;
       for (Index bytes : {1, 2, 4, 8, 16}) {
         store.bytes = bytes;
         if (bytes > valueType.getByteSize() ||
@@ -255,8 +261,9 @@ struct SafeHeap : public Pass {
           }
           for (auto isAtomic : {true, false}) {
             store.isAtomic = isAtomic;
-            if (isAtomic && !isPossibleAtomicOperation(
-                              align, bytes, module->memory.shared, valueType)) {
+            if (isAtomic &&
+                !isPossibleAtomicOperation(
+                  align, bytes, module->memories[0]->shared, valueType)) {
               continue;
             }
             addStoreFunc(store, module);
@@ -273,22 +280,30 @@ struct SafeHeap : public Pass {
       return;
     }
     // pointer, offset
-    auto indexType = module->memory.indexType;
+    auto memory = module->getMemory(style.memory);
+    auto indexType = memory->indexType;
     auto funcSig = Signature({indexType, indexType}, style.type);
     auto func = Builder::makeFunction(name, funcSig, {indexType});
     Builder builder(*module);
     auto* block = builder.makeBlock();
     block->list.push_back(builder.makeLocalSet(
       2,
-      builder.makeBinary(module->memory.is64() ? AddInt64 : AddInt32,
+      builder.makeBinary(memory->is64() ? AddInt64 : AddInt32,
                          builder.makeLocalGet(0, indexType),
                          builder.makeLocalGet(1, indexType))));
     // check for reading past valid memory: if pointer + offset + bytes
-    block->list.push_back(
-      makeBoundsCheck(style.type, builder, 2, style.bytes, module));
+    block->list.push_back(makeBoundsCheck(style.type,
+                                          builder,
+                                          2,
+                                          style.bytes,
+                                          module,
+                                          memory->indexType,
+                                          memory->is64(),
+                                          memory->name));
     // check proper alignment
     if (style.align > 1) {
-      block->list.push_back(makeAlignCheck(style.align, builder, 2, module));
+      block->list.push_back(
+        makeAlignCheck(style.align, builder, 2, module, memory->name));
     }
     // do the load
     auto* load = module->allocator.alloc<Load>();
@@ -312,7 +327,9 @@ struct SafeHeap : public Pass {
     if (module->getFunctionOrNull(name)) {
       return;
     }
-    auto indexType = module->memory.indexType;
+    auto memory = module->getMemory(style.memory);
+    auto indexType = memory->indexType;
+    bool is64 = memory->is64();
     // pointer, offset, value
     auto funcSig =
       Signature({indexType, indexType, style.valueType}, Type::none);
@@ -321,19 +338,27 @@ struct SafeHeap : public Pass {
     auto* block = builder.makeBlock();
     block->list.push_back(builder.makeLocalSet(
       3,
-      builder.makeBinary(module->memory.is64() ? AddInt64 : AddInt32,
+      builder.makeBinary(is64 ? AddInt64 : AddInt32,
                          builder.makeLocalGet(0, indexType),
                          builder.makeLocalGet(1, indexType))));
     // check for reading past valid memory: if pointer + offset + bytes
-    block->list.push_back(
-      makeBoundsCheck(style.valueType, builder, 3, style.bytes, module));
+    block->list.push_back(makeBoundsCheck(style.valueType,
+                                          builder,
+                                          3,
+                                          style.bytes,
+                                          module,
+                                          indexType,
+                                          is64,
+                                          memory->name));
     // check proper alignment
     if (style.align > 1) {
-      block->list.push_back(makeAlignCheck(style.align, builder, 3, module));
+      block->list.push_back(
+        makeAlignCheck(style.align, builder, 3, module, memory->name));
     }
     // do the store
     auto* store = module->allocator.alloc<Store>();
     *store = style; // basically the same as the template we are given!
+    store->memory = memory->name;
     store->ptr = builder.makeLocalGet(3, indexType);
     store->value = builder.makeLocalGet(2, style.valueType);
     block->list.push_back(store);
@@ -342,11 +367,15 @@ struct SafeHeap : public Pass {
     module->addFunction(std::move(func));
   }
 
-  Expression*
-  makeAlignCheck(Address align, Builder& builder, Index local, Module* module) {
-    auto indexType = module->memory.indexType;
+  Expression* makeAlignCheck(Address align,
+                             Builder& builder,
+                             Index local,
+                             Module* module,
+                             Name memoryName) {
+    auto memory = module->getMemory(memoryName);
+    auto indexType = memory->indexType;
     Expression* ptrBits = builder.makeLocalGet(local, indexType);
-    if (module->memory.is64()) {
+    if (memory->is64()) {
       ptrBits = builder.makeUnary(WrapInt64, ptrBits);
     }
     return builder.makeIf(
@@ -355,17 +384,22 @@ struct SafeHeap : public Pass {
       builder.makeCall(alignfault, {}, Type::none));
   }
 
-  Expression* makeBoundsCheck(
-    Type type, Builder& builder, Index local, Index bytes, Module* module) {
-    auto indexType = module->memory.indexType;
-    auto upperOp = module->memory.is64()
-                     ? options.lowMemoryUnused ? LtUInt64 : EqInt64
-                     : options.lowMemoryUnused ? LtUInt32 : EqInt32;
-    auto upperBound = options.lowMemoryUnused ? PassOptions::LowMemoryBound : 0;
+  Expression* makeBoundsCheck(Type type,
+                              Builder& builder,
+                              Index local,
+                              Index bytes,
+                              Module* module,
+                              Type indexType,
+                              bool is64,
+                              Name memory) {
+    bool lowMemUnused = getPassOptions().lowMemoryUnused;
+    auto upperOp = is64 ? lowMemUnused ? LtUInt64 : EqInt64
+                        : lowMemUnused ? LtUInt32 : EqInt32;
+    auto upperBound = lowMemUnused ? PassOptions::LowMemoryBound : 0;
     Expression* brkLocation;
     if (sbrk.is()) {
       brkLocation =
-        builder.makeCall(sbrk, {builder.makeConstPtr(0)}, indexType);
+        builder.makeCall(sbrk, {builder.makeConstPtr(0, indexType)}, indexType);
     } else {
       Expression* sbrkPtr;
       if (dynamicTopPtr.is()) {
@@ -373,22 +407,23 @@ struct SafeHeap : public Pass {
       } else {
         sbrkPtr = builder.makeCall(getSbrkPtr, {}, indexType);
       }
-      auto size = module->memory.is64() ? 8 : 4;
-      brkLocation = builder.makeLoad(size, false, 0, size, sbrkPtr, indexType);
+      auto size = is64 ? 8 : 4;
+      brkLocation =
+        builder.makeLoad(size, false, 0, size, sbrkPtr, indexType, memory);
     }
-    auto gtuOp = module->memory.is64() ? GtUInt64 : GtUInt32;
-    auto addOp = module->memory.is64() ? AddInt64 : AddInt32;
+    auto gtuOp = is64 ? GtUInt64 : GtUInt32;
+    auto addOp = is64 ? AddInt64 : AddInt32;
     return builder.makeIf(
       builder.makeBinary(
         OrInt32,
         builder.makeBinary(upperOp,
                            builder.makeLocalGet(local, indexType),
-                           builder.makeConstPtr(upperBound)),
+                           builder.makeConstPtr(upperBound, indexType)),
         builder.makeBinary(
           gtuOp,
           builder.makeBinary(addOp,
                              builder.makeLocalGet(local, indexType),
-                             builder.makeConstPtr(bytes)),
+                             builder.makeConstPtr(bytes, indexType)),
           brkLocation)),
       builder.makeCall(segfault, {}, Type::none));
   }
diff --git a/src/passes/SetGlobals.cpp b/src/passes/SetGlobals.cpp
index 8a14766..3bcbb3d 100644
--- a/src/passes/SetGlobals.cpp
+++ b/src/passes/SetGlobals.cpp
@@ -25,13 +25,16 @@
 namespace wasm {
 
 struct SetGlobals : public Pass {
-  void run(PassRunner* runner, Module* module) override {
-    Name input = runner->options.getArgument(
+  // Only modifies globals.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  void run(Module* module) override {
+    Name input = getPassRunner()->options.getArgument(
       "set-globals",
       "SetGlobals usage:  wasm-opt --pass-arg=set-globals@x=y,z=w");
 
     // The input is a set of X=Y pairs separated by commas.
-    String::Split pairs(input.str, ",");
+    String::Split pairs(input.toString(), ",");
     for (auto& pair : pairs) {
       String::Split nameAndValue(pair, "=");
       auto name = nameAndValue[0];
diff --git a/src/passes/SignExtLowering.cpp b/src/passes/SignExtLowering.cpp
new file mode 100644
index 0000000..7d03046
--- /dev/null
+++ b/src/passes/SignExtLowering.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Lowers sign-ext operations into wasm MVP operations.
+//
+
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct SignExtLowering : public WalkerPass<PostWalker<SignExtLowering>> {
+  template<typename T>
+  void lowerToShifts(Expression* value,
+                     BinaryOp leftShift,
+                     BinaryOp rightShift,
+                     T originalBits) {
+    // To sign-extend, we shift all the way left so the effective sign bit is
+    // where the actual sign bit is. For example, when sign-extending i8, the
+    // effective sign bit is at bit 8, and we shift it to the actual place of
+    // the sign bit, which is 32 for i32 or 64 for i64. Then we do a signed
+    // shift in the other direction, which fills with the proper bit all the
+    // way back, so e.g.
+    //
+    //  0x000000ff  =(shift left)=>  0xff000000  =(shift right)=>  0xffffffff
+    //
+    T shiftBits = (sizeof(T) * 8) - originalBits;
+    Builder builder(*getModule());
+    replaceCurrent(builder.makeBinary(
+      rightShift,
+      builder.makeBinary(leftShift, value, builder.makeConst(shiftBits)),
+      builder.makeConst(shiftBits)));
+  }
+
+  void visitUnary(Unary* curr) {
+    switch (curr->op) {
+      case ExtendS8Int32:
+        lowerToShifts(curr->value, ShlInt32, ShrSInt32, int32_t(8));
+        break;
+      case ExtendS16Int32:
+        lowerToShifts(curr->value, ShlInt32, ShrSInt32, int32_t(16));
+        break;
+      case ExtendS8Int64:
+        lowerToShifts(curr->value, ShlInt64, ShrSInt64, int64_t(8));
+        break;
+      case ExtendS16Int64:
+        lowerToShifts(curr->value, ShlInt64, ShrSInt64, int64_t(16));
+        break;
+      case ExtendS32Int64:
+        lowerToShifts(curr->value, ShlInt64, ShrSInt64, int64_t(32));
+        break;
+      default: {
+      }
+    }
+  }
+};
+
+Pass* createSignExtLoweringPass() { return new SignExtLowering(); }
+
+} // namespace wasm
diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp
index 4f142eb..e6a7aa4 100644
--- a/src/passes/SignaturePruning.cpp
+++ b/src/passes/SignaturePruning.cpp
@@ -28,6 +28,7 @@
 //
 
 #include "ir/find_all.h"
+#include "ir/intrinsics.h"
 #include "ir/lubs.h"
 #include "ir/module-utils.h"
 #include "ir/type-updating.h"
@@ -47,9 +48,13 @@ struct SignaturePruning : public Pass {
   // type has no improvement that we can find, it will not appear in this map.
   std::unordered_map<HeapType, Signature> newSignatures;
 
-  void run(PassRunner* runner, Module* module) override {
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      Fatal() << "SignaturePruning requires nominal typing";
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "SignaturePruning requires nominal/isorecursive typing";
     }
 
     if (!module->tables.empty()) {
@@ -100,6 +105,18 @@ struct SignaturePruning : public Pass {
       // called.
       for (auto* call : info.calls) {
         allInfo[module->getFunction(call->target)->type].calls.push_back(call);
+
+        // Intrinsics limit our ability to optimize in some cases. We will avoid
+        // modifying any type that is used by call.without.effects, to avoid
+        // the complexity of handling that. After intrinsics are lowered,
+        // this optimization will be able to run at full power anyhow.
+        if (Intrinsics(*module).isCallWithoutEffects(call)) {
+          // The last operand is the actual call target.
+          auto* target = call->operands.back();
+          if (target->type != Type::unreachable) {
+            allInfo[target->type.getHeapType()].optimizable = false;
+          }
+        }
       }
 
       // For indirect calls, add each call_ref to the type the call_ref uses.
@@ -177,8 +194,12 @@ struct SignaturePruning : public Pass {
       }
 
       auto oldParams = sig.params;
-      auto removedIndexes = ParamUtils::removeParameters(
-        funcs, unusedParams, info.calls, info.callRefs, module, runner);
+      auto removedIndexes = ParamUtils::removeParameters(funcs,
+                                                         unusedParams,
+                                                         info.calls,
+                                                         info.callRefs,
+                                                         module,
+                                                         getPassRunner());
       if (removedIndexes.empty()) {
         continue;
       }
diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp
index 2c2146c..488e2c6 100644
--- a/src/passes/SignatureRefining.cpp
+++ b/src/passes/SignatureRefining.cpp
@@ -41,15 +41,22 @@ namespace wasm {
 namespace {
 
 struct SignatureRefining : public Pass {
+  // Only changes heap types and parameter types (but not locals).
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   // Maps each heap type to the possible refinement of the types in their
   // signatures. We will fill this during analysis and then use it while doing
   // an update of the types. If a type has no improvement that we can find, it
   // will not appear in this map.
   std::unordered_map<HeapType, Signature> newSignatures;
 
-  void run(PassRunner* runner, Module* module) override {
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      Fatal() << "SignatureRefining requires nominal typing";
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "SignatureRefining requires nominal/hybrid typing";
     }
 
     if (!module->tables.empty()) {
@@ -81,6 +88,10 @@ struct SignatureRefining : public Pass {
     ModuleUtils::ParallelFunctionAnalysis<Info, Mutable> analysis(
       *module, [&](Function* func, Info& info) {
         if (func->imported()) {
+          // Avoid changing the types of imported functions. Spec and VM support
+          // for that is not yet stable.
+          // TODO: optimize this when possible in the future
+          info.canModify = false;
           return;
         }
         info.calls = std::move(FindAll<Call>(func->body).list);
@@ -111,6 +122,11 @@ struct SignatureRefining : public Pass {
       // Add the function's return LUB to the one for the heap type of that
       // function.
       allInfo[func->type].resultsLUB.combine(info.resultsLUB);
+
+      // If one function cannot be modified, that entire type cannot be.
+      if (!info.canModify) {
+        allInfo[func->type].canModify = false;
+      }
     }
 
     // We cannot alter the signature of an exported function, as the outside may
@@ -228,13 +244,19 @@ struct SignatureRefining : public Pass {
     struct CodeUpdater : public WalkerPass<PostWalker<CodeUpdater>> {
       bool isFunctionParallel() override { return true; }
 
+      // Updating parameter types cannot affect validation (only updating var
+      // types types might).
+      bool requiresNonNullableLocalFixups() override { return false; }
+
       SignatureRefining& parent;
       Module& wasm;
 
       CodeUpdater(SignatureRefining& parent, Module& wasm)
         : parent(parent), wasm(wasm) {}
 
-      CodeUpdater* create() override { return new CodeUpdater(parent, wasm); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<CodeUpdater>(parent, wasm);
+      }
 
       void doWalkFunction(Function* func) {
         auto iter = parent.newSignatures.find(func->type);
@@ -243,11 +265,19 @@ struct SignatureRefining : public Pass {
           for (auto param : iter->second.params) {
             newParamsTypes.push_back(param);
           }
-          TypeUpdating::updateParamTypes(func, newParamsTypes, wasm);
+          // Do not update local.get/local.tee here, as we will do so in
+          // GlobalTypeRewriter::updateSignatures, below. (Doing an update here
+          // would leave the IR in an inconsistent state of a partial update;
+          // instead, do the full update at the end.)
+          TypeUpdating::updateParamTypes(
+            func,
+            newParamsTypes,
+            wasm,
+            TypeUpdating::LocalUpdatingMode::DoNotUpdate);
         }
       }
     };
-    CodeUpdater(*this, *module).run(runner, module);
+    CodeUpdater(*this, *module).run(getPassRunner(), module);
 
     // Rewrite the types.
     GlobalTypeRewriter::updateSignatures(newSignatures, *module);
@@ -255,7 +285,7 @@ struct SignatureRefining : public Pass {
     if (refinedResults) {
       // After return types change we need to propagate.
       // TODO: we could do this only in relevant functions perhaps
-      ReFinalize().run(runner, module);
+      ReFinalize().run(getPassRunner(), module);
     }
   }
 };
diff --git a/src/passes/SimplifyGlobals.cpp b/src/passes/SimplifyGlobals.cpp
index daaa824..463261d 100644
--- a/src/passes/SimplifyGlobals.cpp
+++ b/src/passes/SimplifyGlobals.cpp
@@ -97,7 +97,9 @@ struct GlobalUseScanner : public WalkerPass<PostWalker<GlobalUseScanner>> {
 
   GlobalUseScanner(GlobalInfoMap* infos) : infos(infos) {}
 
-  GlobalUseScanner* create() override { return new GlobalUseScanner(infos); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<GlobalUseScanner>(infos);
+  }
 
   void visitGlobalSet(GlobalSet* curr) {
     (*infos)[curr->name].written++;
@@ -307,8 +309,8 @@ struct GlobalUseModifier : public WalkerPass<PostWalker<GlobalUseModifier>> {
   GlobalUseModifier(NameNameMap* copiedParentMap)
     : copiedParentMap(copiedParentMap) {}
 
-  GlobalUseModifier* create() override {
-    return new GlobalUseModifier(copiedParentMap);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<GlobalUseModifier>(copiedParentMap);
   }
 
   void visitGlobalGet(GlobalGet* curr) {
@@ -331,8 +333,8 @@ struct ConstantGlobalApplier
   ConstantGlobalApplier(NameSet* constantGlobals, bool optimize)
     : constantGlobals(constantGlobals), optimize(optimize) {}
 
-  ConstantGlobalApplier* create() override {
-    return new ConstantGlobalApplier(constantGlobals, optimize);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<ConstantGlobalApplier>(constantGlobals, optimize);
   }
 
   void visitExpression(Expression* curr) {
@@ -377,8 +379,7 @@ struct ConstantGlobalApplier
 
   void visitFunction(Function* curr) {
     if (replaced && optimize) {
-      PassRunner runner(getModule(), getPassRunner()->options);
-      runner.setIsNested(true);
+      PassRunner runner(getPassRunner());
       runner.addDefaultFunctionOptimizationPasses();
       runner.runOnFunction(curr);
     }
@@ -399,8 +400,8 @@ struct GlobalSetRemover : public WalkerPass<PostWalker<GlobalSetRemover>> {
 
   bool isFunctionParallel() override { return true; }
 
-  GlobalSetRemover* create() override {
-    return new GlobalSetRemover(toRemove, optimize);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<GlobalSetRemover>(toRemove, optimize);
   }
 
   void visitGlobalSet(GlobalSet* curr) {
@@ -412,8 +413,7 @@ struct GlobalSetRemover : public WalkerPass<PostWalker<GlobalSetRemover>> {
 
   void visitFunction(Function* curr) {
     if (removed && optimize) {
-      PassRunner runner(getModule(), getPassRunner()->options);
-      runner.setIsNested(true);
+      PassRunner runner(getPassRunner());
       runner.addDefaultFunctionOptimizationPasses();
       runner.runOnFunction(curr);
     }
@@ -428,7 +428,6 @@ private:
 } // anonymous namespace
 
 struct SimplifyGlobals : public Pass {
-  PassRunner* runner;
   Module* module;
 
   GlobalInfoMap map;
@@ -436,8 +435,7 @@ struct SimplifyGlobals : public Pass {
 
   SimplifyGlobals(bool optimize = false) : optimize(optimize) {}
 
-  void run(PassRunner* runner_, Module* module_) override {
-    runner = runner_;
+  void run(Module* module_) override {
     module = module_;
 
     while (iteration()) {
@@ -475,7 +473,7 @@ struct SimplifyGlobals : public Pass {
         map[ex->value].exported = true;
       }
     }
-    GlobalUseScanner(&map).run(runner, module);
+    GlobalUseScanner(&map).run(getPassRunner(), module);
     // We now know which are immutable in practice.
     for (auto& global : module->globals) {
       auto& info = map[global->name];
@@ -564,7 +562,8 @@ struct SimplifyGlobals : public Pass {
     // then see that since the global has no writes, it is a constant, which
     // will lead to removal of gets, and after removing them, the global itself
     // will be removed as well.
-    GlobalSetRemover(&globalsNotNeedingSets, optimize).run(runner, module);
+    GlobalSetRemover(&globalsNotNeedingSets, optimize)
+      .run(getPassRunner(), module);
 
     return more;
   }
@@ -595,7 +594,7 @@ struct SimplifyGlobals : public Pass {
         }
       }
       // Apply to the gets.
-      GlobalUseModifier(&copiedParentMap).run(runner, module);
+      GlobalUseModifier(&copiedParentMap).run(getPassRunner(), module);
     }
   }
 
@@ -633,7 +632,8 @@ struct SimplifyGlobals : public Pass {
         constantGlobals.insert(global->name);
       }
     }
-    ConstantGlobalApplier(&constantGlobals, optimize).run(runner, module);
+    ConstantGlobalApplier(&constantGlobals, optimize)
+      .run(getPassRunner(), module);
   }
 };
 
diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp
index 35980f5..07527ee 100644
--- a/src/passes/SimplifyLocals.cpp
+++ b/src/passes/SimplifyLocals.cpp
@@ -53,6 +53,7 @@
 #include <ir/linear-execution.h>
 #include <ir/local-utils.h>
 #include <ir/manipulation.h>
+#include <ir/utils.h>
 #include <pass.h>
 #include <wasm-builder.h>
 #include <wasm-traversal.h>
@@ -70,8 +71,9 @@ struct SimplifyLocals
       SimplifyLocals<allowTee, allowStructure, allowNesting>>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override {
-    return new SimplifyLocals<allowTee, allowStructure, allowNesting>();
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<
+      SimplifyLocals<allowTee, allowStructure, allowNesting>>();
   }
 
   // information for a local.set we can sink
@@ -84,7 +86,7 @@ struct SimplifyLocals
   };
 
   // a list of sinkables in a linear execution trace
-  typedef std::map<Index, SinkableInfo> Sinkables;
+  using Sinkables = std::map<Index, SinkableInfo>;
 
   // locals in current linear execution trace, which we try to sink
   Sinkables sinkables;
@@ -120,6 +122,9 @@ struct SimplifyLocals
   // local => # of local.gets for it
   LocalGetCounter getCounter;
 
+  // In rare cases we make a change to a type that requires a refinalize.
+  bool refinalize = false;
+
   static void
   doNoteNonLinear(SimplifyLocals<allowTee, allowStructure, allowNesting>* self,
                   Expression** currp) {
@@ -254,6 +259,23 @@ struct SimplifyLocals
       if (oneUse) {
         // with just one use, we can sink just the value
         this->replaceCurrent(set->value);
+
+        // We are replacing a local.get with the value of the local.set. That
+        // may require a refinalize in certain cases, like this:
+        //
+        //  (struct.get $X 0
+        //    (local.get $x)
+        //  )
+        //
+        // If we replace the local.get with a more refined type then the
+        // struct.get may read a more refined type (if the subtype has a more
+        // refined type for that particular field). Note that this cannot happen
+        // in the other arm of this if-else, where we replace the local.get with
+        // a tee, since tees have the type of the local, so no types change
+        // there.
+        if (set->value->type != curr->type) {
+          refinalize = true;
+        }
       } else {
         this->replaceCurrent(set);
         assert(!set->isTee());
@@ -299,13 +321,16 @@ struct SimplifyLocals
            Expression** currp) {
     Expression* curr = *currp;
 
-    // Expressions that may throw cannot be sinked into 'try'. At the start of
-    // 'try', we drop all sinkables that may throw.
+    // Certain expressions cannot be sinked into 'try', and so at the start of
+    // 'try' we forget about them.
     if (curr->is<Try>()) {
       std::vector<Index> invalidated;
       for (auto& [index, info] : self->sinkables) {
+        // Expressions that may throw cannot be moved into a try (which might
+        // catch them, unlike before the move).
         if (info.effects.throws()) {
           invalidated.push_back(index);
+          continue;
         }
       }
       for (auto index : invalidated) {
@@ -736,7 +761,48 @@ struct SimplifyLocals
     if (sinkables.empty()) {
       return;
     }
+
+    // Check if the type makes sense. A non-nullable local might be dangerous
+    // here, as creating new local.gets for such locals is risky:
+    //
+    //  (func $silly
+    //    (local $x (ref $T))
+    //    (if
+    //      (condition)
+    //      (local.set $x ..)
+    //    )
+    //  )
+    //
+    // That local is silly as the write is never read. If we optimize it and add
+    // a local.get, however, then we'd no longer validate (as no set would
+    // dominate that new get in the if's else arm). Fixups would add a
+    // ref.as_non_null around the local.get, which will then trap at runtime:
+    //
+    //  (func $silly
+    //    (local $x (ref null $T))
+    //    (local.set $x
+    //      (if
+    //        (condition)
+    //        (..)
+    //        (ref.as_non_null
+    //          (local.get $x)
+    //        )
+    //      )
+    //    )
+    //  )
+    //
+    // In other words, local.get is not necessarily free of effects if the local
+    // is non-nullable - it must have been set already. We could check that
+    // here, but running that linear-time check may not be worth it as this
+    // optimization is fairly minor, so just skip the non-nullable case.
+    //
+    // TODO investigate more
     Index goodIndex = sinkables.begin()->first;
+    auto localType = this->getFunction()->getLocalType(goodIndex);
+    if (localType.isNonNullable()) {
+      return;
+    }
+
     // Ensure we have a place to write the return values for, if not, we
     // need another cycle.
     auto* ifTrueBlock = iff->ifTrue->dynCast<Block>();
@@ -745,6 +811,9 @@ struct SimplifyLocals
       ifsToEnlarge.push_back(iff);
       return;
     }
+
+    // We can optimize!
+
     // Update the ifTrue side.
     Builder builder(*this->getModule());
     auto** item = sinkables.at(goodIndex).item;
@@ -754,8 +823,7 @@ struct SimplifyLocals
     ifTrueBlock->finalize();
     assert(ifTrueBlock->type != Type::none);
     // Update the ifFalse side.
-    iff->ifFalse = builder.makeLocalGet(
-      set->index, this->getFunction()->getLocalType(set->index));
+    iff->ifFalse = builder.makeLocalGet(set->index, localType);
     iff->finalize(); // update type
     // Update the get count.
     getCounter.num[set->index]++;
@@ -842,6 +910,10 @@ struct SimplifyLocals
         }
       }
     } while (anotherCycle);
+
+    if (refinalize) {
+      ReFinalize().walkFunctionInModule(func, this->getModule());
+    }
   }
 
   bool runMainOptimizations(Function* func) {
@@ -928,8 +1000,10 @@ struct SimplifyLocals
       std::vector<Index>* numLocalGets;
       bool removeEquivalentSets;
       Module* module;
+      PassOptions passOptions;
 
       bool anotherCycle = false;
+      bool refinalize = false;
 
       // We track locals containing the same value.
       EquivalentSets equivalences;
@@ -943,12 +1017,9 @@ struct SimplifyLocals
 
       void visitLocalSet(LocalSet* curr) {
         // Remove trivial copies, even through a tee
-        auto* value = curr->value;
-        Function* func = this->getFunction();
-        while (auto* subSet = value->dynCast<LocalSet>()) {
-          value = subSet->value;
-        }
-        if (auto* get = value->dynCast<LocalGet>()) {
+        auto* value =
+          Properties::getFallthrough(curr->value, passOptions, *module);
+        if (auto* get = value->template dynCast<LocalGet>()) {
           if (equivalences.check(curr->index, get->index)) {
             // This is an unnecessary copy!
             if (removeEquivalentSets) {
@@ -961,13 +1032,9 @@ struct SimplifyLocals
             }
             // Nothing more to do, ignore the copy.
             return;
-          } else if (func->getLocalType(curr->index) ==
-                     func->getLocalType(get->index)) {
+          } else {
             // There is a new equivalence now. Remove all the old ones, and add
             // the new one.
-            // Note that we ignore the case of subtyping here, to keep this
-            // optimization simple by assuming all equivalent indexes also have
-            // the same type. TODO: consider optimizing this.
             equivalences.reset(curr->index);
             equivalences.add(curr->index, get->index);
             return;
@@ -982,8 +1049,6 @@ struct SimplifyLocals
         // Canonicalize gets: if some are equivalent, then we can pick more
         // then one, and other passes may benefit from having more uniformity.
         if (auto* set = equivalences.getEquivalents(curr->index)) {
-          // Pick the index with the most uses - maximizing the chance to
-          // lower one's uses to zero.
           // Helper method that returns the # of gets *ignoring the current
           // get*, as we want to see what is best overall, treating this one as
           // to be decided upon.
@@ -995,25 +1060,60 @@ struct SimplifyLocals
             }
             return ret;
           };
+
+          // Pick the index with the most uses - maximizing the chance to
+          // lower one's uses to zero. If types differ though then we prefer to
+          // switch to a more refined type even if there are fewer uses, as that
+          // may have significant benefits to later optimizations (we may be
+          // able to use it to remove casts, etc.).
+          auto* func = this->getFunction();
           Index best = -1;
           for (auto index : *set) {
-            if (best == Index(-1) ||
+            if (best == Index(-1)) {
+              // This is the first possible option we've seen.
+              best = index;
+              continue;
+            }
+
+            auto bestType = func->getLocalType(best);
+            auto indexType = func->getLocalType(index);
+            if (!Type::isSubType(indexType, bestType)) {
+              // This is less refined than the current best; ignore.
+              continue;
+            }
+
+            // This is better if it has a more refined type, or if it has more
+            // uses.
+            if (indexType != bestType ||
                 getNumGetsIgnoringCurr(index) > getNumGetsIgnoringCurr(best)) {
               best = index;
             }
           }
           assert(best != Index(-1));
           // Due to ordering, the best index may be different from us but have
-          // the same # of locals - make sure we actually improve.
-          if (best != curr->index && getNumGetsIgnoringCurr(best) >
-                                       getNumGetsIgnoringCurr(curr->index)) {
-            // Update the get counts.
-            (*numLocalGets)[best]++;
-            assert((*numLocalGets)[curr->index] >= 1);
-            (*numLocalGets)[curr->index]--;
-            // Make the change.
-            curr->index = best;
-            anotherCycle = true;
+          // the same # of locals - make sure we actually improve, either adding
+          // more gets, or a more refined type (and never change to a less
+          // refined type).
+          auto bestType = func->getLocalType(best);
+          auto oldType = func->getLocalType(curr->index);
+          if (best != curr->index && Type::isSubType(bestType, oldType)) {
+            auto hasMoreGets = getNumGetsIgnoringCurr(best) >
+                               getNumGetsIgnoringCurr(curr->index);
+            if (hasMoreGets || bestType != oldType) {
+              // Update the get counts.
+              (*numLocalGets)[best]++;
+              assert((*numLocalGets)[curr->index] >= 1);
+              (*numLocalGets)[curr->index]--;
+              // Make the change.
+              curr->index = best;
+              anotherCycle = true;
+              if (bestType != oldType) {
+                curr->type = func->getLocalType(best);
+                // We are switching to a more refined type, which might require
+                // changes in the user of the local.get.
+                refinalize = true;
+              }
+            }
           }
         }
       }
@@ -1021,9 +1121,13 @@ struct SimplifyLocals
 
     EquivalentOptimizer eqOpter;
     eqOpter.module = this->getModule();
+    eqOpter.passOptions = this->getPassOptions();
     eqOpter.numLocalGets = &getCounter.num;
     eqOpter.removeEquivalentSets = allowStructure;
     eqOpter.walkFunction(func);
+    if (eqOpter.refinalize) {
+      ReFinalize().walkFunctionInModule(func, this->getModule());
+    }
 
     // We may have already had a local with no uses, or we may have just
     // gotten there thanks to the EquivalentOptimizer. If there are such
diff --git a/src/passes/SpillPointers.cpp b/src/passes/SpillPointers.cpp
new file mode 100644
index 0000000..f496b8c
--- /dev/null
+++ b/src/passes/SpillPointers.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2017 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Spills values that might be pointers to the C stack. This allows
+// Boehm-style GC to see them properly.
+//
+// To reduce the overhead of the extra operations added here, you
+// should probably run optimizations after doing it.
+// TODO: add a dead store elimination pass, which would help here
+//
+//  * There is currently no check that there is enough stack space.
+//
+
+#include "abi/stack.h"
+#include "cfg/liveness-traversal.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct SpillPointers
+  : public WalkerPass<LivenessWalker<SpillPointers, Visitor<SpillPointers>>> {
+  bool isFunctionParallel() override { return true; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<SpillPointers>();
+  }
+
+  // a mapping of the pointers to all the spillable things. We need to know
+  // how to replace them, and as we spill we may modify them. This map
+  // gives us, for an Expression** seen during the walk (and placed in the
+  // basic block, which is what we iterate on for efficiency) => the
+  // current actual pointer, which may have moded
+  std::unordered_map<Expression**, Expression**> actualPointers;
+
+  // note calls in basic blocks
+  template<typename T> void visitSpillable(T* curr) {
+    // if in unreachable code, ignore
+    if (!currBasicBlock) {
+      return;
+    }
+    auto* pointer = getCurrentPointer();
+    currBasicBlock->contents.actions.emplace_back(pointer);
+    // starts out as correct, may change later
+    actualPointers[pointer] = pointer;
+  }
+
+  void visitCall(Call* curr) { visitSpillable(curr); }
+  void visitCallIndirect(CallIndirect* curr) { visitSpillable(curr); }
+
+  // main entry point
+
+  void doWalkFunction(Function* func) {
+    super::doWalkFunction(func);
+    spillPointers();
+  }
+
+  // map pointers to their offset in the spill area
+  using PointerMap = std::unordered_map<Index, Index>;
+
+  Type pointerType;
+
+  void spillPointers() {
+    pointerType = getModule()->memories[0]->indexType;
+
+    // we only care about possible pointers
+    auto* func = getFunction();
+    PointerMap pointerMap;
+    for (Index i = 0; i < func->getNumLocals(); i++) {
+      if (func->getLocalType(i) == pointerType) {
+        auto offset = pointerMap.size() * pointerType.getByteSize();
+        pointerMap[i] = offset;
+      }
+    }
+    // find calls and spill around them
+    bool spilled = false;
+    Index spillLocal = -1;
+    for (auto& curr : basicBlocks) {
+      if (liveBlocks.count(curr.get()) == 0) {
+        continue; // ignore dead blocks
+      }
+      auto& liveness = curr->contents;
+      auto& actions = liveness.actions;
+      Index lastCall = -1;
+      for (Index i = 0; i < actions.size(); i++) {
+        auto& action = liveness.actions[i];
+        if (action.isOther()) {
+          lastCall = i;
+        }
+      }
+      if (lastCall == Index(-1)) {
+        continue; // nothing to see here
+      }
+      // scan through the block, spilling around the calls
+      // TODO: we can filter on pointerMap everywhere
+      SetOfLocals live = liveness.end;
+      for (int i = int(actions.size()) - 1; i >= 0; i--) {
+        auto& action = actions[i];
+        if (action.isGet()) {
+          live.insert(action.index);
+        } else if (action.isSet()) {
+          live.erase(action.index);
+        } else if (action.isOther()) {
+          std::vector<Index> toSpill;
+          for (auto index : live) {
+            if (pointerMap.count(index) > 0) {
+              toSpill.push_back(index);
+            }
+          }
+          if (!toSpill.empty()) {
+            // we now have a call + the information about which locals
+            // should be spilled
+            if (!spilled) {
+              // prepare stack support: get a pointer to stack space big enough
+              // for all our data
+              spillLocal = Builder::addVar(func, pointerType);
+              spilled = true;
+            }
+            // the origin was seen at walk, but the thing may have moved
+            auto* pointer = actualPointers[action.origin];
+            spillPointersAroundCall(
+              pointer, toSpill, spillLocal, pointerMap, func, getModule());
+          }
+        } else {
+          WASM_UNREACHABLE("unexpected action");
+        }
+      }
+    }
+    if (spilled) {
+      // get the stack space, and set the local to it
+      ABI::getStackSpace(spillLocal,
+                         func,
+                         pointerType.getByteSize() * pointerMap.size(),
+                         *getModule());
+    }
+  }
+
+  void spillPointersAroundCall(Expression** origin,
+                               std::vector<Index>& toSpill,
+                               Index spillLocal,
+                               PointerMap& pointerMap,
+                               Function* func,
+                               Module* module) {
+    auto* call = *origin;
+    if (call->type == Type::unreachable) {
+      return; // the call is never reached anyhow, ignore
+    }
+    Builder builder(*module);
+    auto* block = builder.makeBlock();
+    // move the operands into locals, as we must spill after they are executed
+    auto handleOperand = [&](Expression*& operand) {
+      auto temp = builder.addVar(func, operand->type);
+      auto* set = builder.makeLocalSet(temp, operand);
+      block->list.push_back(set);
+      block->finalize();
+      if (actualPointers.count(&operand) > 0) {
+        // this is something we track, and it's moving - update
+        actualPointers[&operand] = &set->value;
+      }
+      operand = builder.makeLocalGet(temp, operand->type);
+    };
+    if (call->is<Call>()) {
+      for (auto*& operand : call->cast<Call>()->operands) {
+        handleOperand(operand);
+      }
+    } else if (call->is<CallIndirect>()) {
+      for (auto*& operand : call->cast<CallIndirect>()->operands) {
+        handleOperand(operand);
+      }
+      handleOperand(call->cast<CallIndirect>()->target);
+    } else {
+      WASM_UNREACHABLE("unexpected expr");
+    }
+    // add the spills
+    for (auto index : toSpill) {
+      block->list.push_back(
+        builder.makeStore(pointerType.getByteSize(),
+                          pointerMap[index],
+                          pointerType.getByteSize(),
+                          builder.makeLocalGet(spillLocal, pointerType),
+                          builder.makeLocalGet(index, pointerType),
+                          pointerType,
+                          getModule()->memories[0]->name));
+    }
+    // add the (modified) call
+    block->list.push_back(call);
+    block->finalize();
+    *origin = block;
+  }
+};
+
+Pass* createSpillPointersPass() { return new SpillPointers(); }
+
+} // namespace wasm
diff --git a/src/passes/StackCheck.cpp b/src/passes/StackCheck.cpp
index 4b10545..9fba683 100644
--- a/src/passes/StackCheck.cpp
+++ b/src/passes/StackCheck.cpp
@@ -67,8 +67,11 @@ struct EnforceStackLimits : public WalkerPass<PostWalker<EnforceStackLimits>> {
 
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override {
-    return new EnforceStackLimits(
+  // Only affects linear memory operations.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<EnforceStackLimits>(
       stackPointer, stackBase, stackLimit, builder, handler);
   }
 
@@ -123,7 +126,7 @@ private:
 };
 
 struct StackCheck : public Pass {
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     Global* stackPointer = getStackPointerGlobal(*module);
     if (!stackPointer) {
       BYN_DEBUG(std::cerr << "no stack pointer found\n");
@@ -136,7 +139,7 @@ struct StackCheck : public Pass {
 
     Name handler;
     auto handlerName =
-      runner->options.getArgumentOrDefault("stack-check-handler", "");
+      getPassOptions().getArgumentOrDefault("stack-check-handler", "");
     if (handlerName != "") {
       handler = handlerName;
       importStackOverflowHandler(
@@ -146,21 +149,22 @@ struct StackCheck : public Pass {
     Builder builder(*module);
 
     // Add the globals.
+    Type indexType =
+      module->memories.empty() ? Type::i32 : module->memories[0]->indexType;
     auto stackBase =
       module->addGlobal(builder.makeGlobal(stackBaseName,
                                            stackPointer->type,
-                                           builder.makeConstPtr(0),
+                                           builder.makeConstPtr(0, indexType),
                                            Builder::Mutable));
     auto stackLimit =
       module->addGlobal(builder.makeGlobal(stackLimitName,
                                            stackPointer->type,
-                                           builder.makeConstPtr(0),
+                                           builder.makeConstPtr(0, indexType),
                                            Builder::Mutable));
 
     // Instrument all the code.
-    PassRunner innerRunner(module);
     EnforceStackLimits(stackPointer, stackBase, stackLimit, builder, handler)
-      .run(&innerRunner, module);
+      .run(getPassRunner(), module);
 
     // Generate the exported function.
     auto limitsFunc = builder.makeFunction(
diff --git a/src/passes/StackIR.cpp b/src/passes/StackIR.cpp
index a3d9442..2614a36 100644
--- a/src/passes/StackIR.cpp
+++ b/src/passes/StackIR.cpp
@@ -31,7 +31,9 @@ namespace wasm {
 struct GenerateStackIR : public WalkerPass<PostWalker<GenerateStackIR>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new GenerateStackIR; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<GenerateStackIR>();
+  }
 
   bool modifiesBinaryenIR() override { return false; }
 
@@ -69,39 +71,7 @@ public:
     if (passOptions.optimizeLevel >= 3 || passOptions.shrinkLevel >= 1) {
       local2Stack();
     }
-    // Removing unneeded blocks is dangerous with GC, as if we do this:
-    //
-    //   (call
-    //     (rtt)
-    //     (block
-    //       (nop)
-    //       (i32)
-    //     )
-    //   )
-    // === remove inner block ==>
-    //   (call
-    //     (rtt)
-    //     (nop)
-    //     (i32)
-    //   )
-    //
-    // Then we end up with a nop that forces us to emit this during load:
-    //
-    //   (call
-    //     (block
-    //       (local.set
-    //         (rtt)
-    //       )
-    //       (nop)
-    //       (local.get)
-    //     )
-    //     (i32)
-    //   )
-    //
-    // However, that is not valid as an rtt cannot be set to a local.
-    if (!features.hasGC()) {
-      removeUnneededBlocks();
-    }
+    removeUnneededBlocks();
     dce();
   }
 
@@ -368,7 +338,9 @@ private:
 struct OptimizeStackIR : public WalkerPass<PostWalker<OptimizeStackIR>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new OptimizeStackIR; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<OptimizeStackIR>();
+  }
 
   bool modifiesBinaryenIR() override { return false; }
 
diff --git a/src/passes/Strip.cpp b/src/passes/Strip.cpp
index 6377d23..fe34762 100644
--- a/src/passes/Strip.cpp
+++ b/src/passes/Strip.cpp
@@ -28,13 +28,15 @@
 namespace wasm {
 
 struct Strip : public Pass {
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   // A function that returns true if the method should be removed.
-  typedef std::function<bool(UserSection&)> Decider;
+  using Decider = std::function<bool(UserSection&)>;
   Decider decider;
 
   Strip(Decider decider) : decider(decider) {}
 
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     // Remove name and debug sections.
     auto& sections = module->userSections;
     sections.erase(std::remove_if(sections.begin(), sections.end(), decider),
diff --git a/src/passes/StripTargetFeatures.cpp b/src/passes/StripTargetFeatures.cpp
index cb5e51f..cb5b8c8 100644
--- a/src/passes/StripTargetFeatures.cpp
+++ b/src/passes/StripTargetFeatures.cpp
@@ -19,9 +19,11 @@
 namespace wasm {
 
 struct StripTargetFeatures : public Pass {
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   bool isStripped = false;
   StripTargetFeatures(bool isStripped) : isStripped(isStripped) {}
-  void run(PassRunner* runner, Module* module) override {
+  void run(Module* module) override {
     module->hasFeaturesSection = !isStripped;
   }
 };
diff --git a/src/passes/TrapMode.cpp b/src/passes/TrapMode.cpp
index ee245af..8ea6fbf 100644
--- a/src/passes/TrapMode.cpp
+++ b/src/passes/TrapMode.cpp
@@ -306,7 +306,9 @@ public:
 
   TrapModePass(TrapMode mode) : mode(mode) { assert(mode != TrapMode::Allow); }
 
-  Pass* create() override { return new TrapModePass(mode); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<TrapModePass>(mode);
+  }
 
   void visitUnary(Unary* curr) {
     replaceCurrent(makeTrappingUnary(curr, *trappingFunctions));
diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp
index a2a37f7..e9aa07c 100644
--- a/src/passes/TypeRefining.cpp
+++ b/src/passes/TypeRefining.cpp
@@ -38,8 +38,9 @@ using FieldInfo = LUBFinder;
 
 struct FieldInfoScanner
   : public StructUtils::StructScanner<FieldInfo, FieldInfoScanner> {
-  Pass* create() override {
-    return new FieldInfoScanner(functionNewInfos, functionSetGetInfos);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FieldInfoScanner>(functionNewInfos,
+                                              functionSetGetInfos);
   }
 
   FieldInfoScanner(
@@ -72,22 +73,47 @@ struct FieldInfoScanner
   void noteRead(HeapType type, Index index, FieldInfo& info) {
     // Nothing to do for a read, we just care about written values.
   }
+
+  Properties::FallthroughBehavior getFallthroughBehavior() {
+    // Looking at fallthrough values may be dangerous here, because it ignores
+    // intermediate steps. Consider this:
+    //
+    // (struct.set $T 0
+    //   (local.tee
+    //     (struct.get $T 0)))
+    //
+    // This is a copy of a field to itself - normally something we can ignore
+    // (see above). But in this case, if we refine the field then that will
+    // update the struct.get, but the local.tee will still have the old type,
+    // which may not be refined enough. We could in theory always fix this up
+    // using casts later, but those casts may be expensive (especially ref.casts
+    // as opposed to ref.as_non_null), so for now just ignore tee fallthroughs.
+    // TODO: investigate more
+    return Properties::FallthroughBehavior::NoTeeBrIf;
+  }
 };
 
 struct TypeRefining : public Pass {
+  // Only affects GC type declarations and struct.gets.
+  bool requiresNonNullableLocalFixups() override { return false; }
+
   StructUtils::StructValuesMap<FieldInfo> finalInfos;
 
-  void run(PassRunner* runner, Module* module) override {
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      Fatal() << "TypeRefining requires nominal typing";
+  void run(Module* module) override {
+    if (!module->features.hasGC()) {
+      return;
+    }
+    if (getTypeSystem() != TypeSystem::Nominal &&
+        getTypeSystem() != TypeSystem::Isorecursive) {
+      Fatal() << "TypeRefining requires nominal/hybrid typing";
     }
 
     // Find and analyze struct operations inside each function.
     StructUtils::FunctionStructValuesMap<FieldInfo> functionNewInfos(*module),
       functionSetGetInfos(*module);
     FieldInfoScanner scanner(functionNewInfos, functionSetGetInfos);
-    scanner.run(runner, module);
-    scanner.runOnModuleCode(runner, module);
+    scanner.run(getPassRunner(), module);
+    scanner.runOnModuleCode(getPassRunner(), module);
 
     // Combine the data from the functions.
     StructUtils::StructValuesMap<FieldInfo> combinedNewInfos;
@@ -191,14 +217,14 @@ struct TypeRefining : public Pass {
         }
       }
 
-      for (auto subType : subTypes.getSubTypes(type)) {
+      for (auto subType : subTypes.getStrictSubTypes(type)) {
         work.push(subType);
       }
     }
 
     if (canOptimize) {
-      updateInstructions(*module, runner);
-      updateTypes(*module, runner);
+      updateInstructions(*module);
+      updateTypes(*module);
     }
   }
 
@@ -209,18 +235,23 @@ struct TypeRefining : public Pass {
   // at all, and we can end up with a situation where we alter the type to
   // something that is invalid for that read. To ensure the code still
   // validates, simply remove such reads.
-  void updateInstructions(Module& wasm, PassRunner* runner) {
+  void updateInstructions(Module& wasm) {
     struct ReadUpdater : public WalkerPass<PostWalker<ReadUpdater>> {
       bool isFunctionParallel() override { return true; }
 
+      // Only affects struct.gets.
+      bool requiresNonNullableLocalFixups() override { return false; }
+
       TypeRefining& parent;
 
       ReadUpdater(TypeRefining& parent) : parent(parent) {}
 
-      ReadUpdater* create() override { return new ReadUpdater(parent); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<ReadUpdater>(parent);
+      }
 
       void visitStructGet(StructGet* curr) {
-        if (curr->ref->type == Type::unreachable) {
+        if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) {
           return;
         }
 
@@ -246,11 +277,11 @@ struct TypeRefining : public Pass {
     };
 
     ReadUpdater updater(*this);
-    updater.run(runner, &wasm);
-    updater.runOnModuleCode(runner, &wasm);
+    updater.run(getPassRunner(), &wasm);
+    updater.runOnModuleCode(getPassRunner(), &wasm);
   }
 
-  void updateTypes(Module& wasm, PassRunner* runner) {
+  void updateTypes(Module& wasm) {
     class TypeRewriter : public GlobalTypeRewriter {
       TypeRefining& parent;
 
@@ -275,7 +306,7 @@ struct TypeRefining : public Pass {
 
     TypeRewriter(wasm, *this).update();
 
-    ReFinalize().run(runner, &wasm);
+    ReFinalize().run(getPassRunner(), &wasm);
   }
 };
 
diff --git a/src/passes/Untee.cpp b/src/passes/Untee.cpp
index 848e88a..0123ed4 100644
--- a/src/passes/Untee.cpp
+++ b/src/passes/Untee.cpp
@@ -31,7 +31,7 @@ namespace wasm {
 struct Untee : public WalkerPass<PostWalker<Untee>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new Untee; }
+  std::unique_ptr<Pass> create() override { return std::make_unique<Untee>(); }
 
   void visitLocalSet(LocalSet* curr) {
     if (curr->isTee()) {
diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp
index 6adab04..44abed2 100644
--- a/src/passes/Vacuum.cpp
+++ b/src/passes/Vacuum.cpp
@@ -33,7 +33,7 @@ namespace wasm {
 struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new Vacuum; }
+  std::unique_ptr<Pass> create() override { return std::make_unique<Vacuum>(); }
 
   TypeUpdater typeUpdater;
 
@@ -365,6 +365,19 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
       for (auto* catchBody : curr->catchBodies) {
         typeUpdater.noteRecursiveRemoval(catchBody);
       }
+      return;
+    }
+
+    // The try's body does throw. However, throwing may be the only thing it
+    // does, and if the try has a catch-all, then the entire try including
+    // children may have no effects. Note that this situation can only happen
+    // if we do have a catch-all, so avoid wasted work by checking that first.
+    // Also, we can't do this if a result is returned, so check the type.
+    if (curr->type == Type::none && curr->hasCatchAll() &&
+        !EffectAnalyzer(getPassOptions(), *getModule(), curr)
+           .hasUnremovableSideEffects()) {
+      typeUpdater.noteRecursiveRemoval(curr);
+      ExpressionManipulator::nop(curr);
     }
   }
 
@@ -377,7 +390,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
       ExpressionManipulator::nop(curr->body);
     }
     if (curr->getResults() == Type::none &&
-        !EffectAnalyzer(getPassOptions(), *getModule(), curr->body)
+        !EffectAnalyzer(getPassOptions(), *getModule(), curr)
            .hasUnremovableSideEffects()) {
       ExpressionManipulator::nop(curr->body);
     }
diff --git a/src/passes/call-utils.h b/src/passes/call-utils.h
new file mode 100644
index 0000000..7f2ebfd
--- /dev/null
+++ b/src/passes/call-utils.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_function_h
+#define wasm_ir_function_h
+
+#include <variant>
+
+#include "ir/type-updating.h"
+#include "wasm.h"
+
+namespace wasm::CallUtils {
+
+// Define a variant to describe the information we know about an indirect call,
+// which is one of three things:
+//  * Unknown: Nothing is known this call.
+//  * Trap: This call target is invalid and will trap at runtime.
+//  * Known: This call goes to a known static call target, which is provided.
+struct Unknown : public std::monostate {};
+struct Trap : public std::monostate {};
+struct Known {
+  Name target;
+};
+using IndirectCallInfo = std::variant<Unknown, Trap, Known>;
+
+// Converts indirect calls that target selects between values into ifs over
+// direct calls. For example, consider this input:
+//
+//  (call_ref
+//    (select
+//      (ref.func A)
+//      (ref.func B)
+//      (..condition..)
+//    )
+//  )
+//
+// We'll check if the input falls into such a pattern, and if so, return the new
+// form:
+//
+//  (if
+//    (..condition..)
+//    (call $A)
+//    (call $B)
+//  )
+//
+// If we fail to find the expected pattern, or we decide it is not worth
+// optimizing it for some reason, we return nullptr.
+//
+// If this returns the new form, it will modify the function as necessary,
+// adding new locals etc., which later passes should optimize.
+//
+// |getCallInfo| is given one of the arms of the select and should return an
+// IndirectCallInfo that says what we know about it. We may know nothing, or
+// that it will trap, or that it will go to a known static target.
+template<typename T>
+inline Expression*
+convertToDirectCalls(T* curr,
+                     std::function<IndirectCallInfo(Expression*)> getCallInfo,
+                     Function& func,
+                     Module& wasm) {
+  auto* select = curr->target->template dynCast<Select>();
+  if (!select) {
+    return nullptr;
+  }
+
+  if (select->type == Type::unreachable) {
+    // Leave this for DCE.
+    return nullptr;
+  }
+
+  // Check if we can find useful info for both arms: either known call targets,
+  // or traps.
+  // TODO: support more than 2 targets (with nested selects)
+  auto ifTrueCallInfo = getCallInfo(select->ifTrue);
+  auto ifFalseCallInfo = getCallInfo(select->ifFalse);
+  if (std::get_if<Unknown>(&ifTrueCallInfo) ||
+      std::get_if<Unknown>(&ifFalseCallInfo)) {
+    // We know nothing about at least one arm, so give up.
+    // TODO: Perhaps emitting a direct call for one arm is enough even if the
+    //       other remains indirect?
+    return nullptr;
+  }
+
+  auto& operands = curr->operands;
+
+  // We must use the operands twice, and also must move the condition to
+  // execute first, so we'll use locals for them all. First, see if any are
+  // unreachable, and if so stop trying to optimize and leave this for DCE.
+  for (auto* operand : operands) {
+    if (operand->type == Type::unreachable ||
+        !TypeUpdating::canHandleAsLocal(operand->type)) {
+      return nullptr;
+    }
+  }
+
+  Builder builder(wasm);
+  std::vector<Expression*> blockContents;
+
+  // None of the types are a problem, so we can proceed to add new vars as
+  // needed and perform this optimization.
+  std::vector<Index> operandLocals;
+  for (auto* operand : operands) {
+    auto currLocal = builder.addVar(&func, operand->type);
+    operandLocals.push_back(currLocal);
+    blockContents.push_back(builder.makeLocalSet(currLocal, operand));
+  }
+
+  // Build the calls.
+  auto numOperands = operands.size();
+  auto getOperands = [&]() {
+    std::vector<Expression*> newOperands(numOperands);
+    for (Index i = 0; i < numOperands; i++) {
+      newOperands[i] =
+        builder.makeLocalGet(operandLocals[i], operands[i]->type);
+    }
+    return newOperands;
+  };
+
+  auto makeCall = [&](IndirectCallInfo info) -> Expression* {
+    if (std::get_if<Trap>(&info)) {
+      return builder.makeUnreachable();
+    } else {
+      return builder.makeCall(std::get<Known>(info).target,
+                              getOperands(),
+                              curr->type,
+                              curr->isReturn);
+    }
+  };
+  auto* ifTrueCall = makeCall(ifTrueCallInfo);
+  auto* ifFalseCall = makeCall(ifFalseCallInfo);
+
+  // Create the if to pick the calls, and emit the final block.
+  auto* iff = builder.makeIf(select->condition, ifTrueCall, ifFalseCall);
+  blockContents.push_back(iff);
+  return builder.makeBlock(blockContents);
+}
+
+} // namespace wasm::CallUtils
+
+#endif // wasm_ir_function_h
diff --git a/src/passes/opt-utils.h b/src/passes/opt-utils.h
index 28ace04..9bc81c3 100644
--- a/src/passes/opt-utils.h
+++ b/src/passes/opt-utils.h
@@ -64,8 +64,8 @@ struct FunctionRefReplacer
 
   FunctionRefReplacer(MaybeReplace maybeReplace) : maybeReplace(maybeReplace) {}
 
-  FunctionRefReplacer* create() override {
-    return new FunctionRefReplacer(maybeReplace);
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FunctionRefReplacer>(maybeReplace);
   }
 
   void visitCall(Call* curr) { maybeReplace(curr->target); }
diff --git a/src/passes/param-utils.cpp b/src/passes/param-utils.cpp
index cf7873d..e94ea95 100644
--- a/src/passes/param-utils.cpp
+++ b/src/passes/param-utils.cpp
@@ -62,20 +62,37 @@ bool removeParameter(const std::vector<Function*>& funcs,
   // Check if none of the calls has a param with side effects that we cannot
   // remove (as if we can remove them, we will simply do that when we remove the
   // parameter). Note: flattening the IR beforehand can help here.
-  auto hasBadEffects = [&](ExpressionList& operands) {
-    return EffectAnalyzer(runner->options, *module, operands[index])
-      .hasUnremovableSideEffects();
+  //
+  // It would also be bad if we remove a parameter that causes the type of the
+  // call to change, like this:
+  //
+  //  (call $foo
+  //    (unreachable))
+  //
+  // After removing the parameter the type should change from unreachable to
+  // something concrete. We could handle this by updating the type and then
+  // propagating that out, or by appending an unreachable after the call, but
+  // for simplicity just ignore such cases; if we are called again later then
+  // if DCE ran meanwhile then we could optimize.
+  auto hasBadEffects = [&](auto* call) {
+    auto& operands = call->operands;
+    bool hasUnremovable =
+      EffectAnalyzer(runner->options, *module, operands[index])
+        .hasUnremovableSideEffects();
+    bool wouldChangeType = call->type == Type::unreachable && !call->isReturn &&
+                           operands[index]->type == Type::unreachable;
+    return hasUnremovable || wouldChangeType;
   };
   bool callParamsAreValid =
     std::none_of(calls.begin(), calls.end(), [&](Call* call) {
-      return hasBadEffects(call->operands);
+      return hasBadEffects(call);
     });
   if (!callParamsAreValid) {
     return false;
   }
   bool callRefParamsAreValid =
     std::none_of(callRefs.begin(), callRefs.end(), [&](CallRef* call) {
-      return hasBadEffects(call->operands);
+      return hasBadEffects(call);
     });
   if (!callRefParamsAreValid) {
     return false;
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 04947ae..68561f7 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -23,6 +23,7 @@
 
 #include "ir/hashed.h"
 #include "ir/module-utils.h"
+#include "ir/type-updating.h"
 #include "pass.h"
 #include "passes/passes.h"
 #include "support/colors.h"
@@ -127,6 +128,9 @@ void PassRegistry::registerPasses() {
                createDeNaNPass);
   registerPass(
     "directize", "turns indirect calls into direct ones", createDirectizePass);
+  registerPass("discard-global-effects",
+               "discards global effect info",
+               createDiscardGlobalEffectsPass);
   registerPass(
     "dfo", "optimizes using the DataFlow SSA IR", createDataFlowOptsPass);
   registerPass("dwarfdump",
@@ -164,12 +168,25 @@ void PassRegistry::registerPasses() {
     "functions with i64 in their signature (which cannot be invoked "
     "via the wasm table without JavaScript BigInt support).",
     createGenerateI64DynCallsPass);
+  registerPass("generate-global-effects",
+               "generate global effect info (helps later passes)",
+               createGenerateGlobalEffectsPass);
   registerPass(
     "generate-stack-ir", "generate Stack IR", createGenerateStackIRPass);
   registerPass(
     "global-refining", "refine the types of globals", createGlobalRefiningPass);
+  registerPass(
+    "gsi", "globally optimize struct values", createGlobalStructInferencePass);
   registerPass(
     "gto", "globally optimize GC types", createGlobalTypeOptimizationPass);
+  registerPass("gufa",
+               "Grand Unified Flow Analysis: optimize the entire program using "
+               "information about what content can actually appear in each "
+               "location",
+               createGUFAPass);
+  registerPass("gufa-optimizing",
+               "GUFA plus local optimizations in functions we modified",
+               createGUFAOptimizingPass);
   registerPass("type-refining",
                "apply more specific subtypes to type fields where possible",
                createTypeRefiningPass);
@@ -186,6 +203,9 @@ void PassRegistry::registerPasses() {
   registerPass("intrinsic-lowering",
                "lower away binaryen intrinsics",
                createIntrinsicLoweringPass);
+  registerPass("jspi",
+               "wrap imports and exports for JavaScript promise integration",
+               createJSPIPass);
   registerPass("legalize-js-interface",
                "legalizes i64 types on the import/export boundary",
                createLegalizeJSInterfacePass);
@@ -252,6 +272,15 @@ void PassRegistry::registerPasses() {
   registerPass("mod-asyncify-never-unwind",
                "apply the assumption that asyncify never unwinds",
                createModAsyncifyNeverUnwindPass);
+  registerPass("monomorphize",
+               "creates specialized versions of functions",
+               createMonomorphizePass);
+  registerPass("monomorphize-always",
+               "creates specialized versions of functions (even if unhelpful)",
+               createMonomorphizeAlwaysPass);
+  registerPass("multi-memory-lowering",
+               "combines multiple memories into a single memory",
+               createMultiMemoryLoweringPass);
   registerPass("nm", "name list", createNameListPass);
   registerPass("name-types", "(re)name all heap types", createNameTypesPass);
   registerPass("once-reduction",
@@ -264,6 +293,8 @@ void PassRegistry::registerPasses() {
                "optimizes added constants into load/store offsets, propagating "
                "them across locals too",
                createOptimizeAddedConstantsPropagatePass);
+  registerPass(
+    "optimize-casts", "eliminate and reuse casts", createOptimizeCastsPass);
   registerPass("optimize-instructions",
                "optimizes instruction combinations",
                createOptimizeInstructionsPass);
@@ -336,6 +367,12 @@ void PassRegistry::registerPasses() {
   registerPass("reorder-functions",
                "sorts functions by access frequency",
                createReorderFunctionsPass);
+  registerPass("reorder-globals",
+               "sorts globals by access frequency",
+               createReorderGlobalsPass);
+  registerTestPass("reorder-globals-always",
+                   "sorts globals by access frequency (even if there are few)",
+                   createReorderGlobalsAlwaysPass);
   registerPass("reorder-locals",
                "sorts locals by access frequency",
                createReorderLocalsPass);
@@ -359,6 +396,9 @@ void PassRegistry::registerPasses() {
   registerPass("signature-refining",
                "apply more specific subtypes to signature types where possible",
                createSignatureRefiningPass);
+  registerPass("signext-lowering",
+               "lower sign-ext operations to wasm mvp",
+               createSignExtLoweringPass);
   registerPass("simplify-globals",
                "miscellaneous globals-related optimizations",
                createSimplifyGlobalsPass);
@@ -387,6 +427,9 @@ void PassRegistry::registerPasses() {
   registerPass("souperify-single-use",
                "emit Souper IR in text form (single-use nodes only)",
                createSouperifySingleUsePass);
+  registerPass("spill-pointers",
+               "spill pointers to the C stack (useful for Boehm-style GC)",
+               createSpillPointersPass);
   registerPass("stub-unsupported-js",
                "stub out unsupported JS operations",
                createStubUnsupportedJSOpsPass);
@@ -505,6 +548,7 @@ void PassRunner::addDefaultFunctionOptimizationPasses() {
     addIfNoDWARFIssues("merge-locals"); // very slow on e.g. sqlite
   }
   if (options.optimizeLevel > 1 && wasm->features.hasGC()) {
+    addIfNoDWARFIssues("optimize-casts");
     // Coalescing may prevent subtyping (as a coalesced local must have the
     // supertype of all those combined into it), so subtype first.
     // TODO: when optimizing for size, maybe the order should reverse?
@@ -549,7 +593,9 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
   if (options.optimizeLevel >= 2) {
     addIfNoDWARFIssues("once-reduction");
   }
-  if (wasm->features.hasGC() && getTypeSystem() == TypeSystem::Nominal &&
+  if (wasm->features.hasGC() &&
+      (getTypeSystem() == TypeSystem::Nominal ||
+       getTypeSystem() == TypeSystem::Isorecursive) &&
       options.optimizeLevel >= 2) {
     addIfNoDWARFIssues("type-refining");
     addIfNoDWARFIssues("signature-pruning");
@@ -562,7 +608,12 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
     addIfNoDWARFIssues("gto");
     addIfNoDWARFIssues("remove-unused-module-elements");
     addIfNoDWARFIssues("cfp");
+    addIfNoDWARFIssues("gsi");
   }
+  // TODO: generate-global-effects here, right before function passes, then
+  //       discard in addDefaultGlobalOptimizationPostPasses? the benefit seems
+  //       quite minor so far, except perhaps when using call.without.effects
+  //       which can lead to more opportunities for global effects to matter.
 }
 
 void PassRunner::addDefaultGlobalOptimizationPostPasses() {
@@ -588,6 +639,9 @@ void PassRunner::addDefaultGlobalOptimizationPostPasses() {
     addIfNoDWARFIssues("simplify-globals");
   }
   addIfNoDWARFIssues("remove-unused-module-elements");
+  if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
+    addIfNoDWARFIssues("reorder-globals");
+  }
   // may allow more inlining/dae/etc., need --converge for that
   addIfNoDWARFIssues("directize");
   // perform Stack IR optimizations here, at the very end of the
@@ -610,7 +664,7 @@ static void dumpWast(Name name, Module* wasm) {
   // TODO: use _getpid() on windows, elsewhere?
   fullName += std::to_string(getpid()) + '-';
 #endif
-  fullName += numstr + "-" + name.str;
+  fullName += numstr + "-" + name.toString();
   Colors::setEnabled(false);
   ModuleWriter writer;
   writer.writeText(*wasm, fullName + ".wast");
@@ -868,7 +922,11 @@ void PassRunner::runPass(Pass* pass) {
     checker = std::unique_ptr<AfterEffectModuleChecker>(
       new AfterEffectModuleChecker(wasm));
   }
-  pass->run(this, wasm);
+  // Passes can only be run once and we deliberately do not clear the pass
+  // runner after running the pass, so there must not already be a runner here.
+  assert(!pass->getPassRunner());
+  pass->setPassRunner(this);
+  pass->run(wasm);
   handleAfterEffects(pass);
   if (getPassDebug()) {
     checker->check();
@@ -877,31 +935,76 @@ void PassRunner::runPass(Pass* pass) {
 
 void PassRunner::runPassOnFunction(Pass* pass, Function* func) {
   assert(pass->isFunctionParallel());
-  // function-parallel passes get a new instance per function
-  auto instance = std::unique_ptr<Pass>(pass->create());
+
+  auto passDebug = getPassDebug();
+
+  // Add extra validation logic in pass-debug mode 2. The main logic in
+  // PassRunner::run will work at the module level, and here for a function-
+  // parallel pass we can do the same at the function level: we can print the
+  // function before the pass, run the pass on the function, and then if it
+  // fails to validate we can show an error and print the state right before the
+  // pass broke it.
+  //
+  // Skip nameless passes for this. Anything without a name is an internal
+  // component of some larger pass, and information about it won't be very
+  // useful - leave it to the entire module to fail validation in that case.
+  bool extraFunctionValidation =
+    passDebug == 2 && options.validate && !pass->name.empty();
+  std::stringstream bodyBefore;
+  if (extraFunctionValidation) {
+    bodyBefore << *func->body << '\n';
+  }
+
   std::unique_ptr<AfterEffectFunctionChecker> checker;
-  if (getPassDebug()) {
-    checker = std::unique_ptr<AfterEffectFunctionChecker>(
-      new AfterEffectFunctionChecker(func));
+  if (passDebug) {
+    checker = std::make_unique<AfterEffectFunctionChecker>(func);
   }
-  instance->runOnFunction(this, wasm, func);
+
+  // Function-parallel passes get a new instance per function
+  auto instance = pass->create();
+  instance->setPassRunner(this);
+  instance->runOnFunction(wasm, func);
   handleAfterEffects(pass, func);
-  if (getPassDebug()) {
+
+  if (passDebug) {
     checker->check();
   }
+
+  if (extraFunctionValidation) {
+    if (!WasmValidator().validate(func, *wasm, WasmValidator::Minimal)) {
+      Fatal() << "Last nested function-parallel pass (" << pass->name
+              << ") broke validation of function " << func->name
+              << ". Here is the function body before:\n"
+              << bodyBefore.str() << "\n\nAnd here it is now:\n"
+              << *func->body << '\n';
+    }
+  }
 }
 
 void PassRunner::handleAfterEffects(Pass* pass, Function* func) {
-  if (pass->modifiesBinaryenIR()) {
-    // If Binaryen IR is modified, Stack IR must be cleared - it would
-    // be out of sync in a potentially dangerous way.
-    if (func) {
-      func->stackIR.reset(nullptr);
-    } else {
-      for (auto& func : wasm->functions) {
-        func->stackIR.reset(nullptr);
-      }
+  if (!pass->modifiesBinaryenIR()) {
+    return;
+  }
+
+  // Binaryen IR is modified, so we may have work here.
+
+  if (!func) {
+    // If no function is provided, then this is not a function-parallel pass,
+    // and it may have operated on any of the functions in theory, so run on
+    // them all.
+    assert(!pass->isFunctionParallel());
+    for (auto& func : wasm->functions) {
+      handleAfterEffects(pass, func.get());
     }
+    return;
+  }
+
+  // If Binaryen IR is modified, Stack IR must be cleared - it would
+  // be out of sync in a potentially dangerous way.
+  func->stackIR.reset(nullptr);
+
+  if (pass->requiresNonNullableLocalFixups()) {
+    TypeUpdating::handleNonDefaultableLocals(func, *wasm);
   }
 }
 
diff --git a/src/passes/passes.h b/src/passes/passes.h
index d7a6f99..ae0cc62 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -38,6 +38,7 @@ Pass* createDeadCodeEliminationPass();
 Pass* createDeNaNPass();
 Pass* createDeAlignPass();
 Pass* createDirectizePass();
+Pass* createDiscardGlobalEffectsPass();
 Pass* createDWARFDumpPass();
 Pass* createDuplicateImportEliminationPass();
 Pass* createDuplicateFunctionEliminationPass();
@@ -50,14 +51,19 @@ Pass* createFullPrinterPass();
 Pass* createFunctionMetricsPass();
 Pass* createGenerateDynCallsPass();
 Pass* createGenerateI64DynCallsPass();
+Pass* createGenerateGlobalEffectsPass();
 Pass* createGenerateStackIRPass();
 Pass* createGlobalRefiningPass();
+Pass* createGlobalStructInferencePass();
 Pass* createGlobalTypeOptimizationPass();
+Pass* createGUFAPass();
+Pass* createGUFAOptimizingPass();
 Pass* createHeap2LocalPass();
 Pass* createI64ToI32LoweringPass();
 Pass* createInlineMainPass();
 Pass* createInliningPass();
 Pass* createInliningOptimizingPass();
+Pass* createJSPIPass();
 Pass* createLegalizeJSInterfacePass();
 Pass* createLegalizeJSInterfaceMinimallyPass();
 Pass* createLimitSegmentsPass();
@@ -78,12 +84,16 @@ Pass* createMinifyImportsPass();
 Pass* createMinifyImportsAndExportsPass();
 Pass* createMinifyImportsAndExportsAndModulesPass();
 Pass* createMetricsPass();
+Pass* createMonomorphizePass();
+Pass* createMonomorphizeAlwaysPass();
+Pass* createMultiMemoryLoweringPass();
 Pass* createNameListPass();
 Pass* createNameTypesPass();
 Pass* createOnceReductionPass();
 Pass* createOptimizeAddedConstantsPass();
 Pass* createOptimizeAddedConstantsPropagatePass();
 Pass* createOptimizeInstructionsPass();
+Pass* createOptimizeCastsPass();
 Pass* createOptimizeForJSPass();
 Pass* createOptimizeStackIRPass();
 Pass* createPickLoadSignsPass();
@@ -106,6 +116,8 @@ Pass* createRemoveUnusedModuleElementsPass();
 Pass* createRemoveUnusedNonFunctionModuleElementsPass();
 Pass* createRemoveUnusedNamesPass();
 Pass* createReorderFunctionsPass();
+Pass* createReorderGlobalsPass();
+Pass* createReorderGlobalsAlwaysPass();
 Pass* createReorderLocalsPass();
 Pass* createReReloopPass();
 Pass* createRedundantSetEliminationPass();
@@ -114,6 +126,7 @@ Pass* createSafeHeapPass();
 Pass* createSetGlobalsPass();
 Pass* createSignaturePruningPass();
 Pass* createSignatureRefiningPass();
+Pass* createSignExtLoweringPass();
 Pass* createSimplifyLocalsPass();
 Pass* createSimplifyGlobalsPass();
 Pass* createSimplifyGlobalsOptimizingPass();
@@ -128,6 +141,7 @@ Pass* createStripProducersPass();
 Pass* createStripTargetFeaturesPass();
 Pass* createSouperifyPass();
 Pass* createSouperifySingleUsePass();
+Pass* createSpillPointersPass();
 Pass* createStubUnsupportedJSOpsPass();
 Pass* createSSAifyPass();
 Pass* createSSAifyNoMergePass();
diff --git a/src/passes/test_passes.cpp b/src/passes/test_passes.cpp
index 4ea5b86..54c4818 100644
--- a/src/passes/test_passes.cpp
+++ b/src/passes/test_passes.cpp
@@ -31,12 +31,13 @@ namespace {
 // Pass to test EHUtil::handleBlockNestedPops function
 struct CatchPopFixup : public WalkerPass<PostWalker<CatchPopFixup>> {
   bool isFunctionParallel() override { return true; }
-  Pass* create() override { return new CatchPopFixup; }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<CatchPopFixup>();
+  }
 
   void doWalkFunction(Function* func) {
     EHUtils::handleBlockNestedPops(func, *getModule());
   }
-  void run(PassRunner* runner, Module* module) override {}
 };
 
 } // anonymous namespace
diff --git a/src/pretty_printing.h b/src/pretty_printing.h
index f1dd9db..f693c4d 100644
--- a/src/pretty_printing.h
+++ b/src/pretty_printing.h
@@ -26,10 +26,7 @@
 #include "support/colors.h"
 
 inline std::ostream& doIndent(std::ostream& o, unsigned indent) {
-  for (unsigned i = 0; i < indent; i++) {
-    o << " ";
-  }
-  return o;
+  return o << std::string(indent, ' ');
 }
 
 inline std::ostream& prepareMajorColor(std::ostream& o) {
diff --git a/src/shared-constants.h b/src/shared-constants.h
index 6ad3ab9..0d2cc73 100644
--- a/src/shared-constants.h
+++ b/src/shared-constants.h
@@ -21,12 +21,7 @@
 
 namespace wasm {
 
-extern Name MEMORY_BASE;
-extern Name TABLE_BASE;
 extern Name STACK_POINTER;
-extern Name GET_TEMP_RET0;
-extern Name SET_TEMP_RET0;
-extern Name NEW_SIZE;
 extern Name MODULE;
 extern Name START;
 extern Name FUNC;
diff --git a/src/shell-interface.h b/src/shell-interface.h
index d1cf329..e27f5c6 100644
--- a/src/shell-interface.h
+++ b/src/shell-interface.h
@@ -57,8 +57,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
       static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2");
       return 0 == (reinterpret_cast<uintptr_t>(address) & (sizeof(T) - 1));
     }
-    Memory(Memory&) = delete;
-    Memory& operator=(const Memory&) = delete;
 
   public:
     Memory() = default;
@@ -92,14 +90,14 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
         return loaded;
       }
     }
-  } memory;
+  };
 
+  std::map<Name, Memory> memories;
   std::unordered_map<Name, std::vector<Literal>> tables;
   std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances;
 
   ShellExternalInterface(
-    std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances_ = {})
-    : memory() {
+    std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances_ = {}) {
     linkedInstances.swap(linkedInstances_);
   }
   virtual ~ShellExternalInterface() = default;
@@ -114,9 +112,11 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
   }
 
   void init(Module& wasm, ModuleRunner& instance) override {
-    if (wasm.memory.exists && !wasm.memory.imported()) {
-      memory.resize(wasm.memory.initial * wasm::Memory::kPageSize);
-    }
+    ModuleUtils::iterDefinedMemories(wasm, [&](wasm::Memory* memory) {
+      auto shellMemory = Memory();
+      shellMemory.resize(memory->initial * wasm::Memory::kPageSize);
+      memories[memory->name] = shellMemory;
+    });
     ModuleUtils::iterDefinedTables(
       wasm, [&](Table* table) { tables[table->name].resize(table->initial); });
   }
@@ -195,31 +195,91 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
     }
   }
 
-  int8_t load8s(Address addr) override { return memory.get<int8_t>(addr); }
-  uint8_t load8u(Address addr) override { return memory.get<uint8_t>(addr); }
-  int16_t load16s(Address addr) override { return memory.get<int16_t>(addr); }
-  uint16_t load16u(Address addr) override { return memory.get<uint16_t>(addr); }
-  int32_t load32s(Address addr) override { return memory.get<int32_t>(addr); }
-  uint32_t load32u(Address addr) override { return memory.get<uint32_t>(addr); }
-  int64_t load64s(Address addr) override { return memory.get<int64_t>(addr); }
-  uint64_t load64u(Address addr) override { return memory.get<uint64_t>(addr); }
-  std::array<uint8_t, 16> load128(Address addr) override {
+  int8_t load8s(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<int8_t>(addr);
+  }
+  uint8_t load8u(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<uint8_t>(addr);
+  }
+  int16_t load16s(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<int16_t>(addr);
+  }
+  uint16_t load16u(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<uint16_t>(addr);
+  }
+  int32_t load32s(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<int32_t>(addr);
+  }
+  uint32_t load32u(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<uint32_t>(addr);
+  }
+  int64_t load64s(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<int64_t>(addr);
+  }
+  uint64_t load64u(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
+    return memory.get<uint64_t>(addr);
+  }
+  std::array<uint8_t, 16> load128(Address addr, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     return memory.get<std::array<uint8_t, 16>>(addr);
   }
 
-  void store8(Address addr, int8_t value) override {
+  void store8(Address addr, int8_t value, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     memory.set<int8_t>(addr, value);
   }
-  void store16(Address addr, int16_t value) override {
+  void store16(Address addr, int16_t value, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     memory.set<int16_t>(addr, value);
   }
-  void store32(Address addr, int32_t value) override {
+  void store32(Address addr, int32_t value, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     memory.set<int32_t>(addr, value);
   }
-  void store64(Address addr, int64_t value) override {
+  void store64(Address addr, int64_t value, Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     memory.set<int64_t>(addr, value);
   }
-  void store128(Address addr, const std::array<uint8_t, 16>& value) override {
+  void store128(Address addr,
+                const std::array<uint8_t, 16>& value,
+                Name memoryName) override {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     memory.set<std::array<uint8_t, 16>>(addr, value);
   }
 
@@ -250,12 +310,18 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
     return table[index];
   }
 
-  bool growMemory(Address /*oldSize*/, Address newSize) override {
+  bool
+  growMemory(Name memoryName, Address /*oldSize*/, Address newSize) override {
     // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the
     // interpreter.
     if (newSize > 1024 * 1024 * 1024) {
       return false;
     }
+    auto it = memories.find(memoryName);
+    if (it == memories.end()) {
+      trap("growMemory on non-existing memory");
+    }
+    auto& memory = it->second;
     memory.resize(newSize);
     return true;
   }
diff --git a/src/support/CMakeLists.txt b/src/support/CMakeLists.txt
index 0d93503..a02c1f4 100644
--- a/src/support/CMakeLists.txt
+++ b/src/support/CMakeLists.txt
@@ -6,6 +6,7 @@ set(support_SOURCES
   command-line.cpp
   debug.cpp
   file.cpp
+  istring.cpp
   path.cpp
   safe_integer.cpp
   threads.cpp
diff --git a/src/support/command-line.cpp b/src/support/command-line.cpp
index 0147f90..23ded03 100644
--- a/src/support/command-line.cpp
+++ b/src/support/command-line.cpp
@@ -55,6 +55,10 @@ Options::Options(const std::string& command, const std::string& description)
   : debug(false), positional(Arguments::Zero) {
   std::string GeneralOption = "General options";
 
+  if (getenv("BINARYEN_DEBUG")) {
+    setDebugEnabled(getenv("BINARYEN_DEBUG"));
+  }
+
   add("--version",
       "",
       "Output version information and exit",
diff --git a/src/support/file.cpp b/src/support/file.cpp
index dc9707a..cfd6563 100644
--- a/src/support/file.cpp
+++ b/src/support/file.cpp
@@ -16,6 +16,7 @@
 
 #include "support/file.h"
 #include "support/debug.h"
+#include "support/utilities.h"
 
 #include <cstdint>
 #include <cstdlib>
@@ -58,18 +59,16 @@ T wasm::read_file(const std::string& filename, Flags::BinaryOption binary) {
   }
   infile.open(filename, flags);
   if (!infile.is_open()) {
-    std::cerr << "Failed opening '" << filename << "'" << std::endl;
-    exit(EXIT_FAILURE);
+    Fatal() << "Failed opening '" << filename << "'";
   }
   infile.seekg(0, std::ios::end);
   std::streampos insize = infile.tellg();
   if (uint64_t(insize) >= std::numeric_limits<size_t>::max()) {
     // Building a 32-bit executable where size_t == 32 bits, we are not able to
     // create strings larger than 2^32 bytes in length, so must abort here.
-    std::cerr << "Failed opening '" << filename
-              << "': Input file too large: " << insize
-              << " bytes. Try rebuilding in 64-bit mode." << std::endl;
-    exit(EXIT_FAILURE);
+    Fatal() << "Failed opening '" << filename
+            << "': Input file too large: " << insize
+            << " bytes. Try rebuilding in 64-bit mode.";
   }
   T input(size_t(insize) + (binary == Flags::Binary ? 0 : 1), '\0');
   if (size_t(insize) == 0) {
@@ -115,8 +114,7 @@ wasm::Output::Output(const std::string& filename, Flags::BinaryOption binary)
         }
         outfile.open(filename, flags);
         if (!outfile.is_open()) {
-          std::cerr << "Failed opening '" << filename << "'" << std::endl;
-          exit(EXIT_FAILURE);
+          Fatal() << "Failed opening '" << filename << "'";
         }
         buffer = outfile.rdbuf();
       }
diff --git a/src/support/hash.h b/src/support/hash.h
index d3a8586..fc8b358 100644
--- a/src/support/hash.h
+++ b/src/support/hash.h
@@ -29,7 +29,7 @@ template<typename T> inline std::size_t hash(const T& value) {
 
 // Combines two digests into the first digest. Use instead of `rehash` if
 // `otherDigest` is another digest and not a `size_t` value.
-static inline void hash_combine(std::size_t& digest, std::size_t otherDigest) {
+inline void hash_combine(std::size_t& digest, const std::size_t otherDigest) {
   // see: boost/container_hash/hash.hpp
   // The constant is the N-bits reciprocal of the golden ratio:
   //  phi = (1 + sqrt(5)) / 2
@@ -61,6 +61,24 @@ template<typename T1, typename T2> struct hash<pair<T1, T2>> {
   }
 };
 
+// And hashing tuples is useful, too.
+template<typename T, typename... Ts> struct hash<tuple<T, Ts...>> {
+private:
+  template<size_t... I>
+  static void rehash(const tuple<T, Ts...>& tup,
+                     size_t& digest,
+                     std::index_sequence<I...>) {
+    (wasm::rehash(digest, std::get<1 + I>(tup)), ...);
+  }
+
+public:
+  size_t operator()(const tuple<T, Ts...>& tup) const {
+    auto digest = wasm::hash(std::get<0>(tup));
+    rehash(tup, digest, std::index_sequence_for<Ts...>{});
+    return digest;
+  }
+};
+
 } // namespace std
 
 #endif // wasm_support_hash_h
diff --git a/src/support/insert_ordered.h b/src/support/insert_ordered.h
index 12c98a3..1460c43 100644
--- a/src/support/insert_ordered.h
+++ b/src/support/insert_ordered.h
@@ -32,7 +32,7 @@ template<typename T> struct InsertOrderedSet {
   std::unordered_map<T, typename std::list<T>::iterator> Map;
   std::list<T> List;
 
-  typedef typename std::list<T>::iterator iterator;
+  using iterator = typename std::list<T>::iterator;
   iterator begin() { return List.begin(); }
   iterator end() { return List.end(); }
 
@@ -87,7 +87,7 @@ template<typename Key, typename T> struct InsertOrderedMap {
     Map;
   std::list<std::pair<const Key, T>> List;
 
-  typedef typename std::list<std::pair<const Key, T>>::iterator iterator;
+  using iterator = typename std::list<std::pair<const Key, T>>::iterator;
   iterator begin() { return List.begin(); }
   iterator end() { return List.end(); }
 
@@ -96,7 +96,7 @@ template<typename Key, typename T> struct InsertOrderedMap {
   const_iterator begin() const { return List.begin(); }
   const_iterator end() const { return List.end(); }
 
-  std::pair<iterator, bool> insert(std::pair<const Key, T>& kv) {
+  std::pair<iterator, bool> insert(const std::pair<const Key, T>& kv) {
     // Try inserting with a placeholder list iterator.
     auto inserted = Map.insert({kv.first, List.end()});
     if (inserted.second) {
diff --git a/src/support/istring.cpp b/src/support/istring.cpp
new file mode 100644
index 0000000..8a3319b
--- /dev/null
+++ b/src/support/istring.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "istring.h"
+
+namespace wasm {
+
+std::string_view IString::interned(std::string_view s, bool reuse) {
+  // We need a set of string_views that can be modified in-place to minimize
+  // the number of lookups we do. Since set elements cannot normally be
+  // modified, wrap the string_views in a container that provides mutability
+  // even through a const reference.
+  struct MutStringView {
+    mutable std::string_view str;
+    MutStringView(std::string_view str) : str(str) {}
+  };
+  struct MutStringViewHash {
+    size_t operator()(const MutStringView& mut) const {
+      return std::hash<std::string_view>{}(mut.str);
+    }
+  };
+  struct MutStringViewEqual {
+    bool operator()(const MutStringView& a, const MutStringView& b) const {
+      return a.str == b.str;
+    }
+  };
+  using StringSet =
+    std::unordered_set<MutStringView, MutStringViewHash, MutStringViewEqual>;
+
+  // The authoritative global set of interned string views.
+  static StringSet globalStrings;
+
+  // The global backing store for interned strings that do not otherwise have
+  // stable addresses.
+  static std::vector<std::vector<char>> allocated;
+
+  // Guards access to `globalStrings` and `allocated`.
+  static std::mutex mutex;
+
+  // A thread-local cache of strings to reduce contention.
+  thread_local static StringSet localStrings;
+
+  auto [localIt, localInserted] = localStrings.insert(s);
+  if (!localInserted) {
+    // We already had a local copy of this string.
+    return localIt->str;
+  }
+
+  // No copy yet in the local cache. Check the global cache.
+  std::unique_lock<std::mutex> lock(mutex);
+  auto [globalIt, globalInserted] = globalStrings.insert(s);
+  if (!globalInserted) {
+    // We already had a global copy of this string. Cache it locally.
+    localIt->str = globalIt->str;
+    return localIt->str;
+  }
+
+  if (!reuse) {
+    // We have a new string, but it doesn't have a stable address. Create a copy
+    // of the data at a stable address we can use. Make sure it is null
+    // terminated so legacy uses that get a C string still work.
+    allocated.emplace_back();
+    auto& data = allocated.back();
+    data.reserve(s.size() + 1);
+    data.insert(data.end(), s.begin(), s.end());
+    data.push_back('\0');
+    s = std::string_view(allocated.back().data(), s.size());
+  }
+
+  // Intern our new string.
+  localIt->str = globalIt->str = s;
+  return s;
+}
+
+} // namespace wasm
diff --git a/src/support/istring.h b/src/support/istring.h
new file mode 100644
index 0000000..14f991c
--- /dev/null
+++ b/src/support/istring.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Interned String type, 100% interned on creation. Comparisons are always just
+// a pointer comparison
+
+#ifndef wasm_support_istring_h
+#define wasm_support_istring_h
+
+#include <set>
+#include <string_view>
+#include <unordered_set>
+
+#include <assert.h>
+
+#include "threads.h"
+#include "utilities.h"
+
+namespace wasm {
+
+struct IString {
+private:
+  static std::string_view interned(std::string_view s, bool reuse = true);
+
+public:
+  const std::string_view str;
+
+  IString() = default;
+
+  // TODO: This is a wildly unsafe default inherited from the previous
+  // implementation. Change it?
+  IString(std::string_view str, bool reuse = true)
+    : str(interned(str, reuse)) {}
+
+  // But other C strings generally do need to be copied.
+  IString(const char* str) : str(interned(str, false)) {}
+  IString(const std::string& str) : str(interned(str, false)) {}
+
+  IString(const IString& other) = default;
+
+  IString& operator=(const IString& other) {
+    return *(new (this) IString(other));
+  }
+
+  bool operator==(const IString& other) const {
+    // Fast! No need to compare contents due to interning
+    return str.data() == other.str.data();
+  }
+  bool operator!=(const IString& other) const { return !(*this == other); }
+  bool operator<(const IString& other) const { return str < other.str; }
+  bool operator<=(const IString& other) const { return str <= other.str; }
+  bool operator>(const IString& other) const { return str > other.str; }
+  bool operator>=(const IString& other) const { return str >= other.str; }
+
+  char operator[](int x) const { return str[x]; }
+
+  operator bool() const { return str.data() != nullptr; }
+
+  // TODO: deprecate?
+  bool is() const { return bool(*this); }
+  bool isNull() const { return !bool(*this); }
+
+  std::string toString() const { return {str.data(), str.size()}; }
+
+  bool equals(std::string_view other) const { return str == other; }
+
+  bool startsWith(std::string_view prefix) const {
+    // TODO: Use C++20 `starts_with`.
+    return str.substr(0, prefix.size()) == prefix;
+  }
+  bool startsWith(IString str) const { return startsWith(str.str); }
+
+  // Disambiguate for string literals.
+  template<int N> bool startsWith(const char (&str)[N]) {
+    return startsWith(std::string_view(str));
+  }
+
+  size_t size() const { return str.size(); }
+};
+
+} // namespace wasm
+
+namespace std {
+
+template<> struct hash<wasm::IString> {
+  size_t operator()(const wasm::IString& str) const {
+    return std::hash<size_t>{}(uintptr_t(str.str.data()));
+  }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const wasm::IString& str) {
+  return os << str.str;
+}
+
+} // namespace std
+
+#endif // wasm_support_istring_h
diff --git a/src/support/json.h b/src/support/json.h
index be4a6c2..bc880f3 100644
--- a/src/support/json.h
+++ b/src/support/json.h
@@ -37,12 +37,12 @@
 #include <unordered_set>
 #include <vector>
 
-#include "emscripten-optimizer/istring.h"
+#include "support/istring.h"
 #include "support/safe_integer.h"
 
 namespace json {
 
-typedef cashew::IString IString;
+using IString = wasm::IString;
 
 // Main value type
 struct Value {
@@ -65,8 +65,8 @@ struct Value {
 
   Type type = Null;
 
-  typedef std::vector<Ref> ArrayStorage;
-  typedef std::unordered_map<IString, Ref> ObjectStorage;
+  using ArrayStorage = std::vector<Ref>;
+  using ObjectStorage = std::unordered_map<IString, Ref>;
 
   // MSVC does not allow unrestricted unions:
   // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
@@ -112,13 +112,13 @@ struct Value {
   Value& setString(const char* s) {
     free();
     type = String;
-    str.set(s);
+    str = s;
     return *this;
   }
   Value& setString(const IString& s) {
     free();
     type = String;
-    str.set(s);
+    str = s;
     return *this;
   }
   Value& setNumber(double n) {
@@ -173,7 +173,7 @@ struct Value {
 
   const char* getCString() {
     assert(isString());
-    return str.str;
+    return str.str.data();
   }
   IString& getIString() {
     assert(isString());
@@ -403,7 +403,7 @@ struct Value {
   }
 };
 
-typedef Value::Ref Ref;
+using Ref = Value::Ref;
 
 } // namespace json
 
diff --git a/src/support/name.h b/src/support/name.h
index 615740e..a22461d 100644
--- a/src/support/name.h
+++ b/src/support/name.h
@@ -17,9 +17,7 @@
 #ifndef wasm_support_name_h
 #define wasm_support_name_h
 
-#include <string>
-
-#include "emscripten-optimizer/istring.h"
+#include "support/istring.h"
 
 namespace wasm {
 
@@ -33,14 +31,19 @@ namespace wasm {
 // TODO: as an optimization, IString values < some threshold could be considered
 //       numerical indices directly.
 
-struct Name : public cashew::IString {
-  Name() : cashew::IString() {}
-  Name(const char* str) : cashew::IString(str, false) {}
-  Name(cashew::IString str) : cashew::IString(str) {}
-  Name(const std::string& str) : cashew::IString(str.c_str(), false) {}
+struct Name : public IString {
+  Name() : IString() {}
+  Name(std::string_view str) : IString(str, false) {}
+  Name(const char* str) : IString(str, false) {}
+  Name(IString str) : IString(str) {}
+  Name(const std::string& str) : IString(str) {}
+
+  // String literals do not need to be copied. Note: Not safe to construct from
+  // temporary char arrays! Take their address first.
+  template<size_t N> Name(const char (&str)[N]) : IString(str) {}
 
   friend std::ostream& operator<<(std::ostream& o, Name name) {
-    if (name.str) {
+    if (name) {
       return o << name.str;
     } else {
       return o << "(null Name)";
@@ -48,11 +51,12 @@ struct Name : public cashew::IString {
   }
 
   static Name fromInt(size_t i) {
-    return cashew::IString(std::to_string(i).c_str(), false);
+    return IString(std::to_string(i).c_str(), false);
   }
 
-  bool hasSubstring(cashew::IString substring) {
-    return strstr(c_str(), substring.c_str()) != nullptr;
+  bool hasSubstring(IString substring) {
+    // TODO: Use C++23 `contains`.
+    return str.find(substring.str) != std::string_view::npos;
   }
 };
 
@@ -60,7 +64,7 @@ struct Name : public cashew::IString {
 
 namespace std {
 
-template<> struct hash<wasm::Name> : hash<cashew::IString> {};
+template<> struct hash<wasm::Name> : hash<wasm::IString> {};
 
 } // namespace std
 
diff --git a/src/support/safe_integer.cpp b/src/support/safe_integer.cpp
index d99508b..3a50b50 100644
--- a/src/support/safe_integer.cpp
+++ b/src/support/safe_integer.cpp
@@ -51,12 +51,12 @@ int32_t wasm::toSInteger32(double x) {
 
 bool wasm::isUInteger64(double x) {
   return !std::signbit(x) && isInteger(x) &&
-         x <= std::numeric_limits<uint64_t>::max();
+         x <= static_cast<double>(std::numeric_limits<uint64_t>::max());
 }
 
 bool wasm::isSInteger64(double x) {
   return isInteger(x) && x >= std::numeric_limits<int64_t>::min() &&
-         x <= std::numeric_limits<int64_t>::max();
+         x <= static_cast<double>(std::numeric_limits<int64_t>::max());
 }
 
 uint64_t wasm::toUInteger64(double x) {
diff --git a/src/support/small_set.h b/src/support/small_set.h
index f8725ec..b81329d 100644
--- a/src/support/small_set.h
+++ b/src/support/small_set.h
@@ -34,10 +34,72 @@
 
 namespace wasm {
 
-template<typename T, size_t N, typename FlexibleSet> class SmallSetBase {
+template<typename T, size_t N> struct FixedStorageBase {
+  size_t used = 0;
+  std::array<T, N> storage;
+};
+
+template<typename T, size_t N>
+struct UnorderedFixedStorage : public FixedStorageBase<T, N> {
+  void insert(const T& x) {
+    assert(this->used < N);
+    this->storage[this->used++] = x;
+  }
+
+  void erase(const T& x) {
+    for (size_t i = 0; i < this->used; i++) {
+      if (this->storage[i] == x) {
+        // We found the item; erase it by moving the final item to replace it
+        // and truncating the size.
+        this->used--;
+        this->storage[i] = this->storage[this->used];
+        return;
+      }
+    }
+  }
+};
+
+template<typename T, size_t N>
+struct OrderedFixedStorage : public FixedStorageBase<T, N> {
+  void insert(const T& x) {
+    assert(this->used < N);
+
+    // Find the insertion point |i| where x should be placed.
+    size_t i = 0;
+    while (i < this->used && this->storage[i] <= x) {
+      i++;
+    }
+    // |i| is now the location where x should be placed.
+
+    if (i != this->used) {
+      // Push things forward to make room for x.
+      for (size_t j = this->used; j >= i + 1; j--) {
+        this->storage[j] = this->storage[j - 1];
+      }
+    }
+
+    this->storage[i] = x;
+    this->used++;
+  }
+
+  void erase(const T& x) {
+    for (size_t i = 0; i < this->used; i++) {
+      if (this->storage[i] == x) {
+        // We found the item; move things backwards and shrink.
+        for (size_t j = i + 1; j < this->used; j++) {
+          this->storage[j - 1] = this->storage[j];
+        }
+        this->used--;
+        return;
+      }
+    }
+  }
+};
+
+template<typename T, size_t N, typename FixedStorage, typename FlexibleSet>
+class SmallSetBase {
   // fixed-space storage
-  size_t usedFixed = 0;
-  std::array<T, N> fixed;
+  FixedStorage fixed;
 
   // flexible additional storage
   FlexibleSet flexible;
@@ -73,17 +135,18 @@ public:
         return;
       }
       // We must add a new item.
-      if (usedFixed < N) {
+      if (fixed.used < N) {
         // Room remains in the fixed storage.
-        fixed[usedFixed++] = x;
+        fixed.insert(x);
       } else {
         // No fixed storage remains. Switch to flexible.
-        assert(usedFixed == N);
+        assert(fixed.used == N);
         assert(flexible.empty());
-        flexible.insert(fixed.begin(), fixed.begin() + usedFixed);
+        flexible.insert(fixed.storage.begin(),
+                        fixed.storage.begin() + fixed.used);
         flexible.insert(x);
         assert(!usingFixed());
-        usedFixed = 0;
+        fixed.used = 0;
       }
     } else {
       flexible.insert(x);
@@ -92,15 +155,7 @@ public:
 
   void erase(const T& x) {
     if (usingFixed()) {
-      for (size_t i = 0; i < usedFixed; i++) {
-        if (fixed[i] == x) {
-          // We found the item; erase it by moving the final item to replace it
-          // and truncating the size.
-          usedFixed--;
-          fixed[i] = fixed[usedFixed];
-          return;
-        }
-      }
+      fixed.erase(x);
     } else {
       flexible.erase(x);
     }
@@ -109,8 +164,8 @@ public:
   size_t count(const T& x) const {
     if (usingFixed()) {
       // Do a linear search.
-      for (size_t i = 0; i < usedFixed; i++) {
-        if (fixed[i] == x) {
+      for (size_t i = 0; i < fixed.used; i++) {
+        if (fixed.storage[i] == x) {
           return 1;
         }
       }
@@ -122,7 +177,7 @@ public:
 
   size_t size() const {
     if (usingFixed()) {
-      return usedFixed;
+      return fixed.used;
     } else {
       return flexible.size();
     }
@@ -131,28 +186,30 @@ public:
   bool empty() const { return size() == 0; }
 
   void clear() {
-    usedFixed = 0;
+    fixed.used = 0;
     flexible.clear();
   }
 
-  bool operator==(const SmallSetBase<T, N, FlexibleSet>& other) const {
+  bool
+  operator==(const SmallSetBase<T, N, FixedStorage, FlexibleSet>& other) const {
     if (size() != other.size()) {
       return false;
     }
     if (usingFixed()) {
-      return std::all_of(fixed.begin(),
-                         fixed.begin() + usedFixed,
+      return std::all_of(fixed.storage.begin(),
+                         fixed.storage.begin() + fixed.used,
                          [&other](const T& x) { return other.count(x); });
     } else if (other.usingFixed()) {
-      return std::all_of(other.fixed.begin(),
-                         other.fixed.begin() + other.usedFixed,
+      return std::all_of(other.fixed.storage.begin(),
+                         other.fixed.storage.begin() + other.fixed.used,
                          [this](const T& x) { return count(x); });
     } else {
       return flexible == other.flexible;
     }
   }
 
-  bool operator!=(const SmallSetBase<T, N, FlexibleSet>& other) const {
+  bool
+  operator!=(const SmallSetBase<T, N, FixedStorage, FlexibleSet>& other) const {
     return !(*this == other);
   }
 
@@ -226,14 +283,14 @@ public:
 
     const value_type& operator*() const {
       if (this->usingFixed) {
-        return (this->parent->fixed)[this->fixedIndex];
+        return this->parent->fixed.storage[this->fixedIndex];
       } else {
         return *this->flexibleIterator;
       }
     }
   };
 
-  using Iterator = IteratorBase<SmallSetBase<T, N, FlexibleSet>,
+  using Iterator = IteratorBase<SmallSetBase<T, N, FixedStorage, FlexibleSet>,
                                 typename FlexibleSet::const_iterator>;
 
   Iterator begin() {
@@ -266,10 +323,14 @@ public:
 };
 
 template<typename T, size_t N>
-class SmallSet : public SmallSetBase<T, N, std::set<T>> {};
+class SmallSet
+  : public SmallSetBase<T, N, OrderedFixedStorage<T, N>, std::set<T>> {};
 
 template<typename T, size_t N>
-class SmallUnorderedSet : public SmallSetBase<T, N, std::unordered_set<T>> {};
+class SmallUnorderedSet : public SmallSetBase<T,
+                                              N,
+                                              UnorderedFixedStorage<T, N>,
+                                              std::unordered_set<T>> {};
 
 } // namespace wasm
 
diff --git a/src/support/small_vector.h b/src/support/small_vector.h
index e2c865b..c798dc5 100644
--- a/src/support/small_vector.h
+++ b/src/support/small_vector.h
@@ -116,6 +116,8 @@ public:
     usedFixed = std::min(N, newSize);
     if (newSize > N) {
       flexible.resize(newSize - N);
+    } else {
+      flexible.clear();
     }
   }
 
@@ -146,9 +148,9 @@ public:
   // iteration
 
   template<typename Parent, typename Iterator> struct IteratorBase {
-    typedef T value_type;
-    typedef long difference_type;
-    typedef T& reference;
+    using value_type = T;
+    using difference_type = long;
+    using reference = T&;
 
     Parent* parent;
     size_t index;
diff --git a/src/support/utilities.h b/src/support/utilities.h
index 57d1702..abff704 100644
--- a/src/support/utilities.h
+++ b/src/support/utilities.h
@@ -24,6 +24,7 @@
 #include <cstring>
 #include <iostream>
 #include <memory>
+#include <sstream>
 #include <type_traits>
 
 #include "support/bits.h"
@@ -62,19 +63,35 @@ std::unique_ptr<T> make_unique(Args&&... args) {
 
 // For fatal errors which could arise from input (i.e. not assertion failures)
 class Fatal {
+private:
+  std::stringstream buffer;
+
 public:
-  Fatal() { std::cerr << "Fatal: "; }
-  template<typename T> Fatal& operator<<(T arg) {
-    std::cerr << arg;
+  Fatal() { buffer << "Fatal: "; }
+  template<typename T> Fatal& operator<<(T&& arg) {
+    buffer << arg;
     return *this;
   }
+#ifndef THROW_ON_FATAL
   [[noreturn]] ~Fatal() {
-    std::cerr << "\n";
+    std::cerr << buffer.str() << std::endl;
     // Use _Exit here to avoid calling static destructors. This avoids deadlocks
     // in (for example) the thread worker pool, where workers hold a lock while
     // performing their work.
-    _Exit(1);
+    _Exit(EXIT_FAILURE);
+  }
+#else
+  // This variation is a best-effort attempt to make fatal errors recoverable
+  // for embedders of Binaryen as a library, namely wasm-opt-rs.
+  //
+  // Throwing in destructors is strongly discouraged, since it is easy to
+  // accidentally throw during unwinding, which will trigger an abort. Since
+  // `Fatal` is a special type that only occurs on error paths, we are hoping it
+  // is never constructed during unwinding or while destructing another type.
+  [[noreturn]] ~Fatal() noexcept(false) {
+    throw std::runtime_error(buffer.str());
   }
+#endif
 };
 
 [[noreturn]] void handle_unreachable(const char* msg = nullptr,
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h
index 10e2432..925c9b6 100644
--- a/src/tools/execution-results.h
+++ b/src/tools/execution-results.h
@@ -23,7 +23,7 @@
 
 namespace wasm {
 
-typedef std::vector<Literal> Loggings;
+using Loggings = std::vector<Literal>;
 
 // Logs every relevant import call parameter.
 struct LoggingExternalInterface : public ShellExternalInterface {
@@ -95,12 +95,6 @@ struct ExecutionResults {
 
   // If set, we should ignore this and not compare it to anything.
   bool ignore = false;
-  // If set, we don't compare whether a trap has occurred or not.
-  bool ignoreTrap = false;
-
-  ExecutionResults(const PassOptions& options)
-    : ignoreTrap(options.ignoreImplicitTraps || options.trapsNeverHappen) {}
-  ExecutionResults(bool ignoreTrap) : ignoreTrap(ignoreTrap) {}
 
   // get results of execution
   void get(Module& wasm) {
@@ -140,7 +134,7 @@ struct ExecutionResults {
 
   // get current results and check them against previous ones
   void check(Module& wasm) {
-    ExecutionResults optimizedResults(ignoreTrap);
+    ExecutionResults optimizedResults;
     optimizedResults.get(wasm);
     if (optimizedResults != *this) {
       std::cout << "[fuzz-exec] optimization passes changed results\n";
@@ -149,25 +143,17 @@ struct ExecutionResults {
   }
 
   bool areEqual(Literal a, Literal b) {
-    // We allow nulls to have different types (as they compare equal regardless)
-    // but anything else must have an identical type.
-    // We cannot do this in nominal typing, however, as different modules will
-    // have different types in general. We could perhaps compare the entire
-    // graph structurally TODO
-    if (getTypeSystem() != TypeSystem::Nominal) {
-      if (a.type != b.type && !(a.isNull() && b.isNull())) {
-        std::cout << "types not identical! " << a << " != " << b << '\n';
-        return false;
-      }
-    }
     if (a.type.isRef()) {
-      // Don't compare references - only their types. There are several issues
-      // here that we can't fully handle, see
-      // https://github.com/WebAssembly/binaryen/issues/3378, but the core issue
-      // is that we are comparing results between two separate wasm modules (and
-      // a separate instance of each) - we can't really identify an identical
-      // reference between such things. We can only compare things structurally,
-      // for which we compare the types.
+      // Don't compare references. There are several issues here that we can't
+      // fully handle, see https://github.com/WebAssembly/binaryen/issues/3378,
+      // but the core issue is that since we optimize assuming a closed world,
+      // the types and structure of GC data can arbitrarily change after
+      // optimizations, even in ways that are externally visible from outside
+      // the module.
+      //
+      // TODO: Once we support optimizing under some form of open-world
+      // assumption, we should be able to check that the types and/or structure
+      // of GC data passed out of the module does not change.
       return true;
     }
     if (a != b) {
@@ -202,14 +188,7 @@ struct ExecutionResults {
       }
       std::cout << "[fuzz-exec] comparing " << name << '\n';
       if (results[name].index() != other.results[name].index()) {
-        if (ignoreTrap) {
-          if (!std::get_if<Trap>(&results[name]) &&
-              !std::get_if<Trap>(&other.results[name])) {
-            return false;
-          }
-        } else {
-          return false;
-        }
+        return false;
       }
       auto* values = std::get_if<Literals>(&results[name]);
       auto* otherValues = std::get_if<Literals>(&other.results[name]);
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index fe51914..aff2a0a 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -76,8 +76,9 @@ public:
 
   void build();
 
-private:
   Module& wasm;
+
+private:
   Builder builder;
   Random random;
 
@@ -181,11 +182,11 @@ private:
 
   // Recombination and mutation can replace a node with another node of the same
   // type, but should not do so for certain types that are dangerous. For
-  // example, it would be bad to add an RTT in a tuple, as that would force us
-  // to use temporary locals for the tuple, but RTTs are not defaultable.
-  // Also, 'pop' pseudo instruction for EH is supposed to exist only at the
-  // beginning of a 'catch' block, so it shouldn't be moved around or deleted
-  // freely.
+  // example, it would be bad to add a non-nullable reference to a tuple, as
+  // that would force us to use temporary locals for the tuple, but non-nullable
+  // references cannot always be stored in locals. Also, the 'pop' pseudo
+  // instruction for EH is supposed to exist only at the beginning of a 'catch'
+  // block, so it shouldn't be moved around or deleted freely.
   bool canBeArbitrarilyReplaced(Expression* curr) {
     return curr->type.isDefaultable() &&
            !EHUtils::containsValidDanglingPop(curr);
@@ -257,7 +258,18 @@ private:
   Literal tweak(Literal value);
   Literal makeLiteral(Type type);
   Expression* makeRefFuncConst(Type type);
+
+  // Emit a constant expression for a given type, as best we can. We may not be
+  // able to emit a literal Const, like say if the type is a function reference
+  // then we may emit a RefFunc, but also we may have other requirements, like
+  // we may add a GC cast to fixup the type.
   Expression* makeConst(Type type);
+
+  // Like makeConst, but for a type that is a reference type. One function
+  // handles basic types, and the other compound ones.
+  Expression* makeConstBasicRef(Type type);
+  Expression* makeConstCompoundRef(Type type);
+
   Expression* buildUnary(const UnaryArgs& args);
   Expression* makeUnary(Type type);
   Expression* buildBinary(const BinaryArgs& args);
@@ -301,7 +313,6 @@ private:
   bool isLoggableType(Type type);
   Nullability getSubType(Nullability nullability);
   HeapType getSubType(HeapType type);
-  Rtt getSubType(Rtt rtt);
   Type getSubType(Type type);
 
   // Utilities
diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp
index a5f00d8..cda14c9 100644
--- a/src/tools/fuzzing/fuzzing.cpp
+++ b/src/tools/fuzzing/fuzzing.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "tools/fuzzing.h"
+#include "ir/type-updating.h"
 #include "tools/fuzzing/heap-types.h"
 #include "tools/fuzzing/parameters.h"
 
@@ -185,32 +186,38 @@ void TranslateToFuzzReader::build() {
 
 void TranslateToFuzzReader::setupMemory() {
   // Add memory itself
-  MemoryUtils::ensureExists(wasm.memory);
+  MemoryUtils::ensureExists(&wasm);
   if (wasm.features.hasBulkMemory()) {
     size_t memCovered = 0;
     // need at least one segment for memory.inits
     size_t numSegments = upTo(8) + 1;
     for (size_t i = 0; i < numSegments; i++) {
-      Memory::Segment segment;
-      segment.isPassive = bool(upTo(2));
+      auto segment = builder.makeDataSegment();
+      segment->setName(Name::fromInt(i), false);
+      segment->isPassive = bool(upTo(2));
       size_t segSize = upTo(USABLE_MEMORY * 2);
-      segment.data.resize(segSize);
+      segment->data.resize(segSize);
       for (size_t j = 0; j < segSize; j++) {
-        segment.data[j] = upTo(512);
+        segment->data[j] = upTo(512);
       }
-      if (!segment.isPassive) {
-        segment.offset = builder.makeConst(int32_t(memCovered));
+      if (!segment->isPassive) {
+        segment->offset = builder.makeConst(int32_t(memCovered));
         memCovered += segSize;
+        segment->memory = wasm.memories[0]->name;
       }
-      wasm.memory.segments.push_back(segment);
+      wasm.dataSegments.push_back(std::move(segment));
     }
   } else {
     // init some data
-    wasm.memory.segments.emplace_back(builder.makeConst(int32_t(0)));
+    auto segment = builder.makeDataSegment();
+    segment->memory = wasm.memories[0]->name;
+    segment->offset = builder.makeConst(int32_t(0));
+    segment->setName(Name::fromInt(0), false);
+    wasm.dataSegments.push_back(std::move(segment));
     auto num = upTo(USABLE_MEMORY * 2);
     for (size_t i = 0; i < num; i++) {
       auto value = upTo(512);
-      wasm.memory.segments[0].data.push_back(value >= 256 ? 0 : (value & 0xff));
+      wasm.dataSegments[0]->data.push_back(value >= 256 ? 0 : (value & 0xff));
     }
   }
   // Add memory hasher helper (for the hash, see hash.h). The function looks
@@ -225,7 +232,7 @@ void TranslateToFuzzReader::setupMemory() {
   std::vector<Expression*> contents;
   contents.push_back(
     builder.makeLocalSet(0, builder.makeConst(uint32_t(5381))));
-  auto zero = Literal::makeFromInt32(0, wasm.memory.indexType);
+  auto zero = Literal::makeFromInt32(0, wasm.memories[0]->indexType);
   for (Index i = 0; i < USABLE_MEMORY; i++) {
     contents.push_back(builder.makeLocalSet(
       0,
@@ -237,7 +244,13 @@ void TranslateToFuzzReader::setupMemory() {
                              builder.makeLocalGet(0, Type::i32),
                              builder.makeConst(uint32_t(5))),
           builder.makeLocalGet(0, Type::i32)),
-        builder.makeLoad(1, false, i, 1, builder.makeConst(zero), Type::i32))));
+        builder.makeLoad(1,
+                         false,
+                         i,
+                         1,
+                         builder.makeConst(zero),
+                         Type::i32,
+                         wasm.memories[0]->name))));
   }
   contents.push_back(builder.makeLocalGet(0, Type::i32));
   auto* body = builder.makeBlock(contents);
@@ -247,7 +260,8 @@ void TranslateToFuzzReader::setupMemory() {
     builder.makeExport(hasher->name, hasher->name, ExternalKind::Function));
   // Export memory so JS fuzzing can use it
   if (!wasm.getExportOrNull("memory")) {
-    wasm.addExport(builder.makeExport("memory", "0", ExternalKind::Memory));
+    wasm.addExport(builder.makeExport(
+      "memory", wasm.memories[0]->name, ExternalKind::Memory));
   }
 }
 
@@ -256,15 +270,15 @@ void TranslateToFuzzReader::setupTables() {
   // Ensure a funcref element segment and table exist. Segments with more
   // specific function types may have a smaller chance of getting functions.
   Table* table = nullptr;
-  auto iter =
-    std::find_if(wasm.tables.begin(), wasm.tables.end(), [&](auto& table) {
-      return table->type == Type::funcref;
-    });
+  Type funcref = Type(HeapType::func, Nullable);
+  auto iter = std::find_if(wasm.tables.begin(),
+                           wasm.tables.end(),
+                           [&](auto& table) { return table->type == funcref; });
   if (iter != wasm.tables.end()) {
     table = iter->get();
   } else {
     auto tablePtr = builder.makeTable(
-      Names::getValidTableName(wasm, "fuzzing_table"), Type::funcref, 0, 0);
+      Names::getValidTableName(wasm, "fuzzing_table"), funcref, 0, 0);
     tablePtr->hasExplicitName = true;
     table = wasm.addTable(std::move(tablePtr));
   }
@@ -273,7 +287,7 @@ void TranslateToFuzzReader::setupTables() {
     std::any_of(wasm.elementSegments.begin(),
                 wasm.elementSegments.end(),
                 [&](auto& segment) {
-                  return segment->table.is() && segment->type == Type::funcref;
+                  return segment->table.is() && segment->type == funcref;
                 });
   if (!hasFuncrefElemSegment) {
     // TODO: use a random table
@@ -326,10 +340,10 @@ void TranslateToFuzzReader::setupTags() {
 }
 
 void TranslateToFuzzReader::finalizeMemory() {
-  for (auto& segment : wasm.memory.segments) {
-    Address maxOffset = segment.data.size();
-    if (!segment.isPassive) {
-      if (auto* offset = segment.offset->dynCast<GlobalGet>()) {
+  for (auto& segment : wasm.dataSegments) {
+    Address maxOffset = segment->data.size();
+    if (!segment->isPassive) {
+      if (auto* offset = segment->offset->dynCast<GlobalGet>()) {
         // Using a non-imported global in a segment offset is not valid in
         // wasm. This can occur due to us making what used to be an imported
         // global, in initial contents, be not imported any more. To fix that,
@@ -342,33 +356,34 @@ void TranslateToFuzzReader::finalizeMemory() {
         if (!wasm.getGlobal(offset->name)->imported()) {
           // TODO: It would be better to avoid segment overlap so that
           //       MemoryPacking can run.
-          segment.offset =
+          segment->offset =
             builder.makeConst(Literal::makeFromInt32(0, Type::i32));
         }
       }
-      if (auto* offset = segment.offset->dynCast<Const>()) {
+      if (auto* offset = segment->offset->dynCast<Const>()) {
         maxOffset = maxOffset + offset->value.getInteger();
       }
     }
-    wasm.memory.initial = std::max(
-      wasm.memory.initial,
+    wasm.memories[0]->initial = std::max(
+      wasm.memories[0]->initial,
       Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize));
   }
-  wasm.memory.initial = std::max(wasm.memory.initial, USABLE_MEMORY);
+  wasm.memories[0]->initial =
+    std::max(wasm.memories[0]->initial, USABLE_MEMORY);
   // Avoid an unlimited memory size, which would make fuzzing very difficult
   // as different VMs will run out of system memory in different ways.
-  if (wasm.memory.max == Memory::kUnlimitedSize) {
-    wasm.memory.max = wasm.memory.initial;
+  if (wasm.memories[0]->max == Memory::kUnlimitedSize) {
+    wasm.memories[0]->max = wasm.memories[0]->initial;
   }
-  if (wasm.memory.max <= wasm.memory.initial) {
+  if (wasm.memories[0]->max <= wasm.memories[0]->initial) {
     // To allow growth to work (which a testcase may assume), try to make the
     // maximum larger than the initial.
     // TODO: scan the wasm for grow instructions?
-    wasm.memory.max =
-      std::min(Address(wasm.memory.initial + 1), Address(Memory::kMaxSize32));
+    wasm.memories[0]->max = std::min(Address(wasm.memories[0]->initial + 1),
+                                     Address(Memory::kMaxSize32));
   }
   // Avoid an imported memory (which the fuzz harness would need to handle).
-  wasm.memory.module = wasm.memory.base = Name();
+  wasm.memories[0]->module = wasm.memories[0]->base = Name();
 }
 
 void TranslateToFuzzReader::finalizeTable() {
@@ -447,6 +462,9 @@ TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() {
   assert(breakableStack.empty());
   assert(hangStack.empty());
   parent.funcContext = nullptr;
+
+  // We must ensure non-nullable locals validate.
+  TypeUpdating::handleNonDefaultableLocals(func, parent.wasm);
 }
 
 Expression* TranslateToFuzzReader::makeHangLimitCheck() {
@@ -489,7 +507,8 @@ Function* TranslateToFuzzReader::addFunction() {
     params.push_back(type);
   }
   auto paramType = Type(params);
-  func->type = Signature(paramType, getControlFlowType());
+  auto resultType = getControlFlowType();
+  func->type = Signature(paramType, resultType);
   Index numVars = upToSquared(MAX_VARS);
   for (Index i = 0; i < numVars; i++) {
     auto type = getConcreteType();
@@ -531,13 +550,29 @@ Function* TranslateToFuzzReader::addFunction() {
   wasm.addFunction(func);
   // Export some functions, but not all (to allow inlining etc.). Try to export
   // at least one, though, to keep each testcase interesting. Only functions
-  // with defaultable params can be exported because the trap fuzzer depends on
-  // that (TODO: fix this).
-  bool defaultableParams =
-    std::all_of(paramType.begin(), paramType.end(), [](Type t) {
-      return t.isDefaultable();
+  // with valid params and returns can be exported because the trap fuzzer
+  // depends on that (TODO: fix this).
+  auto validExportType = [](Type t) {
+    if (!t.isRef()) {
+      return true;
+    }
+    auto heapType = t.getHeapType();
+    return heapType == HeapType::ext || heapType == HeapType::func ||
+           heapType == HeapType::string;
+  };
+  bool validExportParams =
+    std::all_of(paramType.begin(), paramType.end(), [&](Type t) {
+      return validExportType(t) && t.isDefaultable();
     });
-  if (defaultableParams && (numAddedFunctions == 0 || oneIn(2)) &&
+  // Note: spec discussions around JS API integration are still ongoing, and it
+  // is not clear if we should allow nondefaultable types in exports or not
+  // (in imports, we cannot allow them in the fuzzer anyhow, since it can't
+  // construct such values in JS to send over to the wasm from the fuzzer
+  // harness).
+  bool validExportResults =
+    std::all_of(resultType.begin(), resultType.end(), validExportType);
+  if (validExportParams && validExportResults &&
+      (numAddedFunctions == 0 || oneIn(2)) &&
       !wasm.getExportOrNull(func->name)) {
     auto* export_ = new Export;
     export_->name = func->name;
@@ -588,8 +623,39 @@ void TranslateToFuzzReader::recombine(Function* func) {
 
     void visitExpression(Expression* curr) {
       if (parent.canBeArbitrarilyReplaced(curr)) {
-        exprsByType[curr->type].push_back(curr);
+        for (auto type : getRelevantTypes(curr->type)) {
+          exprsByType[type].push_back(curr);
+        }
+      }
+    }
+
+    std::vector<Type> getRelevantTypes(Type type) {
+      // Given an expression of a type, we can replace not only other
+      // expressions with the same type, but also supertypes - since then we'd
+      // be replacing with a subtype, which is valid.
+      if (!type.isRef()) {
+        return {type};
+      }
+
+      std::vector<Type> ret;
+      auto heapType = type.getHeapType();
+      auto nullability = type.getNullability();
+
+      if (nullability == NonNullable) {
+        ret = getRelevantTypes(Type(heapType, Nullable));
+      }
+
+      while (1) {
+        ret.push_back(Type(heapType, nullability));
+        // TODO: handle basic supertypes too
+        auto super = heapType.getSuperType();
+        if (!super) {
+          break;
+        }
+        heapType = *super;
       }
+
+      return ret;
     }
   };
   Scanner scanner(*this);
@@ -618,12 +684,13 @@ void TranslateToFuzzReader::recombine(Function* func) {
     }
   }
   // Second, with some probability replace an item with another item having
-  // the same type. (This is not always valid due to nesting of labels, but
+  // a proper type. (This is not always valid due to nesting of labels, but
   // we'll fix that up later.)
   struct Modder : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> {
     Module& wasm;
     Scanner& scanner;
     TranslateToFuzzReader& parent;
+    bool needRefinalize = false;
 
     Modder(Module& wasm, Scanner& scanner, TranslateToFuzzReader& parent)
       : wasm(wasm), scanner(scanner), parent(parent) {}
@@ -633,13 +700,20 @@ void TranslateToFuzzReader::recombine(Function* func) {
         // Replace it!
         auto& candidates = scanner.exprsByType[curr->type];
         assert(!candidates.empty()); // this expression itself must be there
-        replaceCurrent(
-          ExpressionManipulator::copy(parent.pick(candidates), wasm));
+        auto* rep = parent.pick(candidates);
+        replaceCurrent(ExpressionManipulator::copy(rep, wasm));
+        if (rep->type != curr->type) {
+          // Subtyping changes require us to finalize later.
+          needRefinalize = true;
+        }
       }
     }
   };
   Modder modder(wasm, scanner, *this);
   modder.walk(func->body);
+  if (modder.needRefinalize) {
+    ReFinalize().walkFunctionInModule(func, &wasm);
+  }
 }
 
 void TranslateToFuzzReader::mutate(Function* func) {
@@ -815,7 +889,7 @@ void TranslateToFuzzReader::dropToLog(Function* func) {
 }
 
 void TranslateToFuzzReader::addInvocations(Function* func) {
-  Name name = func->name.str + std::string("_invoker");
+  Name name = func->name.toString() + std::string("_invoker");
   if (wasm.getFunctionOrNull(name) || wasm.getExportOrNull(name)) {
     return;
   }
@@ -903,8 +977,7 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
            WeightedOption{&Self::makeBreak, Important},
            &Self::makeCall,
            &Self::makeCallIndirect)
-      .add(FeatureSet::TypedFunctionReferences | FeatureSet::ReferenceTypes,
-           &Self::makeCallRef);
+      .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef);
   }
   if (type.isSingle()) {
     options
@@ -914,7 +987,7 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
            &Self::makeSelect)
       .add(FeatureSet::Multivalue, &Self::makeTupleExtract);
   }
-  if (type.isSingle() && !type.isRef() && !type.isRtt()) {
+  if (type.isSingle() && !type.isRef()) {
     options.add(FeatureSet::MVP, {&Self::makeLoad, Important});
     options.add(FeatureSet::SIMD, &Self::makeSIMD);
   }
@@ -930,7 +1003,7 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
   if (type.isTuple()) {
     options.add(FeatureSet::Multivalue, &Self::makeTupleMake);
   }
-  if (type == Type::i31ref) {
+  if (type.isRef() && type.getHeapType() == HeapType::i31) {
     options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeI31New);
   }
   // TODO: struct.get and other GC things
@@ -964,8 +1037,7 @@ Expression* TranslateToFuzzReader::_makenone() {
          &Self::makeGlobalSet)
     .add(FeatureSet::BulkMemory, &Self::makeBulkMemory)
     .add(FeatureSet::Atomics, &Self::makeAtomic)
-    .add(FeatureSet::TypedFunctionReferences | FeatureSet::ReferenceTypes,
-         &Self::makeCallRef);
+    .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef);
   return (this->*pick(options))(Type::none);
 }
 
@@ -990,8 +1062,7 @@ Expression* TranslateToFuzzReader::_makeunreachable() {
          &Self::makeSwitch,
          &Self::makeDrop,
          &Self::makeReturn)
-    .add(FeatureSet::TypedFunctionReferences | FeatureSet::ReferenceTypes,
-         &Self::makeCallRef);
+    .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef);
   return (this->*pick(options))(Type::unreachable);
 }
 
@@ -1399,12 +1470,12 @@ Expression* TranslateToFuzzReader::makeTupleExtract(Type type) {
 }
 
 Expression* TranslateToFuzzReader::makePointer() {
-  auto* ret = make(wasm.memory.indexType);
+  auto* ret = make(wasm.memories[0]->indexType);
   // with high probability, mask the pointer so it's in a reasonable
   // range. otherwise, most pointers are going to be out of range and
   // most memory ops will just trap
   if (!allowOOB || !oneIn(10)) {
-    if (wasm.memory.is64()) {
+    if (wasm.memories[0]->is64()) {
       ret = builder.makeBinary(
         AndInt64, ret, builder.makeConst(int64_t(USABLE_MEMORY - 1)));
     } else {
@@ -1423,11 +1494,19 @@ Expression* TranslateToFuzzReader::makeNonAtomicLoad(Type type) {
       bool signed_ = get() & 1;
       switch (upTo(3)) {
         case 0:
-          return builder.makeLoad(1, signed_, offset, 1, ptr, type);
+          return builder.makeLoad(
+            1, signed_, offset, 1, ptr, type, wasm.memories[0]->name);
         case 1:
-          return builder.makeLoad(2, signed_, offset, pick(1, 2), ptr, type);
+          return builder.makeLoad(
+            2, signed_, offset, pick(1, 2), ptr, type, wasm.memories[0]->name);
         case 2:
-          return builder.makeLoad(4, signed_, offset, pick(1, 2, 4), ptr, type);
+          return builder.makeLoad(4,
+                                  signed_,
+                                  offset,
+                                  pick(1, 2, 4),
+                                  ptr,
+                                  type,
+                                  wasm.memories[0]->name);
       }
       WASM_UNREACHABLE("unexpected value");
     }
@@ -1435,35 +1514,50 @@ Expression* TranslateToFuzzReader::makeNonAtomicLoad(Type type) {
       bool signed_ = get() & 1;
       switch (upTo(4)) {
         case 0:
-          return builder.makeLoad(1, signed_, offset, 1, ptr, type);
+          return builder.makeLoad(
+            1, signed_, offset, 1, ptr, type, wasm.memories[0]->name);
         case 1:
-          return builder.makeLoad(2, signed_, offset, pick(1, 2), ptr, type);
+          return builder.makeLoad(
+            2, signed_, offset, pick(1, 2), ptr, type, wasm.memories[0]->name);
         case 2:
-          return builder.makeLoad(4, signed_, offset, pick(1, 2, 4), ptr, type);
+          return builder.makeLoad(4,
+                                  signed_,
+                                  offset,
+                                  pick(1, 2, 4),
+                                  ptr,
+                                  type,
+                                  wasm.memories[0]->name);
         case 3:
-          return builder.makeLoad(
-            8, signed_, offset, pick(1, 2, 4, 8), ptr, type);
+          return builder.makeLoad(8,
+                                  signed_,
+                                  offset,
+                                  pick(1, 2, 4, 8),
+                                  ptr,
+                                  type,
+                                  wasm.memories[0]->name);
       }
       WASM_UNREACHABLE("unexpected value");
     }
     case Type::f32: {
-      return builder.makeLoad(4, false, offset, pick(1, 2, 4), ptr, type);
+      return builder.makeLoad(
+        4, false, offset, pick(1, 2, 4), ptr, type, wasm.memories[0]->name);
     }
     case Type::f64: {
-      return builder.makeLoad(8, false, offset, pick(1, 2, 4, 8), ptr, type);
+      return builder.makeLoad(
+        8, false, offset, pick(1, 2, 4, 8), ptr, type, wasm.memories[0]->name);
     }
     case Type::v128: {
       if (!wasm.features.hasSIMD()) {
         return makeTrivial(type);
       }
-      return builder.makeLoad(
-        16, false, offset, pick(1, 2, 4, 8, 16), ptr, type);
+      return builder.makeLoad(16,
+                              false,
+                              offset,
+                              pick(1, 2, 4, 8, 16),
+                              ptr,
+                              type,
+                              wasm.memories[0]->name);
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("invalid type");
@@ -1485,7 +1579,7 @@ Expression* TranslateToFuzzReader::makeLoad(Type type) {
   }
   // make it atomic
   auto* load = ret->cast<Load>();
-  wasm.memory.shared = true;
+  wasm.memories[0]->shared = true;
   load->isAtomic = true;
   load->signed_ = false;
   load->align = load->bytes;
@@ -1512,6 +1606,7 @@ Expression* TranslateToFuzzReader::makeNonAtomicStore(Type type) {
         store->value = make(Type::unreachable);
         break;
     }
+    store->memory = wasm.memories[0]->name;
     store->finalize();
     return store;
   }
@@ -1527,46 +1622,59 @@ Expression* TranslateToFuzzReader::makeNonAtomicStore(Type type) {
     case Type::i32: {
       switch (upTo(3)) {
         case 0:
-          return builder.makeStore(1, offset, 1, ptr, value, type);
+          return builder.makeStore(
+            1, offset, 1, ptr, value, type, wasm.memories[0]->name);
         case 1:
-          return builder.makeStore(2, offset, pick(1, 2), ptr, value, type);
+          return builder.makeStore(
+            2, offset, pick(1, 2), ptr, value, type, wasm.memories[0]->name);
         case 2:
-          return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type);
+          return builder.makeStore(
+            4, offset, pick(1, 2, 4), ptr, value, type, wasm.memories[0]->name);
       }
       WASM_UNREACHABLE("invalid value");
     }
     case Type::i64: {
       switch (upTo(4)) {
         case 0:
-          return builder.makeStore(1, offset, 1, ptr, value, type);
+          return builder.makeStore(
+            1, offset, 1, ptr, value, type, wasm.memories[0]->name);
         case 1:
-          return builder.makeStore(2, offset, pick(1, 2), ptr, value, type);
+          return builder.makeStore(
+            2, offset, pick(1, 2), ptr, value, type, wasm.memories[0]->name);
         case 2:
-          return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type);
-        case 3:
           return builder.makeStore(
-            8, offset, pick(1, 2, 4, 8), ptr, value, type);
+            4, offset, pick(1, 2, 4), ptr, value, type, wasm.memories[0]->name);
+        case 3:
+          return builder.makeStore(8,
+                                   offset,
+                                   pick(1, 2, 4, 8),
+                                   ptr,
+                                   value,
+                                   type,
+                                   wasm.memories[0]->name);
       }
       WASM_UNREACHABLE("invalid value");
     }
     case Type::f32: {
-      return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type);
+      return builder.makeStore(
+        4, offset, pick(1, 2, 4), ptr, value, type, wasm.memories[0]->name);
     }
     case Type::f64: {
-      return builder.makeStore(8, offset, pick(1, 2, 4, 8), ptr, value, type);
+      return builder.makeStore(
+        8, offset, pick(1, 2, 4, 8), ptr, value, type, wasm.memories[0]->name);
     }
     case Type::v128: {
       if (!wasm.features.hasSIMD()) {
         return makeTrivial(type);
       }
-      return builder.makeStore(
-        16, offset, pick(1, 2, 4, 8, 16), ptr, value, type);
+      return builder.makeStore(16,
+                               offset,
+                               pick(1, 2, 4, 8, 16),
+                               ptr,
+                               value,
+                               type,
+                               wasm.memories[0]->name);
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("invalid type");
@@ -1590,7 +1698,7 @@ Expression* TranslateToFuzzReader::makeStore(Type type) {
     return store;
   }
   // make it atomic
-  wasm.memory.shared = true;
+  wasm.memories[0]->shared = true;
   store->isAtomic = true;
   store->align = store->bytes;
   return store;
@@ -1697,11 +1805,6 @@ Literal TranslateToFuzzReader::makeLiteral(Type type) {
         case Type::f64:
           return Literal(getDouble());
         case Type::v128:
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("invalid type");
@@ -1743,11 +1846,6 @@ Literal TranslateToFuzzReader::makeLiteral(Type type) {
         case Type::f64:
           return Literal(double(small));
         case Type::v128:
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
@@ -1812,11 +1910,6 @@ Literal TranslateToFuzzReader::makeLiteral(Type type) {
                                        std::numeric_limits<uint64_t>::max()));
           break;
         case Type::v128:
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
@@ -1840,11 +1933,6 @@ Literal TranslateToFuzzReader::makeLiteral(Type type) {
           value = Literal(double(int64_t(1) << upTo(64)));
           break;
         case Type::v128:
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
@@ -1883,111 +1971,42 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) {
       return builder.makeRefFunc(func->name, func->type);
     }
   }
-  // We don't have a matching function, so create a null with high probability
-  // if the type is nullable or otherwise create and cast a null with low
-  // probability.
-  if ((type.isNullable() && !oneIn(8)) || oneIn(8)) {
-    Expression* ret = builder.makeRefNull(Type(heapType, Nullable));
+  // We don't have a matching function. Create a null some of the time here,
+  // but only rarely if the type is non-nullable (because in that case we'd need
+  // to add a ref.as_non_null to validate, and the code will trap when we get
+  // here).
+  if ((type.isNullable() && oneIn(2)) || (type.isNonNullable() && oneIn(16))) {
+    Expression* ret = builder.makeRefNull(HeapType::nofunc);
     if (!type.isNullable()) {
       ret = builder.makeRefAs(RefAsNonNull, ret);
     }
     return ret;
   }
-  // As a final option, create a new function with the correct signature.
-  auto* func = wasm.addFunction(
-    builder.makeFunction(Names::getValidFunctionName(wasm, "ref_func_target"),
-                         heapType,
-                         {},
-                         builder.makeUnreachable()));
+  // As a final option, create a new function with the correct signature. If it
+  // returns a value, write a trap as we do not want to create any more code
+  // here (we might end up recursing). Note that a trap in the function lets us
+  // execute more code then the ref.as_non_null path just before us, which traps
+  // even if we never call the function.
+  auto* body = heapType.getSignature().results == Type::none
+                 ? (Expression*)builder.makeNop()
+                 : (Expression*)builder.makeUnreachable();
+  auto* func = wasm.addFunction(builder.makeFunction(
+    Names::getValidFunctionName(wasm, "ref_func_target"), heapType, {}, body));
   return builder.makeRefFunc(func->name, heapType);
 }
 
 Expression* TranslateToFuzzReader::makeConst(Type type) {
   if (type.isRef()) {
     assert(wasm.features.hasReferenceTypes());
+    // With a low chance, just emit a null if that is valid.
     if (type.isNullable() && oneIn(8)) {
-      return builder.makeRefNull(type);
-    }
-    auto heapType = type.getHeapType();
-    if (heapType.isBasic()) {
-      switch (heapType.getBasic()) {
-        case HeapType::func:
-          return makeRefFuncConst(type);
-        case HeapType::any: {
-          // Choose a subtype we can materialize a constant for. We cannot
-          // materialize non-nullable refs to func or i31 in global contexts.
-          Nullability nullability = getSubType(type.getNullability());
-          HeapType subtype;
-          if (funcContext || nullability == Nullable) {
-            subtype = pick(FeatureOptions<HeapType>()
-                             .add(FeatureSet::ReferenceTypes, HeapType::func)
-                             .add(FeatureSet::ReferenceTypes | FeatureSet::GC,
-                                  HeapType::func,
-                                  HeapType::i31,
-                                  HeapType::data));
-          } else {
-            subtype = HeapType::func;
-          }
-          return makeConst(Type(subtype, nullability));
-        }
-        case HeapType::eq: {
-          auto nullability = getSubType(type.getNullability());
-          // i31.new is not allowed in initializer expressions.
-          HeapType subtype;
-          if (funcContext) {
-            subtype = pick(HeapType::i31, HeapType::data);
-          } else {
-            subtype = HeapType::data;
-          }
-          return makeConst(Type(subtype, nullability));
-        }
-        case HeapType::i31:
-          // i31.new is not allowed in initializer expressions.
-          if (funcContext) {
-            return builder.makeI31New(makeConst(Type::i32));
-          } else {
-            assert(type.isNullable());
-            return builder.makeRefNull(type);
-          }
-        case HeapType::data:
-          assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
-          // TODO: Construct nontrivial types. For now just create a hard coded
-          // struct or array.
-          if (oneIn(2)) {
-            // Use a local static to avoid creating a fresh nominal types in
-            // --nominal mode.
-            static HeapType trivialStruct = HeapType(Struct());
-            return builder.makeStructNew(trivialStruct,
-                                         std::vector<Expression*>{});
-          } else {
-            // Use a local static to avoid creating a fresh nominal types in
-            // --nominal mode.
-            static HeapType trivialArray =
-              HeapType(Array(Field(Field::PackedType::i8, Immutable)));
-            return builder.makeArrayInit(trivialArray, {});
-          }
-      }
-    } else if (heapType.isSignature()) {
-      return makeRefFuncConst(type);
+      return builder.makeRefNull(type.getHeapType());
+    }
+    if (type.getHeapType().isBasic()) {
+      return makeConstBasicRef(type);
     } else {
-      // TODO: Handle nontrivial array and struct types.
-    }
-    // We weren't able to directly materialize a non-null constant. Try again to
-    // create a null.
-    if (type.isNullable()) {
-      return builder.makeRefNull(type);
-    }
-    // We have to produce a non-null value. Possibly create a null and cast it
-    // to non-null even though that will trap at runtime. We must have a
-    // function context because the cast is not allowed in globals.
-    if (!funcContext) {
-      std::cerr << type << "\n";
-    }
-    assert(funcContext);
-    return builder.makeRefAs(RefAsNonNull,
-                             builder.makeRefNull(Type(heapType, Nullable)));
-  } else if (type.isRtt()) {
-    return builder.makeRtt(type);
+      return makeConstCompoundRef(type);
+    }
   } else if (type.isTuple()) {
     std::vector<Expression*> operands;
     for (const auto& t : type) {
@@ -2000,6 +2019,129 @@ Expression* TranslateToFuzzReader::makeConst(Type type) {
   }
 }
 
+Expression* TranslateToFuzzReader::makeConstBasicRef(Type type) {
+  assert(type.isRef());
+  auto heapType = type.getHeapType();
+  assert(heapType.isBasic());
+  assert(wasm.features.hasReferenceTypes());
+  switch (heapType.getBasic()) {
+    case HeapType::ext: {
+      auto null = builder.makeRefNull(HeapType::ext);
+      // TODO: support actual non-nullable externrefs via imported globals or
+      // similar.
+      if (!type.isNullable()) {
+        return builder.makeRefAs(RefAsNonNull, null);
+      }
+      return null;
+    }
+    case HeapType::func: {
+      return makeRefFuncConst(type);
+    }
+    case HeapType::any: {
+      // Choose a subtype we can materialize a constant for. We cannot
+      // materialize non-nullable refs to func or i31 in global contexts.
+      Nullability nullability = getSubType(type.getNullability());
+      HeapType subtype = oneIn(2) ? HeapType::i31 : HeapType::data;
+      return makeConst(Type(subtype, nullability));
+    }
+    case HeapType::eq: {
+      if (!wasm.features.hasGC()) {
+        // Without wasm GC all we have is an "abstract" eqref type, which is
+        // a subtype of anyref, but we cannot create constants of it, except
+        // for null.
+        assert(type.isNullable());
+        return builder.makeRefNull(HeapType::none);
+      }
+      auto nullability = getSubType(type.getNullability());
+      // i31.new is not allowed in initializer expressions.
+      HeapType subtype;
+      if (funcContext) {
+        subtype = pick(HeapType::i31, HeapType::data);
+      } else {
+        subtype = HeapType::data;
+      }
+      return makeConst(Type(subtype, nullability));
+    }
+    case HeapType::i31: {
+      assert(wasm.features.hasGC());
+      if (type.isNullable() && oneIn(4)) {
+        return builder.makeRefNull(HeapType::none);
+      }
+      return builder.makeI31New(makeConst(Type::i32));
+    }
+    case HeapType::data:
+      assert(wasm.features.hasGC());
+      // TODO: Construct nontrivial types. For now just create a hard coded
+      // struct or array.
+      if (oneIn(2)) {
+        // Use a local static to avoid creating a fresh nominal types in
+        // --nominal mode.
+        static HeapType trivialStruct = HeapType(Struct());
+        return builder.makeStructNew(trivialStruct, std::vector<Expression*>{});
+      }
+      [[fallthrough]];
+    case HeapType::array: {
+      // Use a local static to avoid creating a fresh nominal types in
+      // --nominal mode.
+      static HeapType trivialArray =
+        HeapType(Array(Field(Field::PackedType::i8, Immutable)));
+      return builder.makeArrayInit(trivialArray, {});
+    }
+    case HeapType::string:
+    case HeapType::stringview_wtf8:
+    case HeapType::stringview_wtf16:
+    case HeapType::stringview_iter:
+      WASM_UNREACHABLE("TODO: strings");
+    case HeapType::none:
+    case HeapType::noext:
+    case HeapType::nofunc: {
+      auto null = builder.makeRefNull(heapType);
+      if (!type.isNullable()) {
+        return builder.makeRefAs(RefAsNonNull, null);
+      }
+      return null;
+    }
+  }
+  WASM_UNREACHABLE("invalid basic ref type");
+}
+
+Expression* TranslateToFuzzReader::makeConstCompoundRef(Type type) {
+  assert(type.isRef());
+  auto heapType = type.getHeapType();
+  assert(!heapType.isBasic());
+  assert(wasm.features.hasReferenceTypes());
+  if (heapType.isSignature()) {
+    return makeRefFuncConst(type);
+  }
+
+  // We weren't able to directly materialize a non-null constant. Try again to
+  // create a null.
+  if (type.isNullable()) {
+    return builder.makeRefNull(heapType);
+  }
+
+  // We have to produce a non-null value. Possibly create a null and cast it
+  // to non-null even though that will trap at runtime. We must have a
+  // function context for this because the cast is not allowed in globals.
+  if (funcContext) {
+    return builder.makeRefAs(RefAsNonNull, builder.makeRefNull(heapType));
+  }
+
+  // Otherwise, we are not in a function context. This can happen if we need
+  // to make a constant for the initializer of a global, for example. We've
+  // already handled simple cases of this above, for basic heap types, so what
+  // we have left here are user-defined heap types like structs.
+  // TODO: support non-defaultable fields. for now, just use default values.
+  if (type.isStruct()) {
+    return builder.makeStructNew(type.getHeapType(),
+                                 std::vector<Expression*>{});
+  } else if (type.isArray()) {
+    return builder.makeArrayNew(type.getHeapType(), makeConst(Type::i32));
+  } else {
+    WASM_UNREACHABLE("bad user-defined ref type");
+  }
+}
+
 Expression* TranslateToFuzzReader::buildUnary(const UnaryArgs& args) {
   return builder.makeUnary(args.a, args.b);
 }
@@ -2013,14 +2155,15 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) {
     // give up
     return makeTrivial(type);
   }
-  // There are no unary ops for reference or RTT types.
-  if (type.isRef() || type.isRtt()) {
+  // There are no unary ops for reference types.
+  // TODO: not quite true if you count struct.new and array.new.
+  if (type.isRef()) {
     return makeTrivial(type);
   }
   switch (type.getBasic()) {
     case Type::i32: {
       auto singleConcreteType = getSingleConcreteType();
-      if (singleConcreteType.isRef() || singleConcreteType.isRtt()) {
+      if (singleConcreteType.isRef()) {
         // TODO: Do something more interesting here.
         return makeTrivial(type);
       }
@@ -2063,11 +2206,6 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) {
                                   AllTrueVecI32x4),
                              make(Type::v128)});
         }
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
@@ -2203,11 +2341,6 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) {
       }
       WASM_UNREACHABLE("invalid value");
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -2229,8 +2362,9 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) {
     // give up
     return makeTrivial(type);
   }
-  // There are no binary ops for reference or RTT types.
-  if (type.isRef() || type.isRtt()) {
+  // There are no binary ops for reference types.
+  // TODO: Use struct.new
+  if (type.isRef()) {
     return makeTrivial(type);
   }
   switch (type.getBasic()) {
@@ -2441,11 +2575,6 @@ Expression* TranslateToFuzzReader::makeBinary(Type type) {
                           make(Type::v128),
                           make(Type::v128)});
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -2523,7 +2652,7 @@ Expression* TranslateToFuzzReader::makeAtomic(Type type) {
   if (!allowMemory) {
     return makeTrivial(type);
   }
-  wasm.memory.shared = true;
+  wasm.memories[0]->shared = true;
   if (type == Type::none) {
     return builder.makeAtomicFence();
   }
@@ -2533,12 +2662,17 @@ Expression* TranslateToFuzzReader::makeAtomic(Type type) {
       auto expectedType = pick(Type::i32, Type::i64);
       auto* expected = make(expectedType);
       auto* timeout = make(Type::i64);
-      return builder.makeAtomicWait(
-        ptr, expected, timeout, expectedType, logify(get()));
+      return builder.makeAtomicWait(ptr,
+                                    expected,
+                                    timeout,
+                                    expectedType,
+                                    logify(get()),
+                                    wasm.memories[0]->name);
     } else {
       auto* ptr = makePointer();
       auto* count = make(Type::i32);
-      return builder.makeAtomicNotify(ptr, count, logify(get()));
+      return builder.makeAtomicNotify(
+        ptr, count, logify(get()), wasm.memories[0]->name);
     }
   }
   Index bytes;
@@ -2591,12 +2725,13 @@ Expression* TranslateToFuzzReader::makeAtomic(Type type) {
       offset,
       ptr,
       value,
-      type);
+      type,
+      wasm.memories[0]->name);
   } else {
     auto* expected = make(type);
     auto* replacement = make(type);
     return builder.makeAtomicCmpxchg(
-      bytes, offset, ptr, expected, replacement, type);
+      bytes, offset, ptr, expected, replacement, type, wasm.memories[0]->name);
   }
 }
 
@@ -2648,11 +2783,6 @@ Expression* TranslateToFuzzReader::makeSIMDExtract(Type type) {
       op = ExtractLaneVecF64x2;
       break;
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -2802,7 +2932,7 @@ Expression* TranslateToFuzzReader::makeSIMDLoad() {
       WASM_UNREACHABLE("Unexpected SIMD loads");
   }
   Expression* ptr = makePointer();
-  return builder.makeSIMDLoad(op, offset, align, ptr);
+  return builder.makeSIMDLoad(op, offset, align, ptr, wasm.memories[0]->name);
 }
 
 Expression* TranslateToFuzzReader::makeBulkMemory(Type type) {
@@ -2840,7 +2970,7 @@ Expression* TranslateToFuzzReader::makeRefEq(Type type) {
 }
 
 Expression* TranslateToFuzzReader::makeI31New(Type type) {
-  assert(type == Type::i31ref);
+  assert(type.isRef() && type.getHeapType() == HeapType::i31);
   assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
   auto* value = make(Type::i32);
   return builder.makeI31New(value);
@@ -2849,7 +2979,9 @@ Expression* TranslateToFuzzReader::makeI31New(Type type) {
 Expression* TranslateToFuzzReader::makeI31Get(Type type) {
   assert(type == Type::i32);
   assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
-  auto* i31 = make(Type::i31ref);
+  // TODO: Maybe this should be nullable?
+  // https://github.com/WebAssembly/gc/issues/312
+  auto* i31 = make(Type(HeapType::i31, NonNullable));
   return builder.makeI31Get(i31, bool(oneIn(2)));
 }
 
@@ -2857,21 +2989,22 @@ Expression* TranslateToFuzzReader::makeMemoryInit() {
   if (!allowMemory) {
     return makeTrivial(Type::none);
   }
-  uint32_t segment = upTo(wasm.memory.segments.size());
-  size_t totalSize = wasm.memory.segments[segment].data.size();
+  uint32_t segment = upTo(wasm.dataSegments.size());
+  size_t totalSize = wasm.dataSegments[segment]->data.size();
   size_t offsetVal = upTo(totalSize);
   size_t sizeVal = upTo(totalSize - offsetVal);
   Expression* dest = makePointer();
   Expression* offset = builder.makeConst(int32_t(offsetVal));
   Expression* size = builder.makeConst(int32_t(sizeVal));
-  return builder.makeMemoryInit(segment, dest, offset, size);
+  return builder.makeMemoryInit(
+    segment, dest, offset, size, wasm.memories[0]->name);
 }
 
 Expression* TranslateToFuzzReader::makeDataDrop() {
   if (!allowMemory) {
     return makeTrivial(Type::none);
   }
-  return builder.makeDataDrop(upTo(wasm.memory.segments.size()));
+  return builder.makeDataDrop(upTo(wasm.dataSegments.size()));
 }
 
 Expression* TranslateToFuzzReader::makeMemoryCopy() {
@@ -2880,8 +3013,9 @@ Expression* TranslateToFuzzReader::makeMemoryCopy() {
   }
   Expression* dest = makePointer();
   Expression* source = makePointer();
-  Expression* size = make(wasm.memory.indexType);
-  return builder.makeMemoryCopy(dest, source, size);
+  Expression* size = make(wasm.memories[0]->indexType);
+  return builder.makeMemoryCopy(
+    dest, source, size, wasm.memories[0]->name, wasm.memories[0]->name);
 }
 
 Expression* TranslateToFuzzReader::makeMemoryFill() {
@@ -2890,8 +3024,8 @@ Expression* TranslateToFuzzReader::makeMemoryFill() {
   }
   Expression* dest = makePointer();
   Expression* value = make(Type::i32);
-  Expression* size = make(wasm.memory.indexType);
-  return builder.makeMemoryFill(dest, value, size);
+  Expression* size = make(wasm.memories[0]->indexType);
+  return builder.makeMemoryFill(dest, value, size, wasm.memories[0]->name);
 }
 
 Type TranslateToFuzzReader::getSingleConcreteType() {
@@ -2906,23 +3040,28 @@ Type TranslateToFuzzReader::getSingleConcreteType() {
                      WeightedOption{Type::f32, VeryImportant},
                      WeightedOption{Type::f64, VeryImportant})
                 .add(FeatureSet::SIMD, WeightedOption{Type::v128, Important})
-                .add(FeatureSet::ReferenceTypes, Type::funcref, Type::anyref)
+                .add(FeatureSet::ReferenceTypes,
+                     Type(HeapType::func, Nullable),
+                     Type(HeapType::ext, Nullable))
                 .add(FeatureSet::ReferenceTypes | FeatureSet::GC,
                      // Type(HeapType::func, NonNullable),
+                     // Type(HeapType::ext, NonNullable),
+                     Type(HeapType::any, Nullable),
                      // Type(HeapType::any, NonNullable),
                      Type(HeapType::eq, Nullable),
                      Type(HeapType::eq, NonNullable),
                      Type(HeapType::i31, Nullable),
                      // Type(HeapType::i31, NonNullable),
                      Type(HeapType::data, Nullable),
-                     Type(HeapType::data, NonNullable)));
+                     Type(HeapType::data, NonNullable),
+                     Type(HeapType::array, Nullable),
+                     Type(HeapType::array, NonNullable)));
 }
 
 Type TranslateToFuzzReader::getReferenceType() {
   return pick(FeatureOptions<Type>()
-                // Avoid Type::anyref without GC enabled, see
-                // TranslateToFuzzReader::getSingleConcreteType.
-                .add(FeatureSet::ReferenceTypes, Type::funcref)
+                // TODO: Add externref here.
+                .add(FeatureSet::ReferenceTypes, Type(HeapType::func, Nullable))
                 .add(FeatureSet::ReferenceTypes | FeatureSet::GC,
                      Type(HeapType::func, NonNullable),
                      Type(HeapType::any, NonNullable),
@@ -2997,54 +3136,77 @@ bool TranslateToFuzzReader::isLoggableType(Type type) {
 }
 
 Nullability TranslateToFuzzReader::getSubType(Nullability nullability) {
-  return nullability == NonNullable ? NonNullable
-                                    : oneIn(2) ? Nullable : NonNullable;
+  if (nullability == NonNullable) {
+    return NonNullable;
+  }
+  // Without wasm GC, avoid non-nullable types as we cannot create any values
+  // of such types. For example, reference types adds eqref, but there is no
+  // way to create such a value, only to receive it from the outside, while GC
+  // adds i31/struct/array creation. Without GC, we will likely need to create a
+  // null of this type (unless we are lucky enough to have a non-null value
+  // arriving from an import), so avoid a non-null type if possible.
+  if (wasm.features.hasGC() && oneIn(2)) {
+    return NonNullable;
+  }
+  return Nullable;
 }
 
 HeapType TranslateToFuzzReader::getSubType(HeapType type) {
+  if (oneIn(2)) {
+    return type;
+  }
   if (type.isBasic()) {
     switch (type.getBasic()) {
       case HeapType::func:
         // TODO: Typed function references.
-        return HeapType::func;
+        return pick(FeatureOptions<HeapType>()
+                      .add(FeatureSet::ReferenceTypes, HeapType::func)
+                      .add(FeatureSet::GC, HeapType::nofunc));
+      case HeapType::ext:
+        return pick(FeatureOptions<HeapType>()
+                      .add(FeatureSet::ReferenceTypes, HeapType::ext)
+                      .add(FeatureSet::GC, HeapType::noext));
       case HeapType::any:
         // TODO: nontrivial types as well.
-        return pick(
-          FeatureOptions<HeapType>()
-            .add(FeatureSet::ReferenceTypes, HeapType::func, HeapType::any)
-            .add(FeatureSet::ReferenceTypes | FeatureSet::GC,
-                 HeapType::func,
-                 HeapType::any,
-                 HeapType::eq,
-                 HeapType::i31,
-                 HeapType::data));
+        assert(wasm.features.hasReferenceTypes());
+        assert(wasm.features.hasGC());
+        return pick(HeapType::any,
+                    HeapType::eq,
+                    HeapType::i31,
+                    HeapType::data,
+                    HeapType::array,
+                    HeapType::none);
       case HeapType::eq:
         // TODO: nontrivial types as well.
-        return pick(HeapType::eq, HeapType::i31, HeapType::data);
+        assert(wasm.features.hasReferenceTypes());
+        assert(wasm.features.hasGC());
+        return pick(HeapType::eq,
+                    HeapType::i31,
+                    HeapType::data,
+                    HeapType::array,
+                    HeapType::none);
       case HeapType::i31:
-        return HeapType::i31;
+        return pick(HeapType::i31, HeapType::none);
       case HeapType::data:
         // TODO: nontrivial types as well.
-        return HeapType::data;
+        return pick(HeapType::data, HeapType::array, HeapType::none);
+      case HeapType::array:
+        return pick(HeapType::array, HeapType::none);
+      case HeapType::string:
+      case HeapType::stringview_wtf8:
+      case HeapType::stringview_wtf16:
+      case HeapType::stringview_iter:
+        WASM_UNREACHABLE("TODO: fuzz strings");
+      case HeapType::none:
+      case HeapType::noext:
+      case HeapType::nofunc:
+        break;
     }
   }
   // TODO: nontrivial types as well.
   return type;
 }
 
-Rtt TranslateToFuzzReader::getSubType(Rtt rtt) {
-  if (getTypeSystem() == TypeSystem::Nominal ||
-      getTypeSystem() == TypeSystem::Isorecursive) {
-    // With nominal or isorecursive typing the depth in rtts must match the
-    // nominal hierarchy, so we cannot create a random depth like we do below.
-    return rtt;
-  }
-  uint32_t depth = rtt.depth != Rtt::NoDepth
-                     ? rtt.depth
-                     : oneIn(2) ? Rtt::NoDepth : upTo(MAX_RTT_DEPTH + 1);
-  return Rtt(depth, rtt.heapType);
-}
-
 Type TranslateToFuzzReader::getSubType(Type type) {
   if (type.isTuple()) {
     std::vector<Type> types;
@@ -3056,8 +3218,6 @@ Type TranslateToFuzzReader::getSubType(Type type) {
     auto heapType = getSubType(type.getHeapType());
     auto nullability = getSubType(type.getNullability());
     return Type(heapType, nullability);
-  } else if (type.isRtt()) {
-    return Type(getSubType(type.getRtt()));
   } else {
     // This is an MVP type without subtypes.
     assert(type.isBasic());
diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp
index 2e96087..e93aae5 100644
--- a/src/tools/fuzzing/heap-types.cpp
+++ b/src/tools/fuzzing/heap-types.cpp
@@ -34,10 +34,6 @@ struct HeapTypeGeneratorImpl {
   // Map the HeapTypes we are building to their indices in the builder.
   std::unordered_map<HeapType, Index> typeIndices;
 
-  // Abstract over all the types that may be assigned to a builder slot.
-  using Assignable =
-    std::variant<HeapType::BasicHeapType, Signature, Struct, Array>;
-
   // Top-level kinds, chosen before the types are actually constructed. This
   // allows us to choose HeapTypes that we know will be subtypes of data or func
   // before we actually generate the types.
@@ -73,7 +69,7 @@ struct HeapTypeGeneratorImpl {
       typeIndices.insert({builder[i], i});
       // Everything is a subtype of itself.
       subtypeIndices[i].push_back(i);
-      if (i < numRoots) {
+      if (i < numRoots || rand.oneIn(2)) {
         // This is a root type with no supertype. Choose a kind for this type.
         typeKinds.emplace_back(generateHeapTypeKind());
       } else {
@@ -152,7 +148,13 @@ struct HeapTypeGeneratorImpl {
   }
 
   HeapType::BasicHeapType generateBasicHeapType() {
+    // Choose bottom types more rarely.
+    if (rand.oneIn(16)) {
+      return rand.pick(HeapType::noext, HeapType::nofunc, HeapType::none);
+    }
+    // TODO: strings and array
     return rand.pick(HeapType::func,
+                     HeapType::ext,
                      HeapType::any,
                      HeapType::eq,
                      HeapType::i31,
@@ -163,13 +165,7 @@ struct HeapTypeGeneratorImpl {
     return rand.pick(
       Random::FeatureOptions<Type::BasicType>{}
         .add(FeatureSet::MVP, Type::i32, Type::i64, Type::f32, Type::f64)
-        .add(FeatureSet::SIMD, Type::v128)
-        .add(FeatureSet::ReferenceTypes | FeatureSet::GC,
-             Type::funcref,
-             Type::anyref,
-             Type::eqref,
-             Type::i31ref,
-             Type::dataref));
+        .add(FeatureSet::SIMD, Type::v128));
   }
 
   HeapType generateHeapType() {
@@ -187,20 +183,12 @@ struct HeapTypeGeneratorImpl {
     return builder.getTempRefType(heapType, nullability);
   }
 
-  Type generateRttType() {
-    auto heapType = generateHeapType();
-    auto depth = rand.oneIn(2) ? Rtt::NoDepth : rand.upTo(MAX_RTT_DEPTH);
-    return builder.getTempRttType(Rtt(depth, heapType));
-  }
-
   Type generateSingleType() {
-    switch (rand.upTo(3)) {
+    switch (rand.upTo(2)) {
       case 0:
         return generateBasicType();
       case 1:
         return generateRefType();
-      case 2:
-        return generateRttType();
     }
     WASM_UNREACHABLE("unexpected");
   }
@@ -251,63 +239,6 @@ struct HeapTypeGeneratorImpl {
 
   Array generateArray() { return {generateField()}; }
 
-  Assignable generateSubData() {
-    switch (rand.upTo(2)) {
-      case 0:
-        return generateStruct();
-      case 1:
-        return generateArray();
-    }
-    WASM_UNREACHABLE("unexpected index");
-  }
-
-  Assignable generateSubEq() {
-    switch (rand.upTo(3)) {
-      case 0:
-        return HeapType::i31;
-      case 1:
-        return HeapType::data;
-      case 2:
-        return generateSubData();
-    }
-    WASM_UNREACHABLE("unexpected index");
-  }
-
-  Assignable generateSubAny() {
-    switch (rand.upTo(4)) {
-      case 0:
-        return HeapType::eq;
-      case 1:
-        return HeapType::func;
-      case 2:
-        return generateSubEq();
-      case 3:
-        return generateSignature();
-    }
-    WASM_UNREACHABLE("unexpected index");
-  }
-
-  Assignable generateSubBasic(HeapType::BasicHeapType type) {
-    if (rand.oneIn(2)) {
-      return type;
-    } else {
-      switch (type) {
-        case HeapType::i31:
-          // No other subtypes.
-          return type;
-        case HeapType::func:
-          return generateSignature();
-        case HeapType::any:
-          return generateSubAny();
-        case HeapType::eq:
-          return generateSubEq();
-        case HeapType::data:
-          return generateSubData();
-      }
-      WASM_UNREACHABLE("unexpected index");
-    }
-  }
-
   template<typename Kind> std::optional<HeapType> pickKind() {
     std::vector<Index> candidateIndices;
     // Iterate through the top level kinds, finding matches for `Kind`. Since we
@@ -328,6 +259,8 @@ struct HeapTypeGeneratorImpl {
   HeapType pickSubFunc() {
     if (auto type = pickKind<SignatureKind>()) {
       return *type;
+    } else if (rand.oneIn(2)) {
+      return HeapType::nofunc;
     } else {
       return HeapType::func;
     }
@@ -336,6 +269,8 @@ struct HeapTypeGeneratorImpl {
   HeapType pickSubData() {
     if (auto type = pickKind<DataKind>()) {
       return *type;
+    } else if (rand.oneIn(2)) {
+      return HeapType::none;
     } else {
       return HeapType::data;
     }
@@ -350,14 +285,10 @@ struct HeapTypeGeneratorImpl {
   }
 
   HeapType pickSubAny() {
-    switch (rand.upTo(4)) {
+    switch (rand.upTo(2)) {
       case 0:
-        return HeapType::func;
-      case 1:
         return HeapType::eq;
-      case 2:
-        return pickSubFunc();
-      case 3:
+      case 1:
         return pickSubEq();
     }
     WASM_UNREACHABLE("unexpected index");
@@ -370,7 +301,7 @@ struct HeapTypeGeneratorImpl {
       // can only choose those defined before the end of the current recursion
       // group.
       std::vector<Index> candidateIndices;
-      for (auto i : subtypeIndices[typeIndices[type]]) {
+      for (auto i : subtypeIndices[it->second]) {
         if (i < recGroupEnds[index]) {
           candidateIndices.push_back(i);
         }
@@ -379,7 +310,12 @@ struct HeapTypeGeneratorImpl {
     } else {
       // This is not a constructed type, so it must be a basic type.
       assert(type.isBasic());
+      if (rand.oneIn(8)) {
+        return type.getBottom();
+      }
       switch (type.getBasic()) {
+        case HeapType::ext:
+          return HeapType::ext;
         case HeapType::func:
           return pickSubFunc();
         case HeapType::any:
@@ -390,6 +326,16 @@ struct HeapTypeGeneratorImpl {
           return HeapType::i31;
         case HeapType::data:
           return pickSubData();
+        case HeapType::array:
+          WASM_UNREACHABLE("TODO: fuzz array");
+        case HeapType::string:
+        case HeapType::stringview_wtf8:
+        case HeapType::stringview_wtf16:
+        case HeapType::stringview_iter:
+        case HeapType::none:
+        case HeapType::noext:
+        case HeapType::nofunc:
+          return type;
       }
       WASM_UNREACHABLE("unexpected kind");
     }
@@ -408,20 +354,10 @@ struct HeapTypeGeneratorImpl {
     return {pickSubHeapType(super.type), nullability};
   }
 
-  Rtt generateSubRtt(Rtt super) {
-    auto depth = super.hasDepth()
-                   ? super.depth
-                   : rand.oneIn(2) ? Rtt::NoDepth : rand.upTo(MAX_RTT_DEPTH);
-    return {depth, super.heapType};
-  }
-
   Type generateSubtype(Type type) {
     if (type.isRef()) {
       auto ref = generateSubRef({type.getHeapType(), type.getNullability()});
       return builder.getTempRefType(ref.type, ref.nullability);
-    } else if (type.isRtt()) {
-      auto rtt = generateSubRtt(type.getRtt());
-      return builder.getTempRttType(rtt);
     } else if (type.isBasic()) {
       // Non-reference basic types do not have subtypes.
       return type;
@@ -484,6 +420,17 @@ struct HeapTypeGeneratorImpl {
   }
 
   HeapTypeKind getSubKind(HeapTypeKind super) {
+    if (rand.oneIn(16)) {
+      // Occasionally go directly to the bottom type.
+      if (auto* basic = std::get_if<BasicKind>(&super)) {
+        return HeapType(*basic).getBottom();
+      } else if (std::get_if<SignatureKind>(&super)) {
+        return HeapType::nofunc;
+      } else if (std::get_if<DataKind>(&super)) {
+        return HeapType::none;
+      }
+      WASM_UNREACHABLE("unexpected kind");
+    }
     if (auto* basic = std::get_if<BasicKind>(&super)) {
       if (rand.oneIn(8)) {
         return super;
@@ -491,18 +438,43 @@ struct HeapTypeGeneratorImpl {
       switch (*basic) {
         case HeapType::func:
           return SignatureKind{};
+        case HeapType::ext:
         case HeapType::i31:
           return super;
         case HeapType::any:
-          return generateHeapTypeKind();
+          if (rand.oneIn(4)) {
+            switch (rand.upTo(3)) {
+              case 0:
+                return HeapType::eq;
+              case 1:
+                return HeapType::i31;
+              case 2:
+                return HeapType::data;
+            }
+          }
+          return DataKind{};
         case HeapType::eq:
           if (rand.oneIn(4)) {
-            return HeapType::i31;
-          } else {
-            return DataKind{};
+            switch (rand.upTo(2)) {
+              case 0:
+                return HeapType::i31;
+              case 1:
+                return HeapType::data;
+            }
           }
+          return DataKind{};
         case HeapType::data:
           return DataKind{};
+        case HeapType::array:
+          WASM_UNREACHABLE("TODO: fuzz array");
+        case HeapType::string:
+        case HeapType::stringview_wtf8:
+        case HeapType::stringview_wtf16:
+        case HeapType::stringview_iter:
+        case HeapType::none:
+        case HeapType::noext:
+        case HeapType::nofunc:
+          return super;
       }
       WASM_UNREACHABLE("unexpected kind");
     } else {
diff --git a/src/tools/fuzzing/parameters.h b/src/tools/fuzzing/parameters.h
index 6618ce6..1ba7b06 100644
--- a/src/tools/fuzzing/parameters.h
+++ b/src/tools/fuzzing/parameters.h
@@ -38,9 +38,6 @@ constexpr int MAX_TUPLE_SIZE = 6;
 // The maximum number of struct fields.
 static const int MAX_STRUCT_SIZE = 6;
 
-// The maximum rtt depth.
-constexpr int MAX_RTT_DEPTH = 3;
-
 // The number of nontrivial heap types to generate.
 constexpr int MIN_HEAPTYPES = 4;
 constexpr int MAX_HEAPTYPES = 20;
diff --git a/src/tools/js-wrapper.h b/src/tools/js-wrapper.h
index b93948e..85bc3d7 100644
--- a/src/tools/js-wrapper.h
+++ b/src/tools/js-wrapper.h
@@ -94,15 +94,15 @@ inline std::string generateJSWrapper(Module& wasm) {
     ret += "if (instance.exports.hangLimitInitializer) "
            "instance.exports.hangLimitInitializer();\n";
     ret += "try {\n";
-    ret += std::string("  console.log('[fuzz-exec] calling ") + exp->name.str +
-           "');\n";
+    ret += std::string("  console.log('[fuzz-exec] calling ") +
+           exp->name.toString() + "');\n";
     if (func->getResults() != Type::none) {
       ret += std::string("  console.log('[fuzz-exec] note result: ") +
-             exp->name.str + " => ' + literal(";
+             exp->name.toString() + " => ' + literal(";
     } else {
       ret += "  ";
     }
-    ret += std::string("instance.exports.") + exp->name.str + "(";
+    ret += std::string("instance.exports.") + exp->name.toString() + "(";
     bool first = true;
     for (auto param : func->getParams()) {
       // zeros in arguments TODO more?
diff --git a/src/tools/spec-wrapper.h b/src/tools/spec-wrapper.h
index 43c0e93..95ead47 100644
--- a/src/tools/spec-wrapper.h
+++ b/src/tools/spec-wrapper.h
@@ -32,7 +32,7 @@ inline std::string generateSpecWrapper(Module& wasm) {
       continue; // something exported other than a function
     }
     ret += std::string("(invoke \"hangLimitInitializer\") (invoke \"") +
-           exp->name.str + "\" ";
+           exp->name.toString() + "\" ";
     for (const auto& param : func->getParams()) {
       // zeros in arguments TODO more?
       TODO_SINGLE_COMPOUND(param);
@@ -52,19 +52,6 @@ inline std::string generateSpecWrapper(Module& wasm) {
         case Type::v128:
           ret += "(v128.const i32x4 0 0 0 0)";
           break;
-        case Type::funcref:
-          ret += "(ref.null func)";
-          break;
-        case Type::anyref:
-          ret += "(ref.null any)";
-          break;
-        case Type::eqref:
-          ret += "(ref.null eq)";
-          break;
-        case Type::i31ref:
-          WASM_UNREACHABLE("TODO: i31ref");
-        case Type::dataref:
-          WASM_UNREACHABLE("TODO: dataref");
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h
index bc948fc..eb5212c 100644
--- a/src/tools/tool-options.h
+++ b/src/tools/tool-options.h
@@ -89,11 +89,31 @@ struct ToolOptions : public Options {
       .addFeature(FeatureSet::Multivalue, "multivalue functions")
       .addFeature(FeatureSet::GC, "garbage collection")
       .addFeature(FeatureSet::Memory64, "memory64")
-      .addFeature(FeatureSet::TypedFunctionReferences,
-                  "typed function references")
       .addFeature(FeatureSet::GCNNLocals, "GC non-null locals")
       .addFeature(FeatureSet::RelaxedSIMD, "relaxed SIMD")
       .addFeature(FeatureSet::ExtendedConst, "extended const expressions")
+      .addFeature(FeatureSet::Strings, "strings")
+      .addFeature(FeatureSet::MultiMemories, "multi-memories")
+      .add("--enable-typed-function-references",
+           "",
+           "Deprecated compatibility flag",
+           ToolOptionsCategory,
+           Options::Arguments::Zero,
+           [](Options* o, const std::string& argument) {
+             std::cerr
+               << "Warning: Typed function references have been made part of "
+                  "GC and --enable-typed-function-references is deprecated\n";
+           })
+      .add("--disable-typed-function-references",
+           "",
+           "Deprecated compatibility flag",
+           ToolOptionsCategory,
+           Options::Arguments::Zero,
+           [](Options* o, const std::string& argument) {
+             std::cerr
+               << "Warning: Typed function references have been made part of "
+                  "GC and --disable-typed-function-references is deprecated\n";
+           })
       .add("--no-validation",
            "-n",
            "Disables validation, assumes inputs are correct",
@@ -176,11 +196,17 @@ struct ToolOptions : public Options {
   void applyFeatures(Module& module) const {
     module.features.enable(enabledFeatures);
     module.features.disable(disabledFeatures);
+    // Non-default type systems only make sense with GC enabled. TODO: Error on
+    // non-GC equirecursive types as well once we make isorecursive the default
+    // if we don't remove equirecursive types entirely.
+    if (!module.features.hasGC() && getTypeSystem() == TypeSystem::Nominal) {
+      Fatal() << "Nominal typing is only allowed when GC is enabled";
+    }
   }
 
 private:
-  FeatureSet enabledFeatures = FeatureSet::MVP;
-  FeatureSet disabledFeatures = FeatureSet::MVP;
+  FeatureSet enabledFeatures = FeatureSet::Default;
+  FeatureSet disabledFeatures = FeatureSet::None;
 };
 
 } // namespace wasm
diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp
index 3826922..cc4f6fd 100644
--- a/src/tools/wasm-as.cpp
+++ b/src/tools/wasm-as.cpp
@@ -27,7 +27,6 @@
 #include "tool-options.h"
 #include "tool-utils.h"
 
-using namespace cashew;
 using namespace wasm;
 
 int main(int argc, const char* argv[]) {
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
index 59e54d4..40abe2f 100644
--- a/src/tools/wasm-ctor-eval.cpp
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -67,7 +67,8 @@ public:
     auto* global = wasm.getGlobal(curr->name);
     if (global->imported()) {
       throw FailToEvalException(std::string("read from imported global ") +
-                                global->module.str + "." + global->base.str);
+                                global->module.toString() + "." +
+                                global->base.toString());
     }
 
     return ModuleRunnerBase<EvallingModuleRunner>::visitGlobalGet(curr);
@@ -122,14 +123,11 @@ std::unique_ptr<Module> buildEnvModule(Module& wasm) {
   // create an exported memory with the same initial and max size
   ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) {
     if (memory->module == env->name) {
-      env->memory.name = wasm.memory.name;
-      env->memory.exists = true;
-      env->memory.initial = memory->initial;
-      env->memory.max = memory->max;
-      env->memory.shared = memory->shared;
-      env->memory.indexType = memory->indexType;
+      auto* copied = ModuleUtils::copyMemory(memory, *env);
+      copied->module = Name();
+      copied->base = Name();
       env->addExport(Builder(*env).makeExport(
-        wasm.memory.base, wasm.memory.name, ExternalKind::Memory));
+        memory->base, copied->name, ExternalKind::Memory));
     }
   });
 
@@ -147,7 +145,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
   std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances;
 
   // A representation of the contents of wasm memory as we execute.
-  std::vector<char> memory;
+  std::unordered_map<Name, std::vector<char>> memories;
 
   CtorEvalExternalInterface(
     std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances_ =
@@ -160,8 +158,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
   void applyToModule() {
     clearApplyState();
 
-    // If nothing was ever written to memory then there is nothing to update.
-    if (!memory.empty()) {
+    // If nothing was ever written to memories then there is nothing to update.
+    if (!memories.empty()) {
       applyMemoryToModule();
     }
 
@@ -171,6 +169,12 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
   void init(Module& wasm_, EvallingModuleRunner& instance_) override {
     wasm = &wasm_;
     instance = &instance_;
+    for (auto& memory : wasm->memories) {
+      if (!memory->imported()) {
+        std::vector<char> data;
+        memories[memory->name] = data;
+      }
+    }
   }
 
   void importGlobals(GlobalValueSet& globals, Module& wasm_) override {
@@ -181,13 +185,14 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
         auto* globalExport = inst->wasm.getExportOrNull(global->base);
         if (!globalExport) {
           throw FailToEvalException(std::string("importGlobals: ") +
-                                    global->module.str + "." +
-                                    global->base.str);
+                                    global->module.toString() + "." +
+                                    global->base.toString());
         }
         globals[global->name] = inst->globals[globalExport->value];
       } else {
         throw FailToEvalException(std::string("importGlobals: ") +
-                                  global->module.str + "." + global->base.str);
+                                  global->module.toString() + "." +
+                                  global->base.toString());
       }
     });
   }
@@ -204,7 +209,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
           }
 
           // Write out a count of i32(0) and return __WASI_ERRNO_SUCCESS (0).
-          store32(arguments[0].geti32(), 0);
+          store32(arguments[0].geti32(), 0, wasm->memories[0]->name);
           return {Literal(int32_t(0))};
         }
 
@@ -225,7 +230,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
           }
 
           // Write out an argc of i32(0) and return a __WASI_ERRNO_SUCCESS (0).
-          store32(arguments[0].geti32(), 0);
+          store32(arguments[0].geti32(), 0, wasm->memories[0]->name);
           return {Literal(int32_t(0))};
         }
 
@@ -252,8 +257,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
       extra = RECOMMENDATION "consider --ignore-external-input";
     }
     throw FailToEvalException(std::string("call import: ") +
-                              import->module.str + "." + import->base.str +
-                              extra);
+                              import->module.toString() + "." +
+                              import->base.toString() + extra);
   }
 
   // We assume the table is not modified FIXME
@@ -315,13 +320,14 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
     auto* func = wasm->getFunction(targetFunc);
     if (func->type != sig) {
       throw FailToEvalException(std::string("callTable signature mismatch: ") +
-                                targetFunc.str);
+                                targetFunc.toString());
     }
     if (!func->imported()) {
       return instance.callFunctionInternal(targetFunc, arguments);
     } else {
       throw FailToEvalException(
-        std::string("callTable on imported function: ") + targetFunc.str);
+        std::string("callTable on imported function: ") +
+        targetFunc.toString());
     }
   }
 
@@ -336,29 +342,47 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
   // called during initialization
   void tableStore(Name tableName, Index index, const Literal& value) override {}
 
-  int8_t load8s(Address addr) override { return doLoad<int8_t>(addr); }
-  uint8_t load8u(Address addr) override { return doLoad<uint8_t>(addr); }
-  int16_t load16s(Address addr) override { return doLoad<int16_t>(addr); }
-  uint16_t load16u(Address addr) override { return doLoad<uint16_t>(addr); }
-  int32_t load32s(Address addr) override { return doLoad<int32_t>(addr); }
-  uint32_t load32u(Address addr) override { return doLoad<uint32_t>(addr); }
-  int64_t load64s(Address addr) override { return doLoad<int64_t>(addr); }
-  uint64_t load64u(Address addr) override { return doLoad<uint64_t>(addr); }
+  int8_t load8s(Address addr, Name memoryName) override {
+    return doLoad<int8_t>(addr, memoryName);
+  }
+  uint8_t load8u(Address addr, Name memoryName) override {
+    return doLoad<uint8_t>(addr, memoryName);
+  }
+  int16_t load16s(Address addr, Name memoryName) override {
+    return doLoad<int16_t>(addr, memoryName);
+  }
+  uint16_t load16u(Address addr, Name memoryName) override {
+    return doLoad<uint16_t>(addr, memoryName);
+  }
+  int32_t load32s(Address addr, Name memoryName) override {
+    return doLoad<int32_t>(addr, memoryName);
+  }
+  uint32_t load32u(Address addr, Name memoryName) override {
+    return doLoad<uint32_t>(addr, memoryName);
+  }
+  int64_t load64s(Address addr, Name memoryName) override {
+    return doLoad<int64_t>(addr, memoryName);
+  }
+  uint64_t load64u(Address addr, Name memoryName) override {
+    return doLoad<uint64_t>(addr, memoryName);
+  }
 
-  void store8(Address addr, int8_t value) override {
-    doStore<int8_t>(addr, value);
+  void store8(Address addr, int8_t value, Name memoryName) override {
+    doStore<int8_t>(addr, value, memoryName);
   }
-  void store16(Address addr, int16_t value) override {
-    doStore<int16_t>(addr, value);
+  void store16(Address addr, int16_t value, Name memoryName) override {
+    doStore<int16_t>(addr, value, memoryName);
   }
-  void store32(Address addr, int32_t value) override {
-    doStore<int32_t>(addr, value);
+  void store32(Address addr, int32_t value, Name memoryName) override {
+    doStore<int32_t>(addr, value, memoryName);
   }
-  void store64(Address addr, int64_t value) override {
-    doStore<int64_t>(addr, value);
+  void store64(Address addr, int64_t value, Name memoryName) override {
+    doStore<int64_t>(addr, value, memoryName);
   }
 
-  bool growMemory(Address /*oldSize*/, Address /*newSize*/) override {
+  bool growMemory(Name memoryName,
+                  Address /*oldSize*/,
+                  Address /*newSize*/) override {
     throw FailToEvalException("grow memory");
   }
 
@@ -385,8 +409,10 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
 
 private:
   // TODO: handle unaligned too, see shell-interface
-
-  template<typename T> T* getMemory(Address address) {
+  template<typename T> T* getMemory(Address address, Name memoryName) {
+    auto it = memories.find(memoryName);
+    assert(it != memories.end());
+    auto& memory = it->second;
     // resize the memory buffer as needed.
     auto max = address + sizeof(T);
     if (max > memory.size()) {
@@ -395,15 +421,15 @@ private:
     return (T*)(&memory[address]);
   }
 
-  template<typename T> void doStore(Address address, T value) {
+  template<typename T> void doStore(Address address, T value, Name memoryName) {
     // do a memcpy to avoid undefined behavior if unaligned
-    memcpy(getMemory<T>(address), &value, sizeof(T));
+    memcpy(getMemory<T>(address, memoryName), &value, sizeof(T));
   }
 
-  template<typename T> T doLoad(Address address) {
+  template<typename T> T doLoad(Address address, Name memoryName) {
     // do a memcpy to avoid undefined behavior if unaligned
     T ret;
-    memcpy(&ret, getMemory<T>(address), sizeof(T));
+    memcpy(&ret, getMemory<T>(address, memoryName), sizeof(T));
     return ret;
   }
 
@@ -426,18 +452,20 @@ private:
   void applyMemoryToModule() {
     // Memory must have already been flattened into the standard form: one
     // segment at offset 0, or none.
-    if (wasm->memory.segments.empty()) {
+    if (wasm->dataSegments.empty()) {
       Builder builder(*wasm);
-      std::vector<char> empty;
-      wasm->memory.segments.push_back(
-        Memory::Segment(builder.makeConst(int32_t(0)), empty));
+      auto curr = builder.makeDataSegment();
+      curr->offset = builder.makeConst(int32_t(0));
+      curr->setName(Name::fromInt(0), false);
+      curr->memory = wasm->memories[0]->name;
+      wasm->addDataSegment(std::move(curr));
     }
-    auto& segment = wasm->memory.segments[0];
-    assert(segment.offset->cast<Const>()->value.getInteger() == 0);
+    auto& segment = wasm->dataSegments[0];
+    assert(segment->offset->cast<Const>()->value.getInteger() == 0);
 
     // Copy the current memory contents after execution into the Module's
     // memory.
-    segment.data = memory;
+    segment->data = memories[wasm->memories[0]->name];
   }
 
   // Serializing GC data requires more work than linear memory, because
@@ -528,10 +556,7 @@ public:
 
     // This is GC data, which we must handle in a more careful way.
     auto* data = value.getGCData().get();
-    if (!data) {
-      // This is a null, so simply emit one.
-      return builder.makeRefNull(value.type);
-    }
+    assert(data);
 
     // There was actual GC data allocated here.
     auto type = value.type;
@@ -557,7 +582,6 @@ public:
 
       Expression* init;
       auto heapType = type.getHeapType();
-      // TODO: handle rtts if we need them
       if (heapType.isStruct()) {
         init = builder.makeStructNew(heapType, args);
       } else if (heapType.isArray()) {
diff --git a/src/tools/wasm-dis.cpp b/src/tools/wasm-dis.cpp
index b4003b2..e8ca715 100644
--- a/src/tools/wasm-dis.cpp
+++ b/src/tools/wasm-dis.cpp
@@ -24,7 +24,6 @@
 
 #include "tool-options.h"
 
-using namespace cashew;
 using namespace wasm;
 
 int main(int argc, const char* argv[]) {
diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp
index 8fdc203..0f63e81 100644
--- a/src/tools/wasm-emscripten-finalize.cpp
+++ b/src/tools/wasm-emscripten-finalize.cpp
@@ -34,7 +34,6 @@
 
 #define DEBUG_TYPE "emscripten"
 
-using namespace cashew;
 using namespace wasm;
 
 int main(int argc, const char* argv[]) {
@@ -47,7 +46,6 @@ int main(int argc, const char* argv[]) {
   std::string outputSourceMapUrl;
   std::string dataSegmentFile;
   bool emitBinary = true;
-  bool emitMetadata = true;
   bool debugInfo = false;
   bool DWARF = false;
   bool sideModule = false;
@@ -95,13 +93,6 @@ int main(int argc, const char* argv[]) {
          WasmEmscriptenFinalizeOption,
          Options::Arguments::Zero,
          [&emitBinary](Options*, const std::string&) { emitBinary = false; })
-    .add(
-      "--no-emit-metadata",
-      "-n",
-      "Skip the writing to emscripten metadata JSON to stdout.",
-      WasmEmscriptenFinalizeOption,
-      Options::Arguments::Zero,
-      [&emitMetadata](Options*, const std::string&) { emitMetadata = false; })
     .add("--global-base",
          "",
          "The address at which static globals were placed",
@@ -228,9 +219,8 @@ int main(int argc, const char* argv[]) {
   options.applyFeatures(wasm);
   ModuleReader reader;
   // If we are not writing output then we definitely don't need to read debug
-  // info, as it does not affect the metadata we will emit. (However, if we
-  // emit output then definitely load the names section so that we roundtrip
-  // names properly.)
+  // info. However, if we emit output then definitely load the names section so
+  // that we roundtrip names properly.
   reader.setDebugInfo(writeOutput);
   reader.setDWARF(DWARF && writeOutput);
   if (!writeOutput) {
@@ -266,12 +256,6 @@ int main(int argc, const char* argv[]) {
   generator.onlyI64DynCalls = onlyI64DynCalls;
   generator.noDynCalls = noDynCalls;
 
-  if (!standaloneWasm) {
-    // This is also not needed in standalone mode since standalone mode uses
-    // crt1.c to invoke the main and is aware of __main_argc_argv mangling.
-    generator.renameMainArgcArgv();
-  }
-
   PassRunner passRunner(&wasm, options.passOptions);
   passRunner.setDebug(options.debug);
   passRunner.setDebugInfo(debugInfo);
@@ -302,7 +286,6 @@ int main(int argc, const char* argv[]) {
                             : ABI::LegalizationLevel::Minimal));
   }
 
-  // Strip target features section (its information is in the metadata)
   passRunner.add("strip-target-features");
 
   // If DWARF is unused, strip it out. This avoids us keeping it alive
@@ -313,15 +296,7 @@ int main(int argc, const char* argv[]) {
 
   passRunner.run();
 
-  BYN_TRACE("generated metadata\n");
-  // Substantial changes to the wasm are done, enough to create the metadata.
-  std::string metadata;
-  if (emitMetadata) {
-    metadata = generator.generateEmscriptenMetadata();
-  }
-
-  // Finally, separate out data segments if relevant (they may have been needed
-  // for metadata).
+  // Finally, separate out data segments if relevant
   if (!dataSegmentFile.empty()) {
     Output memInitFile(dataSegmentFile, Flags::Binary);
     if (globalBase == INVALID_BASE) {
@@ -344,16 +319,6 @@ int main(int argc, const char* argv[]) {
       writer.setSourceMapUrl(outputSourceMapUrl);
     }
     writer.write(wasm, output);
-    if (emitMetadata && !emitBinary) {
-      output << "(;\n";
-      output << "--BEGIN METADATA --\n" << metadata << "-- END METADATA --\n";
-      output << ";)\n";
-    }
-  }
-  // If we emit text then we emitted the metadata together with that text
-  // earlier. Otherwise emit it to stdout.
-  if (emitMetadata && emitBinary) {
-    std::cout << metadata;
   }
   return 0;
 }
diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp
index d254063..bbf4967 100644
--- a/src/tools/wasm-fuzz-types.cpp
+++ b/src/tools/wasm-fuzz-types.cpp
@@ -152,46 +152,72 @@ void Fuzzer::checkLUBs() const {
       HeapType a = types[i], b = types[j];
       // Check that their LUB is stable when calculated multiple times and in
       // reverse order.
-      HeapType lub = HeapType::getLeastUpperBound(a, b);
-      if (lub != HeapType::getLeastUpperBound(b, a) ||
-          lub != HeapType::getLeastUpperBound(a, b)) {
-        Fatal() << "Could not calculate a stable LUB of HeapTypes " << i
-                << " and " << j << "!\n"
-                << i << ": " << a << "\n"
-                << j << ": " << b << "\n";
-      }
-      // Check that each type is a subtype of the LUB.
-      if (!HeapType::isSubType(a, lub)) {
-        Fatal() << "HeapType " << i
-                << " is not a subtype of its LUB with HeapType " << j << "!\n"
-                << i << ": " << a << "\n"
-                << j << ": " << b << "\n"
-                << "lub: " << lub << "\n";
-      }
-      if (!HeapType::isSubType(b, lub)) {
-        Fatal() << "HeapType " << j
-                << " is not a subtype of its LUB with HeapType " << i << "!\n"
-                << i << ": " << a << "\n"
-                << j << ": " << b << "\n"
-                << "lub: " << lub << "\n";
-      }
-      // Check that the LUB of each type and the original LUB is still the
-      // original LUB.
-      if (lub != HeapType::getLeastUpperBound(a, lub)) {
-        Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
-                << " should be the LUB of itself and HeapType " << i
-                << ", but it is not!\n"
-                << i << ": " << a << "\n"
-                << j << ": " << b << "\n"
-                << "lub: " << lub << "\n";
-      }
-      if (lub != HeapType::getLeastUpperBound(lub, b)) {
-        Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
-                << " should be the LUB of itself and HeapType " << j
-                << ", but it is not!\n"
-                << i << ": " << a << "\n"
-                << j << ": " << b << "\n"
-                << "lub: " << lub << "\n";
+      auto lub = HeapType::getLeastUpperBound(a, b);
+      if (lub) {
+        if (lub != HeapType::getLeastUpperBound(b, a) ||
+            lub != HeapType::getLeastUpperBound(a, b)) {
+          Fatal() << "Could not calculate a stable LUB of HeapTypes " << i
+                  << " and " << j << "!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n";
+        }
+        // Check that each type is a subtype of the LUB.
+        if (!HeapType::isSubType(a, *lub)) {
+          Fatal() << "HeapType " << i
+                  << " is not a subtype of its LUB with HeapType " << j << "!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n"
+                  << "lub: " << *lub << "\n";
+        }
+        if (!HeapType::isSubType(b, *lub)) {
+          Fatal() << "HeapType " << j
+                  << " is not a subtype of its LUB with HeapType " << i << "!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n"
+                  << "lub: " << *lub << "\n";
+        }
+        // Check that the LUB of each type and the original LUB is still the
+        // original LUB.
+        if (lub != HeapType::getLeastUpperBound(a, *lub)) {
+          Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
+                  << " should be the LUB of itself and HeapType " << i
+                  << ", but it is not!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n"
+                  << "lub: " << *lub << "\n";
+        }
+        if (lub != HeapType::getLeastUpperBound(*lub, b)) {
+          Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
+                  << " should be the LUB of itself and HeapType " << j
+                  << ", but it is not!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n"
+                  << "lub: " << *lub << "\n";
+        }
+      } else {
+        // No LUB. Check that this is symmetrical.
+        if (auto lub2 = HeapType::getLeastUpperBound(b, a)) {
+          Fatal() << "There is no LUB of HeapType " << i << " and HeapType "
+                  << j << ", but there is a LUB in the reverse direction!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n"
+                  << "lub: " << *lub2 << "\n";
+        }
+        // There also shouldn't be a subtype relation in this case.
+        if (HeapType::isSubType(a, b)) {
+          Fatal() << "There is no LUB of HeapType " << i << " and HeapType "
+                  << j << ", but HeapType " << i << " is a subtype of HeapType "
+                  << j << "!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n";
+        }
+        if (HeapType::isSubType(b, a)) {
+          Fatal() << "There is no LUB of HeapType " << i << " and HeapType "
+                  << j << ", but HeapType " << j << " is a subtype of HeapType "
+                  << i << "!\n"
+                  << i << ": " << a << "\n"
+                  << j << ": " << b << "\n";
+        }
       }
     }
   }
@@ -386,24 +412,6 @@ void Fuzzer::checkCanonicalization() {
       }
     }
 
-    CopiedType getRtt(Type old) {
-      auto copied = getChildHeapType(old.getHeapType());
-      auto rtt = old.getRtt();
-      rtt.heapType = copied.get();
-      if (copied.getNew()) {
-        // The child is temporary, so we must put it in a temporary type.
-        return {NewType{builder.getTempRttType(rtt)}};
-      } else {
-        // The child is canonical, so we can either put it in a temporary type
-        // or use the canonical type.
-        if (rand.oneIn(2)) {
-          return {NewType{builder.getTempRttType(rtt)}};
-        } else {
-          return {OldType{Type(rtt)}};
-        }
-      }
-    }
-
     CopiedType getRef(Type old) {
       auto copied = getChildHeapType(old.getHeapType());
       auto type = copied.get();
@@ -425,8 +433,6 @@ void Fuzzer::checkCanonicalization() {
     CopiedType getType(Type old) {
       if (old.isTuple()) {
         return getTuple(old);
-      } else if (old.isRtt()) {
-        return getRtt(old);
       } else if (old.isRef()) {
         return getRef(old);
       } else {
diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp
index 4b12446..029d4aa 100644
--- a/src/tools/wasm-metadce.cpp
+++ b/src/tools/wasm-metadce.cpp
@@ -69,7 +69,7 @@ struct MetaDCEGraph {
   // be imported twice, for example. So we don't map a DCE node to an Import,
   // but rather the module.base pair ("id") for the import.
   // TODO: implement this in a safer way, not a string with a magic separator
-  typedef Name ImportId;
+  using ImportId = Name;
 
   ImportId getImportId(Name module, Name base) {
     if (module == "GOT.func" || module == "GOT.mem") {
@@ -115,19 +115,19 @@ struct MetaDCEGraph {
     // does not alter parent state, just adds to things pointed by it,
     // independently (each thread will add for one function, etc.)
     ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) {
-      auto dceName = getName("func", func->name.str);
+      auto dceName = getName("func", func->name.toString());
       DCENodeToFunction[dceName] = func->name;
       functionToDCENode[func->name] = dceName;
       nodes[dceName] = DCENode(dceName);
     });
     ModuleUtils::iterDefinedGlobals(wasm, [&](Global* global) {
-      auto dceName = getName("global", global->name.str);
+      auto dceName = getName("global", global->name.toString());
       DCENodeToGlobal[dceName] = global->name;
       globalToDCENode[global->name] = dceName;
       nodes[dceName] = DCENode(dceName);
     });
     ModuleUtils::iterDefinedTags(wasm, [&](Tag* tag) {
-      auto dceName = getName("tag", tag->name.str);
+      auto dceName = getName("tag", tag->name.toString());
       DCENodeToTag[dceName] = tag->name;
       tagToDCENode[tag->name] = dceName;
       nodes[dceName] = DCENode(dceName);
@@ -137,27 +137,27 @@ struct MetaDCEGraph {
     ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
       auto id = getImportId(import->module, import->base);
       if (importIdToDCENode.find(id) == importIdToDCENode.end()) {
-        auto dceName = getName("importId", import->name.str);
+        auto dceName = getName("importId", import->name.toString());
         importIdToDCENode[id] = dceName;
       }
     });
     ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
       auto id = getImportId(import->module, import->base);
       if (importIdToDCENode.find(id) == importIdToDCENode.end()) {
-        auto dceName = getName("importId", import->name.str);
+        auto dceName = getName("importId", import->name.toString());
         importIdToDCENode[id] = dceName;
       }
     });
     ModuleUtils::iterImportedTags(wasm, [&](Tag* import) {
       auto id = getImportId(import->module, import->base);
       if (importIdToDCENode.find(id) == importIdToDCENode.end()) {
-        auto dceName = getName("importId", import->name.str);
+        auto dceName = getName("importId", import->name.toString());
         importIdToDCENode[id] = dceName;
       }
     });
     for (auto& exp : wasm.exports) {
       if (exportToDCENode.find(exp->name) == exportToDCENode.end()) {
-        auto dceName = getName("export", exp->name.str);
+        auto dceName = getName("export", exp->name.toString());
         DCENodeToExport[dceName] = exp->name;
         exportToDCENode[exp->name] = dceName;
         nodes[dceName] = DCENode(dceName);
@@ -235,11 +235,8 @@ struct MetaDCEGraph {
         });
       rooter.walk(segment->offset);
     });
-    for (auto& segment : wasm.memory.segments) {
-      if (!segment.isPassive) {
-        rooter.walk(segment.offset);
-      }
-    }
+    ModuleUtils::iterActiveDataSegments(
+      wasm, [&](DataSegment* segment) { rooter.walk(segment->offset); });
 
     // A parallel scanner for function bodies
     struct Scanner : public WalkerPass<PostWalker<Scanner>> {
@@ -247,7 +244,9 @@ struct MetaDCEGraph {
 
       Scanner(MetaDCEGraph* parent) : parent(parent) {}
 
-      Scanner* create() override { return new Scanner(parent); }
+      std::unique_ptr<Pass> create() override {
+        return std::make_unique<Scanner>(parent);
+      }
 
       void visitCall(Call* curr) {
         if (!getModule()->getFunction(curr->target)->imported()) {
@@ -369,7 +368,7 @@ public:
     std::set<std::string> unused;
     for (auto& [name, _] : nodes) {
       if (reached.find(name) == reached.end()) {
-        unused.insert(name.str);
+        unused.insert(name.toString());
       }
     }
     for (auto& name : unused) {
diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp
index c026be2..538496e 100644
--- a/src/tools/wasm-opt.cpp
+++ b/src/tools/wasm-opt.cpp
@@ -228,6 +228,12 @@ int main(int argc, const char* argv[]) {
          [&outputSourceMapUrl](Options* o, const std::string& argument) {
            outputSourceMapUrl = argument;
          })
+    .add("--new-wat-parser",
+         "",
+         "Use the experimental new WAT parser",
+         WasmOptOption,
+         Options::Arguments::Zero,
+         [](Options*, const std::string&) { useNewWATParser = true; })
     .add_positional("INFILE",
                     Options::Arguments::One,
                     [](Options* o, const std::string& argument) {
@@ -308,7 +314,7 @@ int main(int argc, const char* argv[]) {
     runner.run();
   }
 
-  ExecutionResults results(options.passOptions);
+  ExecutionResults results;
   if (fuzzExecBefore) {
     results.get(wasm);
   }
diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp
index c6f9be0..eec1e08 100644
--- a/src/tools/wasm-reduce.cpp
+++ b/src/tools/wasm-reduce.cpp
@@ -232,6 +232,7 @@ struct Reducer
   : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<Reducer>>> {
   std::string command, test, working;
   bool binary, deNan, verbose, debugInfo;
+  ToolOptions& toolOptions;
 
   // test is the file we write to that the command will operate on
   // working is the current temporary state, the reduction so far
@@ -241,9 +242,11 @@ struct Reducer
           bool binary,
           bool deNan,
           bool verbose,
-          bool debugInfo)
+          bool debugInfo,
+          ToolOptions& toolOptions)
     : command(command), test(test), working(working), binary(binary),
-      deNan(deNan), verbose(verbose), debugInfo(debugInfo) {}
+      deNan(deNan), verbose(verbose), debugInfo(debugInfo),
+      toolOptions(toolOptions) {}
 
   // runs passes in order to reduce, until we can't reduce any more
   // the criterion here is wasm binary size
@@ -358,12 +361,16 @@ struct Reducer
       std::cerr << '\n';
       Fatal() << "error in parsing working wasm binary";
     }
+
     // If there is no features section, assume we may need them all (without
     // this, a module with no features section but that uses e.g. atomics and
     // bulk memory would not work).
     if (!module->hasFeaturesSection) {
       module->features = FeatureSet::All;
     }
+    // Apply features the user passed on the commandline.
+    toolOptions.applyFeatures(*module);
+
     builder = make_unique<Builder>(*module);
     setModule(module.get());
   }
@@ -481,7 +488,7 @@ struct Reducer
 
   std::string getLocation() {
     if (getFunction()) {
-      return getFunction()->name.str;
+      return getFunction()->name.toString();
     }
     return "(non-function context)";
   }
@@ -583,6 +590,13 @@ struct Reducer
         tryToReplaceCurrent(loop->body);
       }
       return; // nothing more to do
+    } else if (curr->is<Drop>()) {
+      if (curr->type == Type::none) {
+        // We can't improve this: the child has a different type than us. Return
+        // here to avoid reaching the code below that tries to add a drop on
+        // children (which would recreate the current state).
+        return;
+      }
     }
     // Finally, try to replace with a child.
     for (auto* child : ChildIterator(curr)) {
@@ -626,13 +640,9 @@ struct Reducer
               case Type::f64:
                 fixed = builder->makeUnary(TruncSFloat64ToInt32, child);
                 break;
+              // not implemented yet
               case Type::v128:
-              case Type::funcref:
-              case Type::anyref:
-              case Type::eqref:
-              case Type::i31ref:
-              case Type::dataref:
-                continue; // not implemented yet
+                continue;
               case Type::none:
               case Type::unreachable:
                 WASM_UNREACHABLE("unexpected type");
@@ -653,13 +663,9 @@ struct Reducer
               case Type::f64:
                 fixed = builder->makeUnary(TruncSFloat64ToInt64, child);
                 break;
+              // not implemented yet
               case Type::v128:
-              case Type::funcref:
-              case Type::anyref:
-              case Type::eqref:
-              case Type::i31ref:
-              case Type::dataref:
-                continue; // not implemented yet
+                continue;
               case Type::none:
               case Type::unreachable:
                 WASM_UNREACHABLE("unexpected type");
@@ -680,13 +686,9 @@ struct Reducer
               case Type::f64:
                 fixed = builder->makeUnary(DemoteFloat64, child);
                 break;
+              // not implemented yet
               case Type::v128:
-              case Type::funcref:
-              case Type::anyref:
-              case Type::eqref:
-              case Type::i31ref:
-              case Type::dataref:
-                continue; // not implemented yet
+                continue;
               case Type::none:
               case Type::unreachable:
                 WASM_UNREACHABLE("unexpected type");
@@ -707,26 +709,18 @@ struct Reducer
                 break;
               case Type::f64:
                 WASM_UNREACHABLE("unexpected type");
+              // not implemented yet
               case Type::v128:
-              case Type::funcref:
-              case Type::anyref:
-              case Type::eqref:
-              case Type::i31ref:
-              case Type::dataref:
-                continue; // not implemented yet
+                continue;
               case Type::none:
               case Type::unreachable:
                 WASM_UNREACHABLE("unexpected type");
             }
             break;
           }
+          // not implemented yet
           case Type::v128:
-          case Type::funcref:
-          case Type::anyref:
-          case Type::eqref:
-          case Type::i31ref:
-          case Type::dataref:
-            continue; // not implemented yet
+            continue;
           case Type::none:
           case Type::unreachable:
             WASM_UNREACHABLE("unexpected type");
@@ -752,20 +746,14 @@ struct Reducer
 
   // TODO: bisection on segment shrinking?
 
-  void visitMemory(Memory* curr) {
-    std::cerr << "|    try to simplify memory\n";
-
+  void visitDataSegment(DataSegment* curr) {
     // try to reduce to first function. first, shrink segment elements.
     // while we are shrinking successfully, keep going exponentially.
     bool shrank = false;
-    for (auto& segment : curr->segments) {
-      shrank = shrinkByReduction(&segment, 2);
-    }
+    shrank = shrinkByReduction(curr, 2);
     // the "opposite" of shrinking: copy a 'zero' element
-    for (auto& segment : curr->segments) {
-      reduceByZeroing(
-        &segment, 0, [](char item) { return item == 0; }, 2, shrank);
-    }
+    reduceByZeroing(
+      curr, 0, [](char item) { return item == 0; }, 2, shrank);
   }
 
   template<typename T, typename U, typename C>
@@ -831,7 +819,10 @@ struct Reducer
     // First, shrink segment elements.
     bool shrank = false;
     for (auto& segment : module->elementSegments) {
-      shrank = shrank || shrinkByReduction(segment.get(), 1);
+      // Try to shrink all the segments (code in shrinkByReduction will decide
+      // which to actually try to shrink, based on the current factor), and note
+      // if we shrank anything at all (which we'll use later down).
+      shrank = shrinkByReduction(segment.get(), 1) || shrank;
     }
 
     // Second, try to replace elements with a "zero".
@@ -944,6 +935,7 @@ struct Reducer
     // process things here, we may replace the module, so we should never again
     // refer to curr.
     assert(curr == module.get());
+    WASM_UNUSED(curr);
     curr = nullptr;
 
     // Reduction of entire functions at a time is very effective, and we do it
@@ -1150,7 +1142,7 @@ struct Reducer
     }
     // try to replace with a trivial value
     if (curr->type.isNullable()) {
-      RefNull* n = builder->makeRefNull(curr->type);
+      RefNull* n = builder->makeRefNull(curr->type.getHeapType());
       return tryToReplaceCurrent(n);
     }
     if (curr->type.isTuple() && curr->type.isDefaultable()) {
@@ -1302,6 +1294,9 @@ int main(int argc, const char* argv[]) {
   if (getTypeSystem() == TypeSystem::Nominal) {
     extraFlags += " --nominal";
   }
+  if (getTypeSystem() == TypeSystem::Isorecursive) {
+    extraFlags += " --hybrid";
+  }
 
   if (test.size() == 0) {
     Fatal() << "test file not provided\n";
@@ -1407,7 +1402,8 @@ int main(int argc, const char* argv[]) {
   bool stopping = false;
 
   while (1) {
-    Reducer reducer(command, test, working, binary, deNan, verbose, debugInfo);
+    Reducer reducer(
+      command, test, working, binary, deNan, verbose, debugInfo, options);
 
     // run binaryen optimization passes to reduce. passes are fast to run
     // and can often reduce large amounts of code efficiently, as opposed
diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp
index 0de93c6..61b35e6 100644
--- a/src/tools/wasm-shell.cpp
+++ b/src/tools/wasm-shell.cpp
@@ -31,7 +31,6 @@
 #include "wasm-s-parser.h"
 #include "wasm-validator.h"
 
-using namespace cashew;
 using namespace wasm;
 
 Name ASSERT_RETURN("assert_return");
@@ -162,7 +161,7 @@ protected:
     instances[name] = instances[lastModule];
 
     Colors::green(std::cerr);
-    std::cerr << "REGISTER MODULE INSTANCE AS \"" << name.c_str()
+    std::cerr << "REGISTER MODULE INSTANCE AS \"" << name.str
               << "\"  [line: " << s.line << "]\n";
     Colors::normal(std::cerr);
   }
@@ -190,7 +189,7 @@ protected:
       return instance->getExport(base);
     }
 
-    Fatal() << "Invalid operation " << s[0]->c_str();
+    Fatal() << "Invalid operation " << s[0]->toString();
   }
 
   void parseAssertTrap(Element& s) {
@@ -208,7 +207,7 @@ protected:
       std::cout << "[exception thrown: " << e << "]" << std::endl;
       trapped = true;
     }
-
+    WASM_UNUSED(trapped);
     assert(trapped);
   }
 
@@ -228,6 +227,7 @@ protected:
       std::cout << "[exception thrown: " << e << "]" << std::endl;
       trapped = true;
     }
+    WASM_UNUSED(trapped);
     assert(!trapped);
     std::cerr << "seen " << actual << ", expected " << expected << '\n';
     if (expected != actual) {
@@ -283,9 +283,7 @@ protected:
           }
         }
       });
-      if (wasm.memory.imported()) {
-        reportUnknownImport(&wasm.memory);
-      }
+      ModuleUtils::iterImportedMemories(wasm, reportUnknownImport);
     }
 
     if (!invalid && id == ASSERT_TRAP) {
@@ -346,16 +344,15 @@ protected:
     spectest->addExport(
       builder.makeExport("global_f64", Name::fromInt(3), ExternalKind::Global));
 
-    spectest->addTable(
-      builder.makeTable(Name::fromInt(0), Type::funcref, 10, 20));
+    spectest->addTable(builder.makeTable(
+      Name::fromInt(0), Type(HeapType::func, Nullable), 10, 20));
     spectest->addExport(
       builder.makeExport("table", Name::fromInt(0), ExternalKind::Table));
 
-    spectest->memory.exists = true;
-    spectest->memory.initial = 1;
-    spectest->memory.max = 2;
-    spectest->addExport(builder.makeExport(
-      "memory", spectest->memory.name, ExternalKind::Memory));
+    Memory* memory =
+      spectest->addMemory(builder.makeMemory(Name::fromInt(0), 1, 2));
+    spectest->addExport(
+      builder.makeExport("memory", memory->name, ExternalKind::Memory));
 
     modules["spectest"].swap(spectest);
     modules["spectest"]->features = FeatureSet::All;
diff --git a/src/tools/wasm-split/instrumenter.cpp b/src/tools/wasm-split/instrumenter.cpp
index c23a70f..22940a5 100644
--- a/src/tools/wasm-split/instrumenter.cpp
+++ b/src/tools/wasm-split/instrumenter.cpp
@@ -22,28 +22,33 @@
 
 namespace wasm {
 
-Instrumenter::Instrumenter(const WasmSplitOptions& options, uint64_t moduleHash)
-  : options(options), moduleHash(moduleHash) {}
+Instrumenter::Instrumenter(const InstrumenterConfig& config,
+                           uint64_t moduleHash)
+  : config(config), moduleHash(moduleHash) {}
 
-void Instrumenter::run(PassRunner* runner, Module* wasm) {
-  this->runner = runner;
+void Instrumenter::run(Module* wasm) {
   this->wasm = wasm;
-  addGlobals();
+
+  size_t numFuncs = 0;
+  ModuleUtils::iterDefinedFunctions(*wasm, [&](Function*) { ++numFuncs; });
+
+  addGlobals(numFuncs);
+  addSecondaryMemory(numFuncs);
   instrumentFuncs();
-  addProfileExport();
+  addProfileExport(numFuncs);
 }
 
-void Instrumenter::addGlobals() {
-  if (options.storageKind != WasmSplitOptions::StorageKind::InGlobals) {
+void Instrumenter::addGlobals(size_t numFuncs) {
+  if (config.storageKind != WasmSplitOptions::StorageKind::InGlobals) {
     // Don't need globals
     return;
   }
   // Create fresh global names (over-reserves, but that's ok)
   counterGlobal = Names::getValidGlobalName(*wasm, "monotonic_counter");
-  functionGlobals.reserve(wasm->functions.size());
+  functionGlobals.reserve(numFuncs);
   ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
-    functionGlobals.push_back(Names::getValidGlobalName(
-      *wasm, std::string(func->name.c_str()) + "_timestamp"));
+    functionGlobals.push_back(
+      Names::getValidGlobalName(*wasm, func->name.toString() + "_timestamp"));
   });
 
   // Create and add new globals
@@ -62,11 +67,31 @@ void Instrumenter::addGlobals() {
   }
 }
 
+void Instrumenter::addSecondaryMemory(size_t numFuncs) {
+  if (config.storageKind != WasmSplitOptions::StorageKind::InSecondaryMemory) {
+    // Don't need secondary memory
+    return;
+  }
+  if (!wasm->features.hasMultiMemories()) {
+    Fatal()
+      << "error: --in-secondary-memory requires multi-memories to be enabled";
+  }
+
+  secondaryMemory =
+    Names::getValidMemoryName(*wasm, config.secondaryMemoryName);
+  // Create a memory with enough pages to write into
+  size_t pages = (numFuncs + Memory::kPageSize - 1) / Memory::kPageSize;
+  auto mem = Builder::makeMemory(secondaryMemory, pages, pages, true);
+  mem->module = config.importNamespace;
+  mem->base = config.secondaryMemoryName;
+  wasm->addMemory(std::move(mem));
+}
+
 void Instrumenter::instrumentFuncs() {
   // Inject code at the beginning of each function to advance the monotonic
   // counter and set the function's timestamp if it hasn't already been set.
   Builder builder(*wasm);
-  switch (options.storageKind) {
+  switch (config.storageKind) {
     case WasmSplitOptions::StorageKind::InGlobals: {
       // (if (i32.eqz (global.get $timestamp))
       //   (block
@@ -102,19 +127,30 @@ void Instrumenter::instrumentFuncs() {
       });
       break;
     }
-    case WasmSplitOptions::StorageKind::InMemory: {
+    case WasmSplitOptions::StorageKind::InMemory:
+    case WasmSplitOptions::StorageKind::InSecondaryMemory: {
       if (!wasm->features.hasAtomics()) {
-        Fatal() << "error: --in-memory requires atomics to be enabled";
+        const char* command =
+          config.storageKind == WasmSplitOptions::StorageKind::InMemory
+            ? "in-memory"
+            : "in-secondary-memory";
+        Fatal() << "error: --" << command << " requires atomics to be enabled";
       }
       // (i32.atomic.store8 offset=funcidx (i32.const 0) (i32.const 1))
       Index funcIdx = 0;
+      assert(!wasm->memories.empty());
+      Name memoryName =
+        config.storageKind == WasmSplitOptions::StorageKind::InMemory
+          ? wasm->memories[0]->name
+          : secondaryMemory;
       ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
         func->body = builder.makeSequence(
           builder.makeAtomicStore(1,
                                   funcIdx,
-                                  builder.makeConstPtr(0),
+                                  builder.makeConstPtr(0, Type::i32),
                                   builder.makeConst(uint32_t(1)),
-                                  Type::i32),
+                                  Type::i32,
+                                  memoryName),
           func->body,
           func->body->type);
         ++funcIdx;
@@ -139,21 +175,18 @@ void Instrumenter::instrumentFuncs() {
 // otherwise. Functions with smaller non-zero timestamps were called earlier in
 // the instrumented run than funtions with larger timestamps.
 
-void Instrumenter::addProfileExport() {
+void Instrumenter::addProfileExport(size_t numFuncs) {
   // Create and export a function to dump the profile into a given memory
   // buffer. The function takes the available address and buffer size as
   // arguments and returns the total size of the profile. It only actually
   // writes the profile if the given space is sufficient to hold it.
-  auto name = Names::getValidFunctionName(*wasm, options.profileExport);
+  auto name = Names::getValidFunctionName(*wasm, config.profileExport);
   auto writeProfile = Builder::makeFunction(
     name, Signature({Type::i32, Type::i32}, Type::i32), {});
   writeProfile->hasExplicitName = true;
   writeProfile->setLocalName(0, "addr");
   writeProfile->setLocalName(1, "size");
 
-  size_t numFuncs = 0;
-  ModuleUtils::iterDefinedFunctions(*wasm, [&](Function*) { ++numFuncs; });
-
   // Calculate the size of the profile:
   //   8 bytes module hash +
   //   4 bytes for the timestamp for each function
@@ -168,12 +201,25 @@ void Instrumenter::addProfileExport() {
     return builder.makeConst(int32_t(profileSize));
   };
 
+  // Also make sure there is a memory with enough pages to write into
+  size_t pages = (profileSize + Memory::kPageSize - 1) / Memory::kPageSize;
+  if (wasm->memories.empty()) {
+    wasm->addMemory(Builder::makeMemory("0"));
+    wasm->memories[0]->initial = pages;
+    wasm->memories[0]->max = pages;
+  } else if (wasm->memories[0]->initial < pages) {
+    wasm->memories[0]->initial = pages;
+    if (wasm->memories[0]->max < pages) {
+      wasm->memories[0]->max = pages;
+    }
+  }
+
   // Write the hash followed by all the time stamps
-  Expression* writeData =
-    builder.makeStore(8, 0, 1, getAddr(), hashConst(), Type::i64);
+  Expression* writeData = builder.makeStore(
+    8, 0, 1, getAddr(), hashConst(), Type::i64, wasm->memories[0]->name);
   uint32_t offset = 8;
 
-  switch (options.storageKind) {
+  switch (config.storageKind) {
     case WasmSplitOptions::StorageKind::InGlobals: {
       for (const auto& global : functionGlobals) {
         writeData = builder.blockify(
@@ -183,17 +229,23 @@ void Instrumenter::addProfileExport() {
                             1,
                             getAddr(),
                             builder.makeGlobalGet(global, Type::i32),
-                            Type::i32));
+                            Type::i32,
+                            wasm->memories[0]->name));
         offset += 4;
       }
       break;
     }
-    case WasmSplitOptions::StorageKind::InMemory: {
+    case WasmSplitOptions::StorageKind::InMemory:
+    case WasmSplitOptions::StorageKind::InSecondaryMemory: {
       Index funcIdxVar =
         Builder::addVar(writeProfile.get(), "funcIdx", Type::i32);
       auto getFuncIdx = [&]() {
         return builder.makeLocalGet(funcIdxVar, Type::i32);
       };
+      Name loadMemoryName =
+        config.storageKind == WasmSplitOptions::StorageKind::InMemory
+          ? wasm->memories[0]->name
+          : secondaryMemory;
       // (block $outer
       //   (loop $l
       //     (br_if $outer (i32.eq (local.get $fucIdx) (i32.const numFuncs))
@@ -232,8 +284,10 @@ void Instrumenter::addProfileExport() {
                   getAddr(),
                   builder.makeBinary(
                     MulInt32, getFuncIdx(), builder.makeConst(uint32_t(4)))),
-                builder.makeAtomicLoad(1, 0, getFuncIdx(), Type::i32),
-                Type::i32),
+                builder.makeAtomicLoad(
+                  1, 0, getFuncIdx(), Type::i32, loadMemoryName),
+                Type::i32,
+                wasm->memories[0]->name),
               builder.makeLocalSet(
                 funcIdxVar,
                 builder.makeBinary(
@@ -251,23 +305,10 @@ void Instrumenter::addProfileExport() {
   // Create an export for the function
   wasm->addFunction(std::move(writeProfile));
   wasm->addExport(
-    Builder::makeExport(options.profileExport, name, ExternalKind::Function));
-
-  // Also make sure there is a memory with enough pages to write into
-  size_t pages = (profileSize + Memory::kPageSize - 1) / Memory::kPageSize;
-  if (!wasm->memory.exists) {
-    wasm->memory.exists = true;
-    wasm->memory.initial = pages;
-    wasm->memory.max = pages;
-  } else if (wasm->memory.initial < pages) {
-    wasm->memory.initial = pages;
-    if (wasm->memory.max < pages) {
-      wasm->memory.max = pages;
-    }
-  }
+    Builder::makeExport(config.profileExport, name, ExternalKind::Function));
 
   // Export the memory if it is not already exported or imported.
-  if (!wasm->memory.imported()) {
+  if (!wasm->memories[0]->imported()) {
     bool memoryExported = false;
     for (auto& ex : wasm->exports) {
       if (ex->kind == ExternalKind::Memory) {
@@ -276,10 +317,10 @@ void Instrumenter::addProfileExport() {
       }
     }
     if (!memoryExported) {
-      wasm->addExport(
-        Builder::makeExport("profile-memory",
-                            Names::getValidExportName(*wasm, wasm->memory.name),
-                            ExternalKind::Memory));
+      wasm->addExport(Builder::makeExport(
+        "profile-memory",
+        Names::getValidExportName(*wasm, wasm->memories[0]->name),
+        ExternalKind::Memory));
     }
   }
 }
diff --git a/src/tools/wasm-split/instrumenter.h b/src/tools/wasm-split/instrumenter.h
index 7de5a91..135d2b7 100644
--- a/src/tools/wasm-split/instrumenter.h
+++ b/src/tools/wasm-split/instrumenter.h
@@ -23,27 +23,43 @@
 
 namespace wasm {
 
+struct InstrumenterConfig {
+  // The namespace from which to import the secondary memory
+  Name importNamespace = "env";
+  // The name of the secondary memory created to store profile data during
+  // instrumentation
+  Name secondaryMemoryName = "profile-data";
+  // Where to store the profile data
+  WasmSplitOptions::StorageKind storageKind =
+    WasmSplitOptions::StorageKind::InGlobals;
+  // The export name of the function the embedder calls to write the profile
+  // into memory
+  std::string profileExport = DEFAULT_PROFILE_EXPORT;
+};
+
 // Add a global monotonic counter and a timestamp global for each function, code
 // at the beginning of each function to set its timestamp, and a new exported
 // function for dumping the profile data.
 struct Instrumenter : public Pass {
-  PassRunner* runner = nullptr;
   Module* wasm = nullptr;
 
-  const WasmSplitOptions& options;
+  const InstrumenterConfig& config;
   uint64_t moduleHash;
 
   Name counterGlobal;
   std::vector<Name> functionGlobals;
 
-  Instrumenter(const WasmSplitOptions& options, uint64_t moduleHash);
+  Name secondaryMemory;
+
+  Instrumenter(const InstrumenterConfig& config, uint64_t moduleHash);
 
-  void run(PassRunner* runner, Module* wasm) override;
+  void run(Module* wasm) override;
 
 private:
-  void addGlobals();
+  void addGlobals(size_t numFuncs);
+  void addSecondaryMemory(size_t numFuncs);
   void instrumentFuncs();
-  void addProfileExport();
+  void addProfileExport(size_t numFuncs);
 };
 
 } // namespace wasm
diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp
index b5929aa..d077c70 100644
--- a/src/tools/wasm-split/split-options.cpp
+++ b/src/tools/wasm-split/split-options.cpp
@@ -67,6 +67,9 @@ std::ostream& operator<<(std::ostream& o, WasmSplitOptions::Mode& mode) {
     case WasmSplitOptions::Mode::MergeProfiles:
       o << "merge-profiles";
       break;
+    case WasmSplitOptions::Mode::PrintProfile:
+      o << "print-profile";
+      break;
   }
   return o;
 }
@@ -105,6 +108,16 @@ WasmSplitOptions::WasmSplitOptions()
          [&](Options* o, const std::string& argument) {
            mode = Mode::MergeProfiles;
          })
+    .add("--print-profile",
+         "",
+         "Print profile contents in a human-readable format.",
+         WasmSplitOption,
+         {Mode::PrintProfile},
+         Options::Arguments::One,
+         [&](Options* o, const std::string& argument) {
+           mode = Mode::PrintProfile;
+           profileFile = argument;
+         })
     .add(
       "--profile",
       "",
@@ -172,10 +185,12 @@ WasmSplitOptions::WasmSplitOptions()
       [&](Options* o, const std::string& argument) { placeholderMap = true; })
     .add("--import-namespace",
          "",
-         "The namespace from which to import objects from the primary "
-         "module into the secondary module.",
+         "When provided as an option for module splitting, the namespace from "
+         "which to import objects from the primary "
+         "module into the secondary module. In instrument mode, refers to the "
+         "namespace from which to import the secondary memory, if any.",
          WasmSplitOption,
-         {Mode::Split},
+         {Mode::Split, Mode::Instrument},
          Options::Arguments::One,
          [&](Options* o, const std::string& argument) {
            importNamespace = argument;
@@ -232,6 +247,29 @@ WasmSplitOptions::WasmSplitOptions()
       [&](Options* o, const std::string& argument) {
         storageKind = StorageKind::InMemory;
       })
+    .add(
+      "--in-secondary-memory",
+      "",
+      "Store profile information in a separate memory, rather than in module "
+      "main memory or globals (the default). With this option, users do not "
+      "need to reserve the initial memory region for profile data and the "
+      "data can be shared between multiple threads.",
+      WasmSplitOption,
+      {Mode::Instrument},
+      Options::Arguments::Zero,
+      [&](Options* o, const std::string& argument) {
+        storageKind = StorageKind::InSecondaryMemory;
+      })
+    .add("--secondary-memory-name",
+         "",
+         "The name of the secondary memory created to store profile "
+         "information.",
+         WasmSplitOption,
+         {Mode::Instrument},
+         Options::Arguments::One,
+         [&](Options* o, const std::string& argument) {
+           secondaryMemoryName = argument;
+         })
     .add(
       "--emit-module-names",
       "",
@@ -278,6 +316,12 @@ WasmSplitOptions::WasmSplitOptions()
          {Mode::Instrument, Mode::MergeProfiles},
          Options::Arguments::One,
          [&](Options* o, const std::string& argument) { output = argument; })
+    .add("--unescape",
+         "-u",
+         "Un-escape function names (in print-profile output)",
+         WasmSplitOption,
+         Options::Arguments::Zero,
+         [&](Options* o, const std::string& argument) { unescape = true; })
     .add("--verbose",
          "-v",
          "Verbose output mode. Prints the functions that will be kept "
@@ -362,6 +406,11 @@ bool WasmSplitOptions::validate() {
     case Mode::MergeProfiles:
       // Any number >= 1 allowed.
       break;
+    case Mode::PrintProfile:
+      if (inputFiles.size() != 1) {
+        fail("Must have exactly one profile path.");
+      }
+      break;
   }
 
   // Validate that all used options are allowed in the current mode.
diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h
index 16e7b75..5aba0ba 100644
--- a/src/tools/wasm-split/split-options.h
+++ b/src/tools/wasm-split/split-options.h
@@ -28,17 +28,20 @@ struct WasmSplitOptions : ToolOptions {
     Split,
     Instrument,
     MergeProfiles,
+    PrintProfile,
   };
   Mode mode = Mode::Split;
   constexpr static size_t NumModes =
-    static_cast<unsigned>(Mode::MergeProfiles) + 1;
+    static_cast<unsigned>(Mode::PrintProfile) + 1;
 
   enum class StorageKind : unsigned {
     InGlobals, // Store profile data in WebAssembly Globals
     InMemory,  // Store profile data in memory, accessible from all threads
+    InSecondaryMemory, // Store profile data in memory separate from main memory
   };
   StorageKind storageKind = StorageKind::InGlobals;
 
+  bool unescape = false;
   bool verbose = false;
   bool emitBinary = true;
   bool symbolMap = false;
@@ -61,6 +64,7 @@ struct WasmSplitOptions : ToolOptions {
 
   std::string importNamespace;
   std::string placeholderNamespace;
+  std::string secondaryMemoryName;
   std::string exportPrefix;
 
   // A hack to ensure the split and instrumented modules have the same table
diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp
index 871d6bc..6a04267 100644
--- a/src/tools/wasm-split/wasm-split.cpp
+++ b/src/tools/wasm-split/wasm-split.cpp
@@ -17,6 +17,8 @@
 // wasm-split: Split a module in two or instrument a module to inform future
 // splitting.
 
+#include <fstream>
+
 #include "ir/module-splitting.h"
 #include "ir/names.h"
 #include "support/file.h"
@@ -108,8 +110,19 @@ void instrumentModule(const WasmSplitOptions& options) {
   }
 
   uint64_t moduleHash = hashFile(options.inputFiles[0]);
+  InstrumenterConfig config;
+  if (options.importNamespace.size()) {
+    config.importNamespace = options.importNamespace;
+  }
+  if (options.secondaryMemoryName.size()) {
+    config.secondaryMemoryName = options.secondaryMemoryName;
+  }
+  config.storageKind = options.storageKind;
+  config.profileExport = options.profileExport;
+
   PassRunner runner(&wasm, options.passOptions);
-  Instrumenter(options, moduleHash).run(&runner, &wasm);
+  runner.add(std::make_unique<Instrumenter>(config, moduleHash));
+  runner.run();
 
   adjustTableSize(wasm, options.initialTableSize);
 
@@ -149,6 +162,34 @@ ProfileData readProfile(const std::string& file) {
   return {hash, timestamps};
 }
 
+void getFunctionsToKeepAndSplit(Module& wasm,
+                                uint64_t wasmHash,
+                                const std::string& profileFile,
+                                std::set<Name>& keepFuncs,
+                                std::set<Name>& splitFuncs) {
+  ProfileData profile = readProfile(profileFile);
+  if (profile.hash != wasmHash) {
+    Fatal() << "error: checksum in profile does not match module checksum. "
+            << "The module to split must be the original, uninstrumented "
+               "module, not the module used to generate the profile.";
+  }
+
+  size_t i = 0;
+  ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) {
+    if (i >= profile.timestamps.size()) {
+      Fatal() << "Unexpected end of profile data";
+    }
+    if (profile.timestamps[i++] > 0) {
+      keepFuncs.insert(func->name);
+    } else {
+      splitFuncs.insert(func->name);
+    }
+  });
+  if (i != profile.timestamps.size()) {
+    Fatal() << "Unexpected extra profile data";
+  }
+}
+
 void writeSymbolMap(Module& wasm, std::string filename) {
   PassOptions options;
   options.arguments["symbolmap"] = filename;
@@ -175,24 +216,9 @@ void splitModule(const WasmSplitOptions& options) {
   if (options.profileFile.size()) {
     // Use the profile to set `keepFuncs`.
     uint64_t hash = hashFile(options.inputFiles[0]);
-    ProfileData profile = readProfile(options.profileFile);
-    if (profile.hash != hash) {
-      Fatal() << "error: checksum in profile does not match module checksum. "
-              << "The split module must be the original module that was "
-              << "instrumented to generate the profile.";
-    }
-    size_t i = 0;
-    ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) {
-      if (i >= profile.timestamps.size()) {
-        Fatal() << "Unexpected end of profile data";
-      }
-      if (profile.timestamps[i++] > 0) {
-        keepFuncs.insert(func->name);
-      }
-    });
-    if (i != profile.timestamps.size()) {
-      Fatal() << "Unexpected extra profile data";
-    }
+    std::set<Name> splitFuncs;
+    getFunctionsToKeepAndSplit(
+      wasm, hash, options.profileFile, keepFuncs, splitFuncs);
   } else if (options.keepFuncs.size()) {
     // Use the explicitly provided `keepFuncs`.
     for (auto& func : options.keepFuncs) {
@@ -385,6 +411,62 @@ void mergeProfiles(const WasmSplitOptions& options) {
   buffer.writeTo(out.getStream());
 }
 
+std::string unescape(std::string input) {
+  std::string output;
+  for (size_t i = 0; i < input.length(); i++) {
+    if ((input[i] == '\\') && (i + 2 < input.length()) &&
+        isxdigit(input[i + 1]) && isxdigit(input[i + 2])) {
+      std::string byte = input.substr(i + 1, 2);
+      i += 2;
+      char chr = (char)(int)strtol(byte.c_str(), nullptr, 16);
+      output.push_back(chr);
+    } else {
+      output.push_back(input[i]);
+    }
+  }
+  return output;
+}
+
+void checkExists(const std::string& path) {
+  std::ifstream infile(path);
+  if (!infile.is_open()) {
+    Fatal() << "File not found: " << path;
+  }
+}
+
+void printReadableProfile(const WasmSplitOptions& options) {
+  const std::string wasmFile(options.inputFiles[0]);
+  checkExists(options.profileFile);
+  checkExists(wasmFile);
+
+  Module wasm;
+  parseInput(wasm, options);
+
+  std::set<Name> keepFuncs;
+  std::set<Name> splitFuncs;
+
+  uint64_t hash = hashFile(wasmFile);
+  getFunctionsToKeepAndSplit(
+    wasm, hash, options.profileFile, keepFuncs, splitFuncs);
+
+  auto printFnSet = [&](auto funcs, std::string prefix) {
+    for (auto it = funcs.begin(); it != funcs.end(); ++it) {
+      std::cout << prefix << " "
+                << (options.unescape ? unescape(it->toString())
+                                     : it->toString())
+                << std::endl;
+    }
+  };
+
+  std::cout << "Keeping functions: " << std::endl;
+  printFnSet(keepFuncs, "+");
+  std::cout << std::endl;
+
+  std::cout << "Splitting out functions: " << std::endl;
+  printFnSet(splitFuncs, "-");
+  std::cout << std::endl;
+}
+
 } // anonymous namespace
 
 int main(int argc, const char* argv[]) {
@@ -405,5 +487,8 @@ int main(int argc, const char* argv[]) {
     case WasmSplitOptions::Mode::MergeProfiles:
       mergeProfiles(options);
       break;
+    case WasmSplitOptions::Mode::PrintProfile:
+      printReadableProfile(options);
+      break;
   }
 }
diff --git a/src/tools/wasm2c-wrapper.h b/src/tools/wasm2c-wrapper.h
index ae32ec7..f44fc5c 100644
--- a/src/tools/wasm2c-wrapper.h
+++ b/src/tools/wasm2c-wrapper.h
@@ -30,9 +30,7 @@ namespace wasm {
 inline std::string wasm2cMangle(Name name, Signature sig) {
   const char escapePrefix = 'Z';
   std::string mangled = "Z_";
-  const char* original = name.str;
-  unsigned char c;
-  while ((c = *original++)) {
+  for (unsigned char c : name.str) {
     if ((isalnum(c) && c != escapePrefix) || c == '_') {
       // This character is ok to emit as it is.
       mangled += c;
@@ -168,14 +166,14 @@ int main(int argc, char** argv) {
     auto* func = wasm.getFunction(exp->value);
 
     ret += std::string("          puts(\"[fuzz-exec] calling ") +
-           exp->name.str + "\");\n";
+           exp->name.toString() + "\");\n";
     auto result = func->getResults();
 
     // Emit the call itself.
     ret += "            ";
     if (result != Type::none) {
-      ret += std::string("printf(\"[fuzz-exec] note result: ") + exp->name.str +
-             " => ";
+      ret += std::string("printf(\"[fuzz-exec] note result: ") +
+             exp->name.toString() + " => ";
       TODO_SINGLE_COMPOUND(result);
       switch (result.getBasic()) {
         case Type::i32:
diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp
index 061cd38..a6430fe 100644
--- a/src/tools/wasm2js.cpp
+++ b/src/tools/wasm2js.cpp
@@ -39,7 +39,9 @@ static void optimizeWasm(Module& wasm, PassOptions options) {
   struct OptimizeForJS : public WalkerPass<PostWalker<OptimizeForJS>> {
     bool isFunctionParallel() override { return true; }
 
-    Pass* create() override { return new OptimizeForJS; }
+    std::unique_ptr<Pass> create() override {
+      return std::make_unique<OptimizeForJS>();
+    }
 
     void visitBinary(Binary* curr) {
       // x - -c (where c is a constant) is larger than x + c, in js (but not
@@ -832,7 +834,7 @@ void AssertionEmitter::emit() {
   )";
 
   Builder wasmBuilder(sexpBuilder.getModule());
-  Name asmModule = std::string("ret") + ASM_FUNC.str;
+  Name asmModule = std::string("ret") + ASM_FUNC.toString();
   // Track the last built module.
   Module wasm;
   for (size_t i = 0; i < root.size(); ++i) {
@@ -841,11 +843,11 @@ void AssertionEmitter::emit() {
         e[0]->str() == Name("module")) {
       ModuleUtils::clearModule(wasm);
       std::stringstream funcNameS;
-      funcNameS << ASM_FUNC.c_str() << i;
+      funcNameS << ASM_FUNC << i;
       std::stringstream moduleNameS;
-      moduleNameS << "ret" << ASM_FUNC.c_str() << i;
-      Name funcName(funcNameS.str().c_str());
-      asmModule = Name(moduleNameS.str().c_str());
+      moduleNameS << "ret" << ASM_FUNC << i;
+      Name funcName(funcNameS.str());
+      asmModule = Name(moduleNameS.str());
       options.applyFeatures(wasm);
       SExpressionWasmBuilder builder(wasm, e, options.profile);
       emitWasm(wasm, out, flags, options.passOptions, funcName);
@@ -855,13 +857,13 @@ void AssertionEmitter::emit() {
       std::cerr << "skipping " << e << std::endl;
       continue;
     }
-    Name testFuncName(IString(("check" + std::to_string(i)).c_str(), false));
+    Name testFuncName("check" + std::to_string(i));
     bool isInvoke = (e[0]->str() == Name("invoke"));
     bool isReturn = (e[0]->str() == Name("assert_return"));
     bool isReturnNan = (e[0]->str() == Name("assert_return_nan"));
     if (isInvoke) {
       emitInvokeFunc(wasmBuilder, wasm, e, testFuncName, asmModule);
-      out << testFuncName.str << "();\n";
+      out << testFuncName << "();\n";
       continue;
     }
     // Otherwise, this is some form of assertion.
@@ -873,7 +875,7 @@ void AssertionEmitter::emit() {
       emitAssertTrapFunc(wasmBuilder, wasm, e, testFuncName, asmModule);
     }
 
-    out << "if (!" << testFuncName.str << "()) throw 'assertion failed: " << e
+    out << "if (!" << testFuncName << "()) throw 'assertion failed: " << e
         << "';\n";
   }
 }
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 58dcd22..acde624 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -48,7 +48,8 @@ enum {
 enum WebLimitations : uint32_t {
   MaxDataSegments = 100 * 1000,
   MaxFunctionBodySize = 128 * 1024,
-  MaxFunctionLocals = 50 * 1000
+  MaxFunctionLocals = 50 * 1000,
+  MaxFunctionParams = 1000
 };
 
 template<typename T, typename MiniT> struct LEB {
@@ -109,7 +110,7 @@ template<typename T, typename MiniT> struct LEB {
       byte = get();
       bool last = !(byte & 128);
       T payload = byte & 127;
-      typedef typename std::make_unsigned<T>::type mask_type;
+      using mask_type = typename std::make_unsigned<T>::type;
       auto shift_mask = 0 == shift
                           ? ~mask_type(0)
                           : ((mask_type(1) << (sizeof(T) * 8 - shift)) - 1u);
@@ -146,10 +147,10 @@ template<typename T, typename MiniT> struct LEB {
   }
 };
 
-typedef LEB<uint32_t, uint8_t> U32LEB;
-typedef LEB<uint64_t, uint8_t> U64LEB;
-typedef LEB<int32_t, int8_t> S32LEB;
-typedef LEB<int64_t, int8_t> S64LEB;
+using U32LEB = LEB<uint32_t, uint8_t>;
+using U64LEB = LEB<uint64_t, uint8_t>;
+using S32LEB = LEB<int32_t, int8_t>;
+using S64LEB = LEB<int64_t, int8_t>;
 
 //
 // We mostly stream into a buffer as we create the binary format, however,
@@ -326,7 +327,8 @@ enum Section {
   Code = 10,
   Data = 11,
   DataCount = 12,
-  Tag = 13
+  Tag = 13,
+  Strings = 14,
 };
 
 // A passive segment is a segment that will not be automatically copied into a
@@ -362,8 +364,10 @@ enum EncodedType {
   i16 = -0x7,  // 0x79
   // function reference type
   funcref = -0x10, // 0x70
-  // top type of references, including host references
-  anyref = -0x11, // 0x6f
+  // external (host) references
+  externref = -0x11, // 0x6f
+  // top type of references to non-function Wasm data.
+  anyref = -0x12, // 0x6e
   // comparable reference type
   eqref = -0x13, // 0x6d
   // nullable typed function reference type, with parameter
@@ -372,11 +376,17 @@ enum EncodedType {
   nonnullable = -0x15, // 0x6b
   // integer reference type
   i31ref = -0x16, // 0x6a
-  // run-time type info type, with depth index n
-  rtt_n = -0x17, // 0x69
-  // run-time type info type, without depth index n
-  rtt = -0x18,     // 0x68
-  dataref = -0x19, // 0x67
+  // gc and string reference types
+  dataref = -0x19,          // 0x67
+  arrayref = -0x1a,         // 0x66
+  stringref = -0x1c,        // 0x64
+  stringview_wtf8 = -0x1d,  // 0x63
+  stringview_wtf16 = -0x1e, // 0x62
+  stringview_iter = -0x1f,  // 0x61
+  // bottom types
+  nullexternref = -0x17, // 0x69
+  nullfuncref = -0x18,   // 0x68
+  nullref = -0x1b,       // 0x65
   // type forms
   Func = -0x20,   // 0x60
   Struct = -0x21, // 0x5f
@@ -393,11 +403,24 @@ enum EncodedType {
 };
 
 enum EncodedHeapType {
-  func = -0x10, // 0x70
-  any = -0x11,  // 0x6f
-  eq = -0x13,   // 0x6d
-  i31 = -0x16,  // 0x6a
-  data = -0x19, // 0x67
+  func = -0x10,   // 0x70
+  ext = -0x11,    // 0x6f
+  any = -0x12,    // 0x6e
+  eq = -0x13,     // 0x6d
+  i31 = -0x16,    // 0x6a
+  data = -0x19,   // 0x67
+  array = -0x1a,  // 0x66
+  string = -0x1c, // 0x64
+  // stringview/iter constants are identical to type, and cannot be duplicated
+  // here as that would be a compiler error, so add _heap suffixes. See
+  // https://github.com/WebAssembly/stringref/issues/12
+  stringview_wtf8_heap = -0x1d,  // 0x63
+  stringview_wtf16_heap = -0x1e, // 0x62
+  stringview_iter_heap = -0x1f,  // 0x61
+  // bottom types
+  noext = -0x17,  // 0x69
+  nofunc = -0x18, // 0x68
+  none = -0x1b,   // 0x65
 };
 
 namespace UserSections {
@@ -422,9 +445,10 @@ extern const char* ReferenceTypesFeature;
 extern const char* MultivalueFeature;
 extern const char* GCFeature;
 extern const char* Memory64Feature;
-extern const char* TypedFunctionReferencesFeature;
 extern const char* RelaxedSIMDFeature;
 extern const char* ExtendedConstFeature;
+extern const char* StringsFeature;
+extern const char* MultiMemoriesFeature;
 
 enum Subsection {
   NameModule = 0,
@@ -440,6 +464,7 @@ enum Subsection {
   NameData = 9,
   // see: https://github.com/WebAssembly/gc/issues/193
   NameField = 10,
+  NameTag = 11,
 
   DylinkMemInfo = 1,
   DylinkNeeded = 2,
@@ -919,11 +944,11 @@ enum ASTNodes {
 
   I32x4Abs = 0xa0,
   I32x4Neg = 0xa1,
-  // 0xa2 for relaxed SIMD
+  // 0xa2 unused
   I32x4AllTrue = 0xa3,
   I32x4Bitmask = 0xa4,
-  // 0xa5 for relaxed SIMD
-  // 0xa6 for relaxed SIMD
+  // 0xa5 unused
+  // 0xa6 unused
   I32x4ExtendLowI16x8S = 0xa7,
   I32x4ExtendHighI16x8S = 0xa8,
   I32x4ExtendLowI16x8U = 0xa9,
@@ -932,12 +957,12 @@ enum ASTNodes {
   I32x4ShrS = 0xac,
   I32x4ShrU = 0xad,
   I32x4Add = 0xae,
-  // 0xaf for relaxed SIMD
-  // 0xb0 for relaxed SIMD
+  // 0xaf unused
+  // 0xb0 unused
   I32x4Sub = 0xb1,
-  // 0xb2 for relaxed SIMD
-  // 0xb3 for relaxed SIMD
-  // 0xb4 for relaxed SIMD
+  // 0xb2 unused
+  // 0xb3 unused
+  // 0xb4 unused
   I32x4Mul = 0xb5,
   I32x4MinS = 0xb6,
   I32x4MinU = 0xb7,
@@ -955,8 +980,8 @@ enum ASTNodes {
   // 0xc2 unused
   I64x2AllTrue = 0xc3,
   I64x2Bitmask = 0xc4,
-  // 0xc5 for relaxed SIMD
-  // 0xc6 for relaxed SIMD
+  // 0xc5 unused
+  // 0xc6 unused
   I64x2ExtendLowI32x4S = 0xc7,
   I64x2ExtendHighI32x4S = 0xc8,
   I64x2ExtendLowI32x4U = 0xc9,
@@ -965,12 +990,12 @@ enum ASTNodes {
   I64x2ShrS = 0xcc,
   I64x2ShrU = 0xcd,
   I64x2Add = 0xce,
-  // 0xcf for relaxed SIMD
-  // 0xd0 for relaxed SIMD
+  // 0xcf unused
+  // 0xd0 unused
   I64x2Sub = 0xd1,
-  // 0xd2 for relaxed SIMD
-  // 0xd3 for relaxed SIMD
-  // 0xd4 for relaxed SIMD
+  // 0xd2 unused
+  // 0xd3 unused
+  // 0xd4 unused
   I64x2Mul = 0xd5,
   I64x2Eq = 0xd6,
   I64x2Ne = 0xd7,
@@ -985,7 +1010,7 @@ enum ASTNodes {
 
   F32x4Abs = 0xe0,
   F32x4Neg = 0xe1,
-  // 0xe2 for relaxed SIMD
+  // 0xe2 unused
   F32x4Sqrt = 0xe3,
   F32x4Add = 0xe4,
   F32x4Sub = 0xe5,
@@ -998,7 +1023,7 @@ enum ASTNodes {
 
   F64x2Abs = 0xec,
   F64x2Neg = 0xed,
-  // 0xee for relaxed SIMD
+  // 0xee unused
   F64x2Sqrt = 0xef,
   F64x2Add = 0xf0,
   F64x2Sub = 0xf1,
@@ -1019,28 +1044,26 @@ enum ASTNodes {
   F64x2ConvertLowI32x4U = 0xff,
 
   // relaxed SIMD opcodes
-  I8x16RelaxedSwizzle = 0xa2,
-  I32x4RelaxedTruncF32x4S = 0xa5,
-  I32x4RelaxedTruncF32x4U = 0xa6,
-  I32x4RelaxedTruncF64x2SZero = 0xc5,
-  I32x4RelaxedTruncF64x2UZero = 0xc6,
-  F32x4RelaxedFma = 0xaf,
-  F32x4RelaxedFms = 0xb0,
-  F64x2RelaxedFma = 0xcf,
-  F64x2RelaxedFms = 0xd0,
-  I8x16Laneselect = 0xb2,
-  I16x8Laneselect = 0xb3,
-  I32x4Laneselect = 0xd2,
-  I64x2Laneselect = 0xd3,
-  F32x4RelaxedMin = 0xb4,
-  F32x4RelaxedMax = 0xe2,
-  F64x2RelaxedMin = 0xd4,
-  F64x2RelaxedMax = 0xee,
+  I8x16RelaxedSwizzle = 0x100,
+  I32x4RelaxedTruncF32x4S = 0x101,
+  I32x4RelaxedTruncF32x4U = 0x102,
+  I32x4RelaxedTruncF64x2SZero = 0x103,
+  I32x4RelaxedTruncF64x2UZero = 0x104,
+  F32x4RelaxedFma = 0x105,
+  F32x4RelaxedFms = 0x106,
+  F64x2RelaxedFma = 0x107,
+  F64x2RelaxedFms = 0x108,
+  I8x16Laneselect = 0x109,
+  I16x8Laneselect = 0x10a,
+  I32x4Laneselect = 0x10b,
+  I64x2Laneselect = 0x10c,
+  F32x4RelaxedMin = 0x10d,
+  F32x4RelaxedMax = 0x10e,
+  F64x2RelaxedMin = 0x10f,
+  F64x2RelaxedMax = 0x110,
   I16x8RelaxedQ15MulrS = 0x111,
   I16x8DotI8x16I7x16S = 0x112,
-  I16x8DotI8x16I7x16U = 0x113,
-  I32x4DotI8x16I7x16AddS = 0x114,
-  I32x4DotI8x16I7x16AddU = 0x115,
+  I32x4DotI8x16I7x16AddS = 0x113,
 
   // bulk memory opcodes
 
@@ -1073,41 +1096,31 @@ enum ASTNodes {
 
   CallRef = 0x14,
   RetCallRef = 0x15,
-  Let = 0x17,
 
   // gc opcodes
 
   RefEq = 0xd5,
-  StructNewWithRtt = 0x01,
-  StructNewDefaultWithRtt = 0x02,
   StructGet = 0x03,
   StructGetS = 0x04,
   StructGetU = 0x05,
   StructSet = 0x06,
   StructNew = 0x07,
   StructNewDefault = 0x08,
-  ArrayNewWithRtt = 0x11,
-  ArrayNewDefaultWithRtt = 0x12,
+  ArrayNewElem = 0x10,
   ArrayGet = 0x13,
   ArrayGetS = 0x14,
   ArrayGetU = 0x15,
   ArraySet = 0x16,
-  ArrayLen = 0x17,
+  ArrayLenAnnotated = 0x17,
   ArrayCopy = 0x18,
-  ArrayInit = 0x19,
+  ArrayLen = 0x19,
   ArrayInitStatic = 0x1a,
   ArrayNew = 0x1b,
   ArrayNewDefault = 0x1c,
+  ArrayNewData = 0x1d,
   I31New = 0x20,
   I31GetS = 0x21,
   I31GetU = 0x22,
-  RttCanon = 0x30,
-  RttSub = 0x31,
-  RttFreshSub = 0x32,
-  RefTest = 0x40,
-  RefCast = 0x41,
-  BrOnCast = 0x42,
-  BrOnCastFail = 0x43,
   RefTestStatic = 0x44,
   RefCastStatic = 0x45,
   BrOnCastStatic = 0x46,
@@ -1125,6 +1138,34 @@ enum ASTNodes {
   BrOnNonFunc = 0x63,
   BrOnNonData = 0x64,
   BrOnNonI31 = 0x65,
+  ExternInternalize = 0x70,
+  ExternExternalize = 0x71,
+  StringNewWTF8 = 0x80,
+  StringNewWTF16 = 0x81,
+  StringConst = 0x82,
+  StringMeasureWTF8 = 0x84,
+  StringMeasureWTF16 = 0x85,
+  StringEncodeWTF8 = 0x86,
+  StringEncodeWTF16 = 0x87,
+  StringConcat = 0x88,
+  StringEq = 0x89,
+  StringIsUSV = 0x8a,
+  StringAsWTF8 = 0x90,
+  StringViewWTF8Advance = 0x91,
+  StringViewWTF8Slice = 0x93,
+  StringAsWTF16 = 0x98,
+  StringViewWTF16Length = 0x99,
+  StringViewWTF16GetCodePoint = 0x9a,
+  StringViewWTF16Slice = 0x9c,
+  StringAsIter = 0xa0,
+  StringViewIterNext = 0xa1,
+  StringViewIterAdvance = 0xa2,
+  StringViewIterRewind = 0xa3,
+  StringViewIterSlice = 0xa4,
+  StringNewWTF8Array = 0xb0,
+  StringNewWTF16Array = 0xb1,
+  StringEncodeWTF8Array = 0xb2,
+  StringEncodeWTF16Array = 0xb3,
 };
 
 enum MemoryAccess {
@@ -1135,6 +1176,12 @@ enum MemoryAccess {
 
 enum MemoryFlags { HasMaximum = 1 << 0, IsShared = 1 << 1, Is64 = 1 << 2 };
 
+enum StringPolicy {
+  UTF8 = 0x00,
+  WTF8 = 0x01,
+  Replace = 0x02,
+};
+
 enum FeaturePrefix {
   FeatureUsed = '+',
   FeatureRequired = '=',
@@ -1160,6 +1207,8 @@ class WasmBinaryWriter {
     std::unordered_map<Name, Index> globalIndexes;
     std::unordered_map<Name, Index> tableIndexes;
     std::unordered_map<Name, Index> elemIndexes;
+    std::unordered_map<Name, Index> memoryIndexes;
+    std::unordered_map<Name, Index> dataIndexes;
 
     BinaryIndexes(Module& wasm) {
       auto addIndexes = [&](auto& source, auto& indexes) {
@@ -1181,12 +1230,18 @@ class WasmBinaryWriter {
       addIndexes(wasm.functions, functionIndexes);
       addIndexes(wasm.tags, tagIndexes);
       addIndexes(wasm.tables, tableIndexes);
+      addIndexes(wasm.memories, memoryIndexes);
 
       for (auto& curr : wasm.elementSegments) {
         auto index = elemIndexes.size();
         elemIndexes[curr->name] = index;
       }
 
+      for (auto& curr : wasm.dataSegments) {
+        auto index = dataIndexes.size();
+        dataIndexes[curr->name] = index;
+      }
+
       // Globals may have tuple types in the IR, in which case they lower to
       // multiple globals, one for each tuple element, in the binary. Tuple
       // globals therefore occupy multiple binary indices, and we have to take
@@ -1248,13 +1303,14 @@ public:
   int32_t startSubsection(BinaryConsts::UserSections::Subsection code);
   void finishSubsection(int32_t start);
   void writeStart();
-  void writeMemory();
+  void writeMemories();
   void writeTypes();
   void writeImports();
 
   void writeFunctionSignatures();
   void writeExpression(Expression* curr);
   void writeFunctions();
+  void writeStrings();
   void writeGlobals();
   void writeExports();
   void writeDataCount();
@@ -1263,9 +1319,11 @@ public:
 
   uint32_t getFunctionIndex(Name name) const;
   uint32_t getTableIndex(Name name) const;
+  uint32_t getMemoryIndex(Name name) const;
   uint32_t getGlobalIndex(Name name) const;
   uint32_t getTagIndex(Name name) const;
   uint32_t getTypeIndex(HeapType type) const;
+  uint32_t getStringIndex(Name string) const;
 
   void writeTableDeclarations();
   void writeElementSegments();
@@ -1287,8 +1345,8 @@ public:
   void writeExtraDebugLocation(Expression* curr, Function* func, size_t id);
 
   // helpers
-  void writeInlineString(const char* name);
-  void writeEscapedName(const char* name);
+  void writeInlineString(std::string_view name);
+  void writeEscapedName(std::string_view name);
   void writeInlineBuffer(const char* data, size_t size);
   void writeData(const char* data, size_t size);
 
@@ -1356,6 +1414,9 @@ private:
   // info here, and then use it when writing the names.
   std::unordered_map<Name, MappedLocals> funcMappedLocals;
 
+  // Indexes in the string literal section of each StringConst in the wasm.
+  std::unordered_map<Name, Index> stringIndexes;
+
   void prepare();
 };
 
@@ -1394,7 +1455,7 @@ public:
 
   bool more() { return pos < input.size(); }
 
-  std::pair<const char*, const char*> getByteView(size_t size);
+  std::string_view getByteView(size_t size);
   uint8_t getInt8();
   uint16_t getInt16();
   uint32_t getInt32();
@@ -1428,15 +1489,19 @@ public:
   void verifyInt64(int64_t x);
   void readHeader();
   void readStart();
-  void readMemory();
+  void readMemories();
   void readTypes();
 
   // gets a name in the combined import+defined space
   Name getFunctionName(Index index);
   Name getTableName(Index index);
+  Name getMemoryName(Index index);
   Name getGlobalName(Index index);
   Name getTagName(Index index);
 
+  // gets a memory in the combined import+defined space
+  Memory* getMemory(Index index);
+
   void getResizableLimits(Address& initial,
                           Address& max,
                           bool& shared,
@@ -1462,39 +1527,26 @@ public:
   // We read functions and globals before we know their names, so we need to
   // backpatch the names later
 
-  // we store functions here before wasm.addFunction after we know their names
-  std::vector<Function*> functions;
-  // we store function imports here before wasm.addFunctionImport after we know
-  // their names
-  std::vector<Function*> functionImports;
   // at index i we have all refs to the function i
-  std::map<Index, std::vector<Expression*>> functionRefs;
+  std::map<Index, std::vector<Name*>> functionRefs;
   Function* currFunction = nullptr;
   // before we see a function (like global init expressions), there is no end of
   // function to check
   Index endOfFunction = -1;
 
-  // we store tables here before wasm.addTable after we know their names
-  std::vector<std::unique_ptr<Table>> tables;
-  // we store table imports here before wasm.addTableImport after we know
-  // their names
-  std::vector<Table*> tableImports;
   // at index i we have all references to the table i
-  std::map<Index, std::vector<Expression*>> tableRefs;
+  std::map<Index, std::vector<Name*>> tableRefs;
 
   std::map<Index, Name> elemTables;
 
-  // we store elems here after being read from binary, until when we know their
-  // names
-  std::vector<std::unique_ptr<ElementSegment>> elementSegments;
+  // at index i we have all references to the memory i
+  std::map<Index, std::vector<wasm::Name*>> memoryRefs;
 
-  // we store globals here before wasm.addGlobal after we know their names
-  std::vector<std::unique_ptr<Global>> globals;
-  // we store global imports here before wasm.addGlobalImport after we know
-  // their names
-  std::vector<Global*> globalImports;
   // at index i we have all refs to the global i
-  std::map<Index, std::vector<Expression*>> globalRefs;
+  std::map<Index, std::vector<Name*>> globalRefs;
+
+  // at index i we have all refs to the tag i
+  std::map<Index, std::vector<Name*>> tagRefs;
 
   // Throws a parsing error if we are not in a function context
   void requireFunctionContext(const char* error);
@@ -1506,6 +1558,10 @@ public:
   std::vector<Export*> exportOrder;
   void readExports();
 
+  // The strings in the strings section (which are referred to by StringConst).
+  std::vector<Name> strings;
+  void readStrings();
+
   Expression* readExpression();
   void readGlobals();
 
@@ -1523,29 +1579,6 @@ public:
 
   std::vector<Expression*> expressionStack;
 
-  // Each let block in the binary adds new locals to the bottom of the index
-  // space. That is, all previously-existing indexes are bumped to higher
-  // indexes. getAbsoluteLocalIndex does this computation.
-  // Note that we must track not just the number of locals added in each let,
-  // but also the absolute index from which they were allocated, as binaryen
-  // will add new locals as it goes for things like stacky code and tuples (so
-  // there isn't a simple way to get to the absolute index from a relative one).
-  // Hence each entry here is a pair of the number of items, and the absolute
-  // index they begin at.
-  struct LetData {
-    // How many items are defined in this let.
-    Index num;
-    // The absolute index from which they are allocated from. That is, if num is
-    // 5 and absoluteStart is 10, then we use indexes 10-14.
-    Index absoluteStart;
-  };
-  std::vector<LetData> letStack;
-
-  // Given a relative index of a local (the one used in the wasm binary), get
-  // the absolute one which takes into account lets, and is the one used in
-  // Binaryen IR.
-  Index getAbsoluteLocalIndex(Index index);
-
   // Control flow structure parsing: these have not just the normal binary
   // data for an instruction, but also some bytes later on like "end" or "else".
   // We must be aware of the connection between those things, for debug info.
@@ -1592,7 +1625,7 @@ public:
   bool hasDataCount = false;
 
   void readDataSegments();
-  void readDataCount();
+  void readDataSegmentCount();
 
   void readTableDeclarations();
   void readElementSegments();
@@ -1624,7 +1657,7 @@ public:
   BreakTarget getBreakTarget(int32_t offset);
   Name getExceptionTargetName(int32_t offset);
 
-  void readMemoryAccess(Address& alignment, Address& offset);
+  Index readMemoryAccess(Address& alignment, Address& offset);
 
   void visitIf(If* curr);
   void visitLoop(Loop* curr);
@@ -1670,17 +1703,29 @@ public:
   bool maybeVisitRefTest(Expression*& out, uint32_t code);
   bool maybeVisitRefCast(Expression*& out, uint32_t code);
   bool maybeVisitBrOn(Expression*& out, uint32_t code);
-  bool maybeVisitRttCanon(Expression*& out, uint32_t code);
-  bool maybeVisitRttSub(Expression*& out, uint32_t code);
   bool maybeVisitStructNew(Expression*& out, uint32_t code);
   bool maybeVisitStructGet(Expression*& out, uint32_t code);
   bool maybeVisitStructSet(Expression*& out, uint32_t code);
   bool maybeVisitArrayNew(Expression*& out, uint32_t code);
+  bool maybeVisitArrayNewSeg(Expression*& out, uint32_t code);
   bool maybeVisitArrayInit(Expression*& out, uint32_t code);
   bool maybeVisitArrayGet(Expression*& out, uint32_t code);
   bool maybeVisitArraySet(Expression*& out, uint32_t code);
   bool maybeVisitArrayLen(Expression*& out, uint32_t code);
   bool maybeVisitArrayCopy(Expression*& out, uint32_t code);
+  bool maybeVisitStringNew(Expression*& out, uint32_t code);
+  bool maybeVisitStringConst(Expression*& out, uint32_t code);
+  bool maybeVisitStringMeasure(Expression*& out, uint32_t code);
+  bool maybeVisitStringEncode(Expression*& out, uint32_t code);
+  bool maybeVisitStringConcat(Expression*& out, uint32_t code);
+  bool maybeVisitStringEq(Expression*& out, uint32_t code);
+  bool maybeVisitStringAs(Expression*& out, uint32_t code);
+  bool maybeVisitStringWTF8Advance(Expression*& out, uint32_t code);
+  bool maybeVisitStringWTF16Get(Expression*& out, uint32_t code);
+  bool maybeVisitStringIterNext(Expression*& out, uint32_t code);
+  bool maybeVisitStringIterMove(Expression*& out, uint32_t code);
+  bool maybeVisitStringSliceWTF(Expression*& out, uint32_t code);
+  bool maybeVisitStringSliceIter(Expression*& out, uint32_t code);
   void visitSelect(Select* curr, uint8_t code);
   void visitReturn(Return* curr);
   void visitMemorySize(MemorySize* curr);
@@ -1699,14 +1744,12 @@ public:
   void visitRethrow(Rethrow* curr);
   void visitCallRef(CallRef* curr);
   void visitRefAs(RefAs* curr, uint8_t code);
-  // Let is lowered into a block.
-  void visitLet(Block* curr);
 
-  void throwError(std::string text);
+  [[noreturn]] void throwError(std::string text);
 
   // Struct/Array instructions have an unnecessary heap type that is just for
   // validation (except for the case of unreachability, but that's not a problem
-  // anyhow, we can ignore it there). That is, we also have a reference / rtt
+  // anyhow, we can ignore it there). That is, we also have a reference typed
   // child from which we can infer the type anyhow, and we just need to check
   // that type is the same.
   void validateHeapTypeUsingChild(Expression* child, HeapType heapType);
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index 86de1f7..a29867e 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -82,7 +82,8 @@ public:
   }
 
   static std::unique_ptr<Table> makeTable(Name name,
-                                          Type type = Type::funcref,
+                                          Type type = Type(HeapType::func,
+                                                           Nullable),
                                           Address initial = 0,
                                           Address max = Table::kMaxSize) {
     auto table = std::make_unique<Table>();
@@ -97,7 +98,7 @@ public:
   makeElementSegment(Name name,
                      Name table,
                      Expression* offset = nullptr,
-                     Type type = Type::funcref) {
+                     Type type = Type(HeapType::func, Nullable)) {
     auto seg = std::make_unique<ElementSegment>();
     seg->name = name;
     seg->table = table;
@@ -106,6 +107,37 @@ public:
     return seg;
   }
 
+  static std::unique_ptr<Memory> makeMemory(Name name,
+                                            Address initial = 0,
+                                            Address max = Memory::kMaxSize32,
+                                            bool shared = false,
+                                            Type indexType = Type::i32) {
+    auto memory = std::make_unique<Memory>();
+    memory->name = name;
+    memory->initial = initial;
+    memory->max = max;
+    memory->shared = shared;
+    memory->indexType = indexType;
+    return memory;
+  }
+
+  static std::unique_ptr<DataSegment>
+  makeDataSegment(Name name = "",
+                  Name memory = "",
+                  bool isPassive = false,
+                  Expression* offset = nullptr,
+                  const char* init = "",
+                  Address size = 0) {
+    auto seg = std::make_unique<DataSegment>();
+    seg->name = name;
+    seg->memory = memory;
+    seg->isPassive = isPassive;
+    seg->offset = offset;
+    seg->data.resize(size);
+    std::copy_n(init, size, seg->data.begin());
+    return seg;
+  }
+
   static std::unique_ptr<Export>
   makeExport(Name name, Name value, ExternalKind kind) {
     auto export_ = std::make_unique<Export>();
@@ -335,10 +367,11 @@ public:
   }
   Load* makeLoad(unsigned bytes,
                  bool signed_,
-                 uint32_t offset,
+                 Address offset,
                  unsigned align,
                  Expression* ptr,
-                 Type type) {
+                 Type type,
+                 Name memory) {
     auto* ret = wasm.allocator.alloc<Load>();
     ret->isAtomic = false;
     ret->bytes = bytes;
@@ -347,11 +380,12 @@ public:
     ret->align = align;
     ret->ptr = ptr;
     ret->type = type;
+    ret->memory = memory;
     return ret;
   }
-  Load*
-  makeAtomicLoad(unsigned bytes, uint32_t offset, Expression* ptr, Type type) {
-    Load* load = makeLoad(bytes, false, offset, bytes, ptr, type);
+  Load* makeAtomicLoad(
+    unsigned bytes, Address offset, Expression* ptr, Type type, Name memory) {
+    Load* load = makeLoad(bytes, false, offset, bytes, ptr, type, memory);
     load->isAtomic = true;
     return load;
   }
@@ -359,7 +393,8 @@ public:
                              Expression* expected,
                              Expression* timeout,
                              Type expectedType,
-                             Address offset) {
+                             Address offset,
+                             Name memory) {
     auto* wait = wasm.allocator.alloc<AtomicWait>();
     wait->offset = offset;
     wait->ptr = ptr;
@@ -367,24 +402,29 @@ public:
     wait->timeout = timeout;
     wait->expectedType = expectedType;
     wait->finalize();
+    wait->memory = memory;
     return wait;
   }
-  AtomicNotify*
-  makeAtomicNotify(Expression* ptr, Expression* notifyCount, Address offset) {
+  AtomicNotify* makeAtomicNotify(Expression* ptr,
+                                 Expression* notifyCount,
+                                 Address offset,
+                                 Name memory) {
     auto* notify = wasm.allocator.alloc<AtomicNotify>();
     notify->offset = offset;
     notify->ptr = ptr;
     notify->notifyCount = notifyCount;
     notify->finalize();
+    notify->memory = memory;
     return notify;
   }
   AtomicFence* makeAtomicFence() { return wasm.allocator.alloc<AtomicFence>(); }
   Store* makeStore(unsigned bytes,
-                   uint32_t offset,
+                   Address offset,
                    unsigned align,
                    Expression* ptr,
                    Expression* value,
-                   Type type) {
+                   Type type,
+                   Name memory) {
     auto* ret = wasm.allocator.alloc<Store>();
     ret->isAtomic = false;
     ret->bytes = bytes;
@@ -393,25 +433,28 @@ public:
     ret->ptr = ptr;
     ret->value = value;
     ret->valueType = type;
+    ret->memory = memory;
     ret->finalize();
     assert(ret->value->type.isConcrete() ? ret->value->type == type : true);
     return ret;
   }
   Store* makeAtomicStore(unsigned bytes,
-                         uint32_t offset,
+                         Address offset,
                          Expression* ptr,
                          Expression* value,
-                         Type type) {
-    Store* store = makeStore(bytes, offset, bytes, ptr, value, type);
+                         Type type,
+                         Name memory) {
+    Store* store = makeStore(bytes, offset, bytes, ptr, value, type, memory);
     store->isAtomic = true;
     return store;
   }
   AtomicRMW* makeAtomicRMW(AtomicRMWOp op,
                            unsigned bytes,
-                           uint32_t offset,
+                           Address offset,
                            Expression* ptr,
                            Expression* value,
-                           Type type) {
+                           Type type,
+                           Name memory) {
     auto* ret = wasm.allocator.alloc<AtomicRMW>();
     ret->op = op;
     ret->bytes = bytes;
@@ -420,14 +463,16 @@ public:
     ret->value = value;
     ret->type = type;
     ret->finalize();
+    ret->memory = memory;
     return ret;
   }
   AtomicCmpxchg* makeAtomicCmpxchg(unsigned bytes,
-                                   uint32_t offset,
+                                   Address offset,
                                    Expression* ptr,
                                    Expression* expected,
                                    Expression* replacement,
-                                   Type type) {
+                                   Type type,
+                                   Name memory) {
     auto* ret = wasm.allocator.alloc<AtomicCmpxchg>();
     ret->bytes = bytes;
     ret->offset = offset;
@@ -436,6 +481,7 @@ public:
     ret->replacement = replacement;
     ret->type = type;
     ret->finalize();
+    ret->memory = memory;
     return ret;
   }
   SIMDExtract*
@@ -489,13 +535,17 @@ public:
     ret->finalize();
     return ret;
   }
-  SIMDLoad*
-  makeSIMDLoad(SIMDLoadOp op, Address offset, Address align, Expression* ptr) {
+  SIMDLoad* makeSIMDLoad(SIMDLoadOp op,
+                         Address offset,
+                         Address align,
+                         Expression* ptr,
+                         Name memory) {
     auto* ret = wasm.allocator.alloc<SIMDLoad>();
     ret->op = op;
     ret->offset = offset;
     ret->align = align;
     ret->ptr = ptr;
+    ret->memory = memory;
     ret->finalize();
     return ret;
   }
@@ -504,7 +554,8 @@ public:
                                            Address align,
                                            uint8_t index,
                                            Expression* ptr,
-                                           Expression* vec) {
+                                           Expression* vec,
+                                           Name memory) {
     auto* ret = wasm.allocator.alloc<SIMDLoadStoreLane>();
     ret->op = op;
     ret->offset = offset;
@@ -513,17 +564,20 @@ public:
     ret->ptr = ptr;
     ret->vec = vec;
     ret->finalize();
+    ret->memory = memory;
     return ret;
   }
   MemoryInit* makeMemoryInit(uint32_t segment,
                              Expression* dest,
                              Expression* offset,
-                             Expression* size) {
+                             Expression* size,
+                             Name memory) {
     auto* ret = wasm.allocator.alloc<MemoryInit>();
     ret->segment = segment;
     ret->dest = dest;
     ret->offset = offset;
     ret->size = size;
+    ret->memory = memory;
     ret->finalize();
     return ret;
   }
@@ -533,21 +587,29 @@ public:
     ret->finalize();
     return ret;
   }
-  MemoryCopy*
-  makeMemoryCopy(Expression* dest, Expression* source, Expression* size) {
+  MemoryCopy* makeMemoryCopy(Expression* dest,
+                             Expression* source,
+                             Expression* size,
+                             Name destMemory,
+                             Name sourceMemory) {
     auto* ret = wasm.allocator.alloc<MemoryCopy>();
     ret->dest = dest;
     ret->source = source;
     ret->size = size;
+    ret->destMemory = destMemory;
+    ret->sourceMemory = sourceMemory;
     ret->finalize();
     return ret;
   }
-  MemoryFill*
-  makeMemoryFill(Expression* dest, Expression* value, Expression* size) {
+  MemoryFill* makeMemoryFill(Expression* dest,
+                             Expression* value,
+                             Expression* size,
+                             Name memory) {
     auto* ret = wasm.allocator.alloc<MemoryFill>();
     ret->dest = dest;
     ret->value = value;
     ret->size = size;
+    ret->memory = memory;
     ret->finalize();
     return ret;
   }
@@ -566,8 +628,8 @@ public:
     ret->finalize();
     return ret;
   }
-  Const* makeConstPtr(uint64_t val) {
-    return makeConst(Literal::makeFromInt64(val, wasm.memory.indexType));
+  Const* makeConstPtr(uint64_t val, Type indexType) {
+    return makeConst(Literal::makeFromInt64(val, indexType));
   }
   Binary* makeBinary(BinaryOp op, Expression* left, Expression* right) {
     auto* ret = wasm.allocator.alloc<Binary>();
@@ -602,29 +664,46 @@ public:
     ret->value = value;
     return ret;
   }
-  MemorySize* makeMemorySize() {
+
+  // Some APIs can be told if the memory is 32 or 64-bit. If they are not
+  // informed of that, then the memory they refer to is looked up and that
+  // information fetched from there.
+  enum MemoryInfo { Memory32, Memory64, Unspecified };
+
+  bool isMemory64(Name memoryName, MemoryInfo info) {
+    return info == MemoryInfo::Memory64 || (info == MemoryInfo::Unspecified &&
+                                            wasm.getMemory(memoryName)->is64());
+  }
+
+  MemorySize* makeMemorySize(Name memoryName,
+                             MemoryInfo info = MemoryInfo::Unspecified) {
     auto* ret = wasm.allocator.alloc<MemorySize>();
-    if (wasm.memory.is64()) {
+    if (isMemory64(memoryName, info)) {
       ret->make64();
     }
+    ret->memory = memoryName;
     ret->finalize();
     return ret;
   }
-  MemoryGrow* makeMemoryGrow(Expression* delta) {
+  MemoryGrow* makeMemoryGrow(Expression* delta,
+                             Name memoryName,
+                             MemoryInfo info = MemoryInfo::Unspecified) {
     auto* ret = wasm.allocator.alloc<MemoryGrow>();
-    if (wasm.memory.is64()) {
+    if (isMemory64(memoryName, info)) {
       ret->make64();
     }
     ret->delta = delta;
+    ret->memory = memoryName;
     ret->finalize();
     return ret;
   }
   RefNull* makeRefNull(HeapType type) {
     auto* ret = wasm.allocator.alloc<RefNull>();
-    ret->finalize(Type(type, Nullable));
+    ret->finalize(Type(type.getBottom(), Nullable));
     return ret;
   }
   RefNull* makeRefNull(Type type) {
+    assert(type.isNullable() && type.isNull());
     auto* ret = wasm.allocator.alloc<RefNull>();
     ret->finalize(type);
     return ret;
@@ -789,13 +868,6 @@ public:
     ret->finalize();
     return ret;
   }
-  RefTest* makeRefTest(Expression* ref, Expression* rtt) {
-    auto* ret = wasm.allocator.alloc<RefTest>();
-    ret->ref = ref;
-    ret->rtt = rtt;
-    ret->finalize();
-    return ret;
-  }
   RefTest* makeRefTest(Expression* ref, HeapType intendedType) {
     auto* ret = wasm.allocator.alloc<RefTest>();
     ret->ref = ref;
@@ -803,14 +875,6 @@ public:
     ret->finalize();
     return ret;
   }
-  RefCast* makeRefCast(Expression* ref, Expression* rtt) {
-    auto* ret = wasm.allocator.alloc<RefCast>();
-    ret->ref = ref;
-    ret->rtt = rtt;
-    ret->finalize();
-    return ret;
-  }
-
   RefCast*
   makeRefCast(Expression* ref, HeapType intendedType, RefCast::Safety safety) {
     auto* ret = wasm.allocator.alloc<RefCast>();
@@ -820,13 +884,11 @@ public:
     ret->finalize();
     return ret;
   }
-  BrOn*
-  makeBrOn(BrOnOp op, Name name, Expression* ref, Expression* rtt = nullptr) {
+  BrOn* makeBrOn(BrOnOp op, Name name, Expression* ref) {
     auto* ret = wasm.allocator.alloc<BrOn>();
     ret->op = op;
     ret->name = name;
     ret->ref = ref;
-    ret->rtt = rtt;
     ret->finalize();
     return ret;
   }
@@ -839,37 +901,6 @@ public:
     ret->finalize();
     return ret;
   }
-  RttCanon* makeRttCanon(HeapType heapType) {
-    auto* ret = wasm.allocator.alloc<RttCanon>();
-    ret->type = Type(Rtt(heapType.getDepth(), heapType));
-    ret->finalize();
-    return ret;
-  }
-  RttSub* makeRttSub(HeapType heapType, Expression* parent) {
-    auto* ret = wasm.allocator.alloc<RttSub>();
-    ret->parent = parent;
-    auto parentRtt = parent->type.getRtt();
-    if (parentRtt.hasDepth()) {
-      ret->type = Type(Rtt(parentRtt.depth + 1, heapType));
-    } else {
-      ret->type = Type(Rtt(heapType));
-    }
-    ret->finalize();
-    return ret;
-  }
-  RttSub* makeRttFreshSub(HeapType heapType, Expression* parent) {
-    auto* ret = makeRttSub(heapType, parent);
-    ret->fresh = true;
-    return ret;
-  }
-  template<typename T>
-  StructNew* makeStructNew(Expression* rtt, const T& args) {
-    auto* ret = wasm.allocator.alloc<StructNew>();
-    ret->rtt = rtt;
-    ret->operands.set(args);
-    ret->finalize();
-    return ret;
-  }
   template<typename T> StructNew* makeStructNew(HeapType type, const T& args) {
     auto* ret = wasm.allocator.alloc<StructNew>();
     ret->operands.set(args);
@@ -896,15 +927,6 @@ public:
     return ret;
   }
   ArrayNew*
-  makeArrayNew(Expression* rtt, Expression* size, Expression* init = nullptr) {
-    auto* ret = wasm.allocator.alloc<ArrayNew>();
-    ret->rtt = rtt;
-    ret->size = size;
-    ret->init = init;
-    ret->finalize();
-    return ret;
-  }
-  ArrayNew*
   makeArrayNew(HeapType type, Expression* size, Expression* init = nullptr) {
     auto* ret = wasm.allocator.alloc<ArrayNew>();
     ret->size = size;
@@ -913,11 +935,17 @@ public:
     ret->finalize();
     return ret;
   }
-  ArrayInit* makeArrayInit(Expression* rtt,
-                           const std::vector<Expression*>& values) {
-    auto* ret = wasm.allocator.alloc<ArrayInit>();
-    ret->rtt = rtt;
-    ret->values.set(values);
+  ArrayNewSeg* makeArrayNewSeg(ArrayNewSegOp op,
+                               HeapType type,
+                               Index seg,
+                               Expression* offset,
+                               Expression* size) {
+    auto* ret = wasm.allocator.alloc<ArrayNewSeg>();
+    ret->op = op;
+    ret->segment = seg;
+    ret->offset = offset;
+    ret->size = size;
+    ret->type = Type(type, NonNullable);
     ret->finalize();
     return ret;
   }
@@ -929,11 +957,14 @@ public:
     ret->finalize();
     return ret;
   }
-  ArrayGet*
-  makeArrayGet(Expression* ref, Expression* index, bool signed_ = false) {
+  ArrayGet* makeArrayGet(Expression* ref,
+                         Expression* index,
+                         Type type,
+                         bool signed_ = false) {
     auto* ret = wasm.allocator.alloc<ArrayGet>();
     ret->ref = ref;
     ret->index = index;
+    ret->type = type;
     ret->signed_ = signed_;
     ret->finalize();
     return ret;
@@ -974,6 +1005,123 @@ public:
     ret->finalize();
     return ret;
   }
+  StringNew*
+  makeStringNew(StringNewOp op, Expression* ptr, Expression* length) {
+    auto* ret = wasm.allocator.alloc<StringNew>();
+    ret->op = op;
+    ret->ptr = ptr;
+    ret->length = length;
+    ret->finalize();
+    return ret;
+  }
+  StringNew* makeStringNew(StringNewOp op,
+                           Expression* ptr,
+                           Expression* start,
+                           Expression* end) {
+    auto* ret = wasm.allocator.alloc<StringNew>();
+    ret->op = op;
+    ret->ptr = ptr;
+    ret->start = start;
+    ret->end = end;
+    ret->finalize();
+    return ret;
+  }
+  StringConst* makeStringConst(Name string) {
+    auto* ret = wasm.allocator.alloc<StringConst>();
+    ret->string = string;
+    ret->finalize();
+    return ret;
+  }
+  StringMeasure* makeStringMeasure(StringMeasureOp op, Expression* ref) {
+    auto* ret = wasm.allocator.alloc<StringMeasure>();
+    ret->op = op;
+    ret->ref = ref;
+    ret->finalize();
+    return ret;
+  }
+  StringEncode* makeStringEncode(StringEncodeOp op,
+                                 Expression* ref,
+                                 Expression* ptr,
+                                 Expression* start = nullptr) {
+    auto* ret = wasm.allocator.alloc<StringEncode>();
+    ret->op = op;
+    ret->ref = ref;
+    ret->ptr = ptr;
+    ret->start = start;
+    ret->finalize();
+    return ret;
+  }
+  StringConcat* makeStringConcat(Expression* left, Expression* right) {
+    auto* ret = wasm.allocator.alloc<StringConcat>();
+    ret->left = left;
+    ret->right = right;
+    ret->finalize();
+    return ret;
+  }
+  StringEq* makeStringEq(Expression* left, Expression* right) {
+    auto* ret = wasm.allocator.alloc<StringEq>();
+    ret->left = left;
+    ret->right = right;
+    ret->finalize();
+    return ret;
+  }
+  StringAs* makeStringAs(StringAsOp op, Expression* ref) {
+    auto* ret = wasm.allocator.alloc<StringAs>();
+    ret->op = op;
+    ret->ref = ref;
+    ret->finalize();
+    return ret;
+  }
+  StringWTF8Advance*
+  makeStringWTF8Advance(Expression* ref, Expression* pos, Expression* bytes) {
+    auto* ret = wasm.allocator.alloc<StringWTF8Advance>();
+    ret->ref = ref;
+    ret->pos = pos;
+    ret->bytes = bytes;
+    ret->finalize();
+    return ret;
+  }
+  StringWTF16Get* makeStringWTF16Get(Expression* ref, Expression* pos) {
+    auto* ret = wasm.allocator.alloc<StringWTF16Get>();
+    ret->ref = ref;
+    ret->pos = pos;
+    ret->finalize();
+    return ret;
+  }
+  StringIterNext* makeStringIterNext(Expression* ref) {
+    auto* ret = wasm.allocator.alloc<StringIterNext>();
+    ret->ref = ref;
+    ret->finalize();
+    return ret;
+  }
+  StringIterMove*
+  makeStringIterMove(StringIterMoveOp op, Expression* ref, Expression* num) {
+    auto* ret = wasm.allocator.alloc<StringIterMove>();
+    ret->op = op;
+    ret->ref = ref;
+    ret->num = num;
+    ret->finalize();
+    return ret;
+  }
+  StringSliceWTF* makeStringSliceWTF(StringSliceWTFOp op,
+                                     Expression* ref,
+                                     Expression* start,
+                                     Expression* end) {
+    auto* ret = wasm.allocator.alloc<StringSliceWTF>();
+    ret->op = op;
+    ret->ref = ref;
+    ret->start = start;
+    ret->end = end;
+    ret->finalize();
+    return ret;
+  }
+  StringSliceIter* makeStringSliceIter(Expression* ref, Expression* num) {
+    auto* ret = wasm.allocator.alloc<StringSliceIter>();
+    ret->ref = ref;
+    ret->num = num;
+    ret->finalize();
+    return ret;
+  }
 
   // Additional helpers
 
@@ -997,20 +1145,11 @@ public:
     if (type.isFunction()) {
       return makeRefFunc(value.getFunc(), type.getHeapType());
     }
-    if (type.isRtt()) {
-      return makeRtt(value.type);
+    if (type.isRef() && type.getHeapType() == HeapType::i31) {
+      return makeI31New(makeConst(value.geti31()));
     }
     TODO_SINGLE_COMPOUND(type);
-    switch (type.getBasic()) {
-      case Type::anyref:
-      case Type::eqref:
-        assert(value.isNull() && "unexpected non-null reference type literal");
-        return makeRefNull(type);
-      case Type::i31ref:
-        return makeI31New(makeConst(value.geti31()));
-      default:
-        WASM_UNREACHABLE("invalid constant expression");
-    }
+    WASM_UNREACHABLE("unsupported constant expression");
   }
 
   Expression* makeConstantExpression(Literals values) {
@@ -1026,18 +1165,6 @@ public:
     }
   }
 
-  // Given a type, creates an RTT expression of that type, using a combination
-  // of rtt.canon and rtt.subs.
-  Expression* makeRtt(Type type) {
-    Expression* ret = makeRttCanon(type.getHeapType());
-    if (type.getRtt().hasDepth()) {
-      for (Index i = 0; i < type.getRtt().depth; i++) {
-        ret = makeRttSub(type.getHeapType(), ret);
-      }
-    }
-    return ret;
-  }
-
   // Additional utility functions for building on top of nodes
   // Convenient to have these on Builder, as it has allocation built in
 
@@ -1153,10 +1280,13 @@ public:
     if (curr->type.isTuple() && curr->type.isDefaultable()) {
       return makeConstantExpression(Literal::makeZeros(curr->type));
     }
-    if (curr->type.isNullable()) {
+    if (curr->type.isNullable() && curr->type.isNull()) {
       return ExpressionManipulator::refNull(curr, curr->type);
     }
-    if (curr->type.isFunction() || !curr->type.isBasic()) {
+    if (curr->type.isRef() && curr->type.getHeapType() == HeapType::i31) {
+      return makeI31New(makeConst(0));
+    }
+    if (!curr->type.isBasic()) {
       // We can't do any better, keep the original.
       return curr;
     }
@@ -1181,15 +1311,6 @@ public:
         value = Literal(bytes.data());
         break;
       }
-      case Type::funcref:
-        WASM_UNREACHABLE("handled above");
-      case Type::anyref:
-      case Type::eqref:
-        return ExpressionManipulator::refNull(curr, curr->type);
-      case Type::i31ref:
-        return makeI31New(makeConst(0));
-      case Type::dataref:
-        return curr;
       case Type::none:
         return ExpressionManipulator::nop(curr);
       case Type::unreachable:
@@ -1213,48 +1334,13 @@ public:
   ValidatingBuilder(Module& wasm, size_t line, size_t col)
     : Builder(wasm), line(line), col(col) {}
 
-  Expression* validateAndMakeBrOn(BrOnOp op,
-                                  Name name,
-                                  Expression* ref,
-                                  Expression* rtt = nullptr) {
-    if (op == BrOnCast) {
-      if (rtt->type == Type::unreachable) {
-        // An unreachable rtt is not supported: the text and binary formats do
-        // not provide the type, so if it's unreachable we should not even
-        // create a br_on_cast in such a case, as we'd have no idea what it
-        // casts to.
-        return makeSequence(makeDrop(ref), rtt);
-      }
-    }
+  Expression* validateAndMakeBrOn(BrOnOp op, Name name, Expression* ref) {
     if (op == BrOnNull) {
       if (!ref->type.isRef() && ref->type != Type::unreachable) {
         throw ParseException("Invalid ref for br_on_null", line, col);
       }
     }
-    return makeBrOn(op, name, ref, rtt);
-  }
-
-  template<typename T>
-  Expression* validateAndMakeCallRef(Expression* target,
-                                     const T& args,
-                                     bool isReturn = false) {
-    if (!target->type.isRef()) {
-      if (target->type == Type::unreachable) {
-        // An unreachable target is not supported. Similiar to br_on_cast, just
-        // emit an unreachable sequence, since we don't have enough information
-        // to create a full call_ref.
-        auto* block = makeBlock(args);
-        block->list.push_back(target);
-        block->finalize(Type::unreachable);
-        return block;
-      }
-      throw ParseException("Non-reference type for a call_ref", line, col);
-    }
-    auto heapType = target->type.getHeapType();
-    if (!heapType.isSignature()) {
-      throw ParseException("Invalid reference type for a call_ref", line, col);
-    }
-    return makeCallRef(target, args, heapType.getSignature().results, isReturn);
+    return makeBrOn(op, name, ref);
   }
 };
 
diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def
index 68e23fd..85a088d 100644
--- a/src/wasm-delegations-fields.def
+++ b/src/wasm-delegations-fields.def
@@ -266,6 +266,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_ADDRESS(Load, offset);
     DELEGATE_FIELD_ADDRESS(Load, align);
     DELEGATE_FIELD_INT(Load, isAtomic);
+    DELEGATE_FIELD_NAME(Load, memory);
     DELEGATE_END(Load);
     break;
   }
@@ -278,6 +279,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_ADDRESS(Store, align);
     DELEGATE_FIELD_INT(Store, isAtomic);
     DELEGATE_FIELD_TYPE(Store, valueType);
+    DELEGATE_FIELD_NAME(Store, memory);
     DELEGATE_END(Store);
     break;
   }
@@ -288,6 +290,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_INT(AtomicRMW, op);
     DELEGATE_FIELD_INT(AtomicRMW, bytes);
     DELEGATE_FIELD_ADDRESS(AtomicRMW, offset);
+    DELEGATE_FIELD_NAME(AtomicRMW, memory);
     DELEGATE_END(AtomicRMW);
     break;
   }
@@ -298,6 +301,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_CHILD(AtomicCmpxchg, ptr);
     DELEGATE_FIELD_INT(AtomicCmpxchg, bytes);
     DELEGATE_FIELD_ADDRESS(AtomicCmpxchg, offset);
+    DELEGATE_FIELD_NAME(AtomicCmpxchg, memory);
     DELEGATE_END(AtomicCmpxchg);
     break;
   }
@@ -308,6 +312,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_CHILD(AtomicWait, ptr);
     DELEGATE_FIELD_ADDRESS(AtomicWait, offset);
     DELEGATE_FIELD_TYPE(AtomicWait, expectedType);
+    DELEGATE_FIELD_NAME(AtomicWait, memory);
     DELEGATE_END(AtomicWait);
     break;
   }
@@ -316,6 +321,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_CHILD(AtomicNotify, notifyCount);
     DELEGATE_FIELD_CHILD(AtomicNotify, ptr);
     DELEGATE_FIELD_ADDRESS(AtomicNotify, offset);
+    DELEGATE_FIELD_NAME(AtomicNotify, memory);
     DELEGATE_END(AtomicNotify);
     break;
   }
@@ -373,6 +379,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_INT(SIMDLoad, op);
     DELEGATE_FIELD_ADDRESS(SIMDLoad, offset);
     DELEGATE_FIELD_ADDRESS(SIMDLoad, align);
+    DELEGATE_FIELD_NAME(SIMDLoad, memory);
     DELEGATE_END(SIMDLoad);
     break;
   }
@@ -384,6 +391,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_ADDRESS(SIMDLoadStoreLane, offset);
     DELEGATE_FIELD_ADDRESS(SIMDLoadStoreLane, align);
     DELEGATE_FIELD_INT(SIMDLoadStoreLane, index);
+    DELEGATE_FIELD_NAME(SIMDLoadStoreLane, memory);
     DELEGATE_END(SIMDLoadStoreLane);
     break;
   }
@@ -393,6 +401,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_CHILD(MemoryInit, offset);
     DELEGATE_FIELD_CHILD(MemoryInit, dest);
     DELEGATE_FIELD_INT(MemoryInit, segment);
+    DELEGATE_FIELD_NAME(MemoryInit, memory);
     DELEGATE_END(MemoryInit);
     break;
   }
@@ -407,6 +416,8 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_CHILD(MemoryCopy, size);
     DELEGATE_FIELD_CHILD(MemoryCopy, source);
     DELEGATE_FIELD_CHILD(MemoryCopy, dest);
+    DELEGATE_FIELD_NAME(MemoryCopy, sourceMemory);
+    DELEGATE_FIELD_NAME(MemoryCopy, destMemory);
     DELEGATE_END(MemoryCopy);
     break;
   }
@@ -415,6 +426,7 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_CHILD(MemoryFill, size);
     DELEGATE_FIELD_CHILD(MemoryFill, value);
     DELEGATE_FIELD_CHILD(MemoryFill, dest);
+    DELEGATE_FIELD_NAME(MemoryFill, memory);
     DELEGATE_END(MemoryFill);
     break;
   }
@@ -462,6 +474,7 @@ switch (DELEGATE_ID) {
   case Expression::Id::MemorySizeId: {
     DELEGATE_START(MemorySize);
     DELEGATE_FIELD_TYPE(MemorySize, ptrType);
+    DELEGATE_FIELD_NAME(MemorySize, memory);
     DELEGATE_END(MemorySize);
     break;
   }
@@ -469,6 +482,7 @@ switch (DELEGATE_ID) {
     DELEGATE_START(MemoryGrow);
     DELEGATE_FIELD_TYPE(MemoryGrow, ptrType);
     DELEGATE_FIELD_CHILD(MemoryGrow, delta);
+    DELEGATE_FIELD_NAME(MemoryGrow, memory);
     DELEGATE_END(MemoryGrow);
     break;
   }
@@ -601,7 +615,6 @@ switch (DELEGATE_ID) {
   case Expression::Id::RefTestId: {
     DELEGATE_START(RefTest);
     DELEGATE_FIELD_HEAPTYPE(RefTest, intendedType);
-    DELEGATE_FIELD_OPTIONAL_CHILD(RefTest, rtt);
     DELEGATE_FIELD_CHILD(RefTest, ref);
     DELEGATE_END(RefTest);
     break;
@@ -609,7 +622,6 @@ switch (DELEGATE_ID) {
   case Expression::Id::RefCastId: {
     DELEGATE_START(RefCast);
     DELEGATE_FIELD_HEAPTYPE(RefCast, intendedType);
-    DELEGATE_FIELD_OPTIONAL_CHILD(RefCast, rtt);
     DELEGATE_FIELD_CHILD(RefCast, ref);
     DELEGATE_END(RefCast);
     break;
@@ -619,26 +631,12 @@ switch (DELEGATE_ID) {
     DELEGATE_FIELD_INT(BrOn, op);
     DELEGATE_FIELD_SCOPE_NAME_USE(BrOn, name);
     DELEGATE_FIELD_HEAPTYPE(BrOn, intendedType);
-    DELEGATE_FIELD_OPTIONAL_CHILD(BrOn, rtt);
     DELEGATE_FIELD_CHILD(BrOn, ref);
     DELEGATE_END(BrOn);
     break;
   }
-  case Expression::Id::RttCanonId: {
-    DELEGATE_START(RttCanon);
-    DELEGATE_END(RttCanon);
-    break;
-  }
-  case Expression::Id::RttSubId: {
-    DELEGATE_START(RttSub);
-    DELEGATE_FIELD_CHILD(RttSub, parent);
-    DELEGATE_FIELD_INT(RttSub, fresh);
-    DELEGATE_END(RttSub);
-    break;
-  }
   case Expression::Id::StructNewId: {
     DELEGATE_START(StructNew);
-    DELEGATE_FIELD_OPTIONAL_CHILD(StructNew, rtt);
     DELEGATE_FIELD_CHILD_VECTOR(StructNew, operands);
     DELEGATE_END(StructNew);
     break;
@@ -661,15 +659,22 @@ switch (DELEGATE_ID) {
   }
   case Expression::Id::ArrayNewId: {
     DELEGATE_START(ArrayNew);
-    DELEGATE_FIELD_OPTIONAL_CHILD(ArrayNew, rtt);
     DELEGATE_FIELD_CHILD(ArrayNew, size);
     DELEGATE_FIELD_OPTIONAL_CHILD(ArrayNew, init);
     DELEGATE_END(ArrayNew);
     break;
   }
+  case Expression::Id::ArrayNewSegId: {
+    DELEGATE_START(ArrayNewSeg);
+    DELEGATE_FIELD_INT(ArrayNewSeg, op);
+    DELEGATE_FIELD_INT(ArrayNewSeg, segment);
+    DELEGATE_FIELD_CHILD(ArrayNewSeg, size);
+    DELEGATE_FIELD_CHILD(ArrayNewSeg, offset);
+    DELEGATE_END(ArrayNewSeg);
+    break;
+  }
   case Expression::Id::ArrayInitId: {
     DELEGATE_START(ArrayInit);
-    DELEGATE_FIELD_OPTIONAL_CHILD(ArrayInit, rtt);
     DELEGATE_FIELD_CHILD_VECTOR(ArrayInit, values);
     DELEGATE_END(ArrayInit);
     break;
@@ -713,6 +718,104 @@ switch (DELEGATE_ID) {
     DELEGATE_END(RefAs);
     break;
   }
+  case Expression::Id::StringNewId: {
+    DELEGATE_START(StringNew);
+    DELEGATE_FIELD_INT(StringNew, op);
+    DELEGATE_FIELD_OPTIONAL_CHILD(StringNew, end);
+    DELEGATE_FIELD_OPTIONAL_CHILD(StringNew, start);
+    DELEGATE_FIELD_OPTIONAL_CHILD(StringNew, length);
+    DELEGATE_FIELD_CHILD(StringNew, ptr);
+    DELEGATE_END(StringNew);
+    break;
+  }
+  case Expression::Id::StringConstId: {
+    DELEGATE_START(StringConst);
+    DELEGATE_FIELD_NAME(StringConst, string);
+    DELEGATE_END(StringConst);
+    break;
+  }
+  case Expression::Id::StringMeasureId: {
+    DELEGATE_START(StringMeasure);
+    DELEGATE_FIELD_INT(StringMeasure, op);
+    DELEGATE_FIELD_CHILD(StringMeasure, ref);
+    DELEGATE_END(StringMeasure);
+    break;
+  }
+  case Expression::Id::StringEncodeId: {
+    DELEGATE_START(StringEncode);
+    DELEGATE_FIELD_INT(StringEncode, op);
+    DELEGATE_FIELD_OPTIONAL_CHILD(StringEncode, start);
+    DELEGATE_FIELD_CHILD(StringEncode, ptr);
+    DELEGATE_FIELD_CHILD(StringEncode, ref);
+    DELEGATE_END(StringEncode);
+    break;
+  }
+  case Expression::Id::StringConcatId: {
+    DELEGATE_START(StringConcat);
+    DELEGATE_FIELD_CHILD(StringConcat, right);
+    DELEGATE_FIELD_CHILD(StringConcat, left);
+    DELEGATE_END(StringConcat);
+    break;
+  }
+  case Expression::Id::StringEqId: {
+    DELEGATE_START(StringEq);
+    DELEGATE_FIELD_CHILD(StringEq, right);
+    DELEGATE_FIELD_CHILD(StringEq, left);
+    DELEGATE_END(StringEq);
+    break;
+  }
+  case Expression::Id::StringAsId: {
+    DELEGATE_START(StringAs);
+    DELEGATE_FIELD_INT(StringAs, op);
+    DELEGATE_FIELD_CHILD(StringAs, ref);
+    DELEGATE_END(StringAs);
+    break;
+  }
+  case Expression::Id::StringWTF8AdvanceId: {
+    DELEGATE_START(StringWTF8Advance);
+    DELEGATE_FIELD_CHILD(StringWTF8Advance, bytes);
+    DELEGATE_FIELD_CHILD(StringWTF8Advance, pos);
+    DELEGATE_FIELD_CHILD(StringWTF8Advance, ref);
+    DELEGATE_END(StringWTF8Advance);
+    break;
+  }
+  case Expression::Id::StringWTF16GetId: {
+    DELEGATE_START(StringWTF16Get);
+    DELEGATE_FIELD_CHILD(StringWTF16Get, pos);
+    DELEGATE_FIELD_CHILD(StringWTF16Get, ref);
+    DELEGATE_END(StringWTF16Get);
+    break;
+  }
+  case Expression::Id::StringIterNextId: {
+    DELEGATE_START(StringIterNext);
+    DELEGATE_FIELD_CHILD(StringIterNext, ref);
+    DELEGATE_END(StringIterNext);
+    break;
+  }
+  case Expression::Id::StringIterMoveId: {
+    DELEGATE_START(StringIterMove);
+    DELEGATE_FIELD_INT(StringIterMove, op);
+    DELEGATE_FIELD_CHILD(StringIterMove, num);
+    DELEGATE_FIELD_CHILD(StringIterMove, ref);
+    DELEGATE_END(StringIterMove);
+    break;
+  }
+  case Expression::Id::StringSliceWTFId: {
+    DELEGATE_START(StringSliceWTF);
+    DELEGATE_FIELD_INT(StringSliceWTF, op);
+    DELEGATE_FIELD_CHILD(StringSliceWTF, end);
+    DELEGATE_FIELD_CHILD(StringSliceWTF, start);
+    DELEGATE_FIELD_CHILD(StringSliceWTF, ref);
+    DELEGATE_END(StringSliceWTF);
+    break;
+  }
+  case Expression::Id::StringSliceIterId: {
+    DELEGATE_START(StringSliceIter);
+    DELEGATE_FIELD_CHILD(StringSliceIter, num);
+    DELEGATE_FIELD_CHILD(StringSliceIter, ref);
+    DELEGATE_END(StringSliceIter);
+    break;
+  }
 }
 
 #undef DELEGATE_ID
diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def
index c3f0467..dbdef3a 100644
--- a/src/wasm-delegations.def
+++ b/src/wasm-delegations.def
@@ -73,17 +73,29 @@ DELEGATE(CallRef);
 DELEGATE(RefTest);
 DELEGATE(RefCast);
 DELEGATE(BrOn);
-DELEGATE(RttCanon);
-DELEGATE(RttSub);
 DELEGATE(StructNew);
 DELEGATE(StructGet);
 DELEGATE(StructSet);
 DELEGATE(ArrayNew);
+DELEGATE(ArrayNewSeg);
 DELEGATE(ArrayInit);
 DELEGATE(ArrayGet);
 DELEGATE(ArraySet);
 DELEGATE(ArrayLen);
 DELEGATE(ArrayCopy);
 DELEGATE(RefAs);
+DELEGATE(StringNew);
+DELEGATE(StringConst);
+DELEGATE(StringMeasure);
+DELEGATE(StringEncode);
+DELEGATE(StringConcat);
+DELEGATE(StringEq);
+DELEGATE(StringAs);
+DELEGATE(StringWTF8Advance);
+DELEGATE(StringWTF16Get);
+DELEGATE(StringIterNext);
+DELEGATE(StringIterMove);
+DELEGATE(StringSliceWTF);
+DELEGATE(StringSliceIter);
 
 #undef DELEGATE
diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h
index 3c9cb07..4d20b87 100644
--- a/src/wasm-emscripten.h
+++ b/src/wasm-emscripten.h
@@ -33,16 +33,8 @@ public:
     : wasm(wasm), builder(wasm), stackPointerOffset(stackPointerOffset),
       useStackPointerGlobal(stackPointerOffset == 0) {}
 
-  std::string generateEmscriptenMetadata();
-
   void fixInvokeFunctionNames();
 
-  // clang uses name mangling to rename the argc/argv form of main to
-  // __main_argc_argv.  Emscripten in non-standalone mode expects that function
-  // to be exported as main.  This function renames __main_argc_argv to main
-  // as expected by emscripten.
-  void renameMainArgcArgv();
-
   // Emits the data segments to a file. The file contains data from address base
   // onwards (we must pass in base, as we can't tell it from the wasm - the
   // first segment may start after a run of zeros, but we need those zeros in
diff --git a/src/wasm-features.h b/src/wasm-features.h
index 64d831e..8818689 100644
--- a/src/wasm-features.h
+++ b/src/wasm-features.h
@@ -23,9 +23,11 @@
 #include "compiler-support.h"
 #include "support/utilities.h"
 
+namespace wasm {
+
 struct FeatureSet {
   enum Feature : uint32_t {
-    MVP = 0,
+    None = 0,
     Atomics = 1 << 0,
     MutableGlobals = 1 << 1,
     TruncSat = 1 << 2,
@@ -38,16 +40,21 @@ struct FeatureSet {
     Multivalue = 1 << 9,
     GC = 1 << 10,
     Memory64 = 1 << 11,
-    TypedFunctionReferences = 1 << 12,
     // TODO: Remove this feature when the wasm spec stabilizes.
-    GCNNLocals = 1 << 13,
-    RelaxedSIMD = 1 << 14,
-    ExtendedConst = 1 << 15,
+    GCNNLocals = 1 << 12,
+    RelaxedSIMD = 1 << 13,
+    ExtendedConst = 1 << 14,
+    Strings = 1 << 15,
+    MultiMemories = 1 << 16,
+    MVP = None,
+    // Keep in sync with llvm default features:
+    // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153
+    Default = SignExt | MutableGlobals,
     // GCNNLocals are opt-in: merely asking for "All" does not apply them. To
     // get all possible values use AllPossible. See setAll() below for more
     // details.
-    All = ((1 << 16) - 1) & ~GCNNLocals,
-    AllPossible = (1 << 16) - 1,
+    All = ((1 << 17) - 1) & ~GCNNLocals,
+    AllPossible = (1 << 17) - 1,
   };
 
   static std::string toString(Feature f) {
@@ -76,14 +83,16 @@ struct FeatureSet {
         return "gc";
       case Memory64:
         return "memory64";
-      case TypedFunctionReferences:
-        return "typed-function-references";
       case GCNNLocals:
         return "gc-nn-locals";
       case RelaxedSIMD:
         return "relaxed-simd";
       case ExtendedConst:
         return "extended-const";
+      case Strings:
+        return "strings";
+      case MultiMemories:
+        return "multi-memories";
       default:
         WASM_UNREACHABLE("unexpected feature");
     }
@@ -104,7 +113,7 @@ struct FeatureSet {
     return ret;
   }
 
-  FeatureSet() : features(MVP) {}
+  FeatureSet() : features(None) {}
   FeatureSet(uint32_t features) : features(features) {}
   operator uint32_t() const { return features; }
 
@@ -124,12 +133,11 @@ struct FeatureSet {
   bool hasMultivalue() const { return (features & Multivalue) != 0; }
   bool hasGC() const { return (features & GC) != 0; }
   bool hasMemory64() const { return (features & Memory64) != 0; }
-  bool hasTypedFunctionReferences() const {
-    return (features & TypedFunctionReferences) != 0;
-  }
   bool hasGCNNLocals() const { return (features & GCNNLocals) != 0; }
   bool hasRelaxedSIMD() const { return (features & RelaxedSIMD) != 0; }
   bool hasExtendedConst() const { return (features & ExtendedConst) != 0; }
+  bool hasStrings() const { return (features & Strings) != 0; }
+  bool hasMultiMemories() const { return (features & MultiMemories) != 0; }
   bool hasAll() const { return (features & AllPossible) != 0; }
 
   void set(FeatureSet f, bool v = true) {
@@ -147,12 +155,11 @@ struct FeatureSet {
   void setMultivalue(bool v = true) { set(Multivalue, v); }
   void setGC(bool v = true) { set(GC, v); }
   void setMemory64(bool v = true) { set(Memory64, v); }
-  void setTypedFunctionReferences(bool v = true) {
-    set(TypedFunctionReferences, v);
-  }
   void setGCNNLocals(bool v = true) { set(GCNNLocals, v); }
   void setRelaxedSIMD(bool v = true) { set(RelaxedSIMD, v); }
   void setExtendedConst(bool v = true) { set(ExtendedConst, v); }
+  void setStrings(bool v = true) { set(Strings, v); }
+  void setMultiMemories(bool v = true) { set(MultiMemories, v); }
   void setMVP() { features = MVP; }
   void setAll() {
     // Do not set GCNNLocals, which forces the user to opt in to that feature
@@ -200,4 +207,6 @@ struct FeatureSet {
   uint32_t features;
 };
 
+} // namespace wasm
+
 #endif // wasm_features_h
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 641684f..fec5724 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -28,6 +28,7 @@
 #include <sstream>
 #include <variant>
 
+#include "ir/intrinsics.h"
 #include "ir/module-utils.h"
 #include "support/bits.h"
 #include "support/safe_integer.h"
@@ -43,8 +44,6 @@ struct WasmException {
 };
 std::ostream& operator<<(std::ostream& o, const WasmException& exn);
 
-using namespace cashew;
-
 // Utilities
 
 extern Name WASM, RETURN_FLOW, NONCONSTANT_FLOW;
@@ -81,7 +80,7 @@ public:
 
   void clearIf(Name target) {
     if (breakTo == target) {
-      breakTo.clear();
+      breakTo = Name{};
     }
   }
 
@@ -1018,8 +1017,6 @@ public:
 
       case DotI8x16I7x16SToVecI16x8:
         return left.dotSI8x16toI16x8(right);
-      case DotI8x16I7x16UToVecI16x8:
-        return left.dotUI8x16toI16x8(right);
 
       case InvalidBinary:
         WASM_UNREACHABLE("invalid binary op");
@@ -1337,7 +1334,7 @@ public:
   Flow visitCallRef(CallRef* curr) { WASM_UNREACHABLE("unimp"); }
   Flow visitRefNull(RefNull* curr) {
     NOTE_ENTER("RefNull");
-    return Literal::makeNull(curr->type);
+    return Literal::makeNull(curr->type.getHeapType());
   }
   Flow visitRefIs(RefIs* curr) {
     NOTE_ENTER("RefIs");
@@ -1351,12 +1348,11 @@ public:
       case RefIsNull:
         return Literal(value.isNull());
       case RefIsFunc:
-        return Literal(!value.isNull() && value.type.isFunction());
+        return Literal(value.type.isFunction());
       case RefIsData:
-        return Literal(!value.isNull() && value.isData());
+        return Literal(value.isData());
       case RefIsI31:
-        return Literal(!value.isNull() &&
-                       value.type.getHeapType() == HeapType::i31);
+        return Literal(value.type.getHeapType() == HeapType::i31);
       default:
         WASM_UNREACHABLE("unimplemented ref.is_*");
     }
@@ -1364,7 +1360,7 @@ public:
   Flow visitRefFunc(RefFunc* curr) {
     NOTE_ENTER("RefFunc");
     NOTE_NAME(curr->func);
-    return Literal::makeFunc(curr->func, curr->type);
+    return Literal::makeFunc(curr->func, curr->type.getHeapType());
   }
   Flow visitRefEq(RefEq* curr) {
     NOTE_ENTER("RefEq");
@@ -1421,6 +1417,9 @@ public:
     }
     const auto& value = flow.getSingleValue();
     NOTE_EVAL1(value);
+    if (value.isNull()) {
+      trap("null ref");
+    }
     return Literal(value.geti31(curr->signed_));
   }
 
@@ -1465,19 +1464,6 @@ public:
     if (ref.breaking()) {
       return typename Cast::Breaking{ref};
     }
-    // The RTT value for the type we are trying to cast to.
-    Literal intendedRtt;
-    if (curr->rtt) {
-      // This is a dynamic check with an RTT.
-      Flow rtt = self()->visit(curr->rtt);
-      if (rtt.breaking()) {
-        return typename Cast::Breaking{rtt};
-      }
-      intendedRtt = rtt.getSingleValue();
-    } else {
-      // If there is no explicit RTT, use the canonical RTT for the static type.
-      intendedRtt = Literal::makeCanonicalRtt(curr->intendedType);
-    }
     Literal original = ref.getSingleValue();
     if (original.isNull()) {
       return typename Cast::Null{original};
@@ -1487,30 +1473,10 @@ public:
     if (!original.isData() && !original.isFunction()) {
       return typename Cast::Failure{original};
     }
-    Literal actualRtt;
-    if (original.isFunction()) {
-      // Function references always have the canonical RTTs of the functions
-      // they reference. We must have a module to look up the function's type to
-      // get that canonical RTT.
-      auto* func =
-        module ? module->getFunctionOrNull(original.getFunc()) : nullptr;
-      if (!func) {
-        return typename Cast::Breaking{NONCONSTANT_FLOW};
-      }
-      actualRtt = Literal::makeCanonicalRtt(func->type);
-    } else {
-      assert(original.isData());
-      actualRtt = original.getGCData()->rtt;
-    };
-    // We have the actual and intended RTTs, so perform the cast.
-    if (actualRtt.isSubRtt(intendedRtt)) {
-      Type resultType(intendedRtt.type.getHeapType(), NonNullable);
-      if (original.isFunction()) {
-        return typename Cast::Success{Literal{original.getFunc(), resultType}};
-      } else {
-        return
-          typename Cast::Success{Literal(original.getGCData(), resultType)};
-      }
+    HeapType actualType = original.type.getHeapType();
+    // We have the actual and intended types, so perform the cast.
+    if (HeapType::isSubType(actualType, curr->intendedType)) {
+      return typename Cast::Success{original};
     } else {
       return typename Cast::Failure{original};
     }
@@ -1531,7 +1497,7 @@ public:
     if (auto* breaking = cast.getBreaking()) {
       return *breaking;
     } else if (cast.getNull()) {
-      return Literal::makeNull(Type(curr->type.getHeapType(), Nullable));
+      return Literal::makeNull(curr->type.getHeapType());
     } else if (auto* result = cast.getSuccess()) {
       return *result;
     }
@@ -1627,33 +1593,8 @@ public:
     }
     return {value};
   }
-  Flow visitRttCanon(RttCanon* curr) {
-    return Literal::makeCanonicalRtt(curr->type.getHeapType());
-  }
-  Flow visitRttSub(RttSub* curr) {
-    Flow parent = self()->visit(curr->parent);
-    if (parent.breaking()) {
-      return parent;
-    }
-    auto parentValue = parent.getSingleValue();
-    auto newSupers = std::make_unique<RttSupers>(parentValue.getRttSupers());
-    newSupers->push_back(parentValue.type.getHeapType());
-    if (curr->fresh) {
-      newSupers->back().makeFresh();
-    }
-    return Literal(std::move(newSupers), curr->type);
-  }
-
   Flow visitStructNew(StructNew* curr) {
     NOTE_ENTER("StructNew");
-    Literal rttVal;
-    if (curr->rtt) {
-      Flow rtt = self()->visit(curr->rtt);
-      if (rtt.breaking()) {
-        return rtt;
-      }
-      rttVal = rtt.getSingleValue();
-    }
     if (curr->type == Type::unreachable) {
       // We cannot proceed to compute the heap type, as there isn't one. Just
       // find why we are unreachable, and stop there.
@@ -1679,10 +1620,8 @@ public:
         data[i] = value.getSingleValue();
       }
     }
-    if (!curr->rtt) {
-      rttVal = Literal::makeCanonicalRtt(heapType);
-    }
-    return Literal(std::make_shared<GCData>(rttVal, data), curr->type);
+    return Literal(std::make_shared<GCData>(curr->type.getHeapType(), data),
+                   curr->type.getHeapType());
   }
   Flow visitStructGet(StructGet* curr) {
     NOTE_ENTER("StructGet");
@@ -1725,14 +1664,6 @@ public:
 
   Flow visitArrayNew(ArrayNew* curr) {
     NOTE_ENTER("ArrayNew");
-    Literal rttVal;
-    if (curr->rtt) {
-      Flow rtt = self()->visit(curr->rtt);
-      if (rtt.breaking()) {
-        return rtt;
-      }
-      rttVal = rtt.getSingleValue();
-    }
     auto size = self()->visit(curr->size);
     if (size.breaking()) {
       return size;
@@ -1766,21 +1697,12 @@ public:
         data[i] = value;
       }
     }
-    if (!curr->rtt) {
-      rttVal = Literal::makeCanonicalRtt(heapType);
-    }
-    return Literal(std::make_shared<GCData>(rttVal, data), curr->type);
+    return Literal(std::make_shared<GCData>(curr->type.getHeapType(), data),
+                   curr->type.getHeapType());
   }
+  Flow visitArrayNewSeg(ArrayNewSeg* curr) { WASM_UNREACHABLE("unimp"); }
   Flow visitArrayInit(ArrayInit* curr) {
     NOTE_ENTER("ArrayInit");
-    Literal rttVal;
-    if (curr->rtt) {
-      Flow rtt = self()->visit(curr->rtt);
-      if (rtt.breaking()) {
-        return rtt;
-      }
-      rttVal = rtt.getSingleValue();
-    }
     Index num = curr->values.size();
     if (num >= ArrayLimit) {
       hostLimit("allocation failure");
@@ -1806,10 +1728,8 @@ public:
       }
       data[i] = truncateForPacking(value.getSingleValue(), field);
     }
-    if (!curr->rtt) {
-      rttVal = Literal::makeCanonicalRtt(heapType);
-    }
-    return Literal(std::make_shared<GCData>(rttVal, data), curr->type);
+    return Literal(std::make_shared<GCData>(curr->type.getHeapType(), data),
+                   curr->type.getHeapType());
   }
   Flow visitArrayGet(ArrayGet* curr) {
     NOTE_ENTER("ArrayGet");
@@ -1952,11 +1872,31 @@ public:
           trap("not an i31");
         }
         break;
+      case ExternInternalize:
+      case ExternExternalize:
+        WASM_UNREACHABLE("unimplemented extern conversion");
       default:
         WASM_UNREACHABLE("unimplemented ref.as_*");
     }
     return value;
   }
+  Flow visitStringNew(StringNew* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringConst(StringConst* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringMeasure(StringMeasure* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringEncode(StringEncode* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringConcat(StringConcat* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringEq(StringEq* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringAs(StringAs* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringWTF8Advance(StringWTF8Advance* curr) {
+    WASM_UNREACHABLE("unimp");
+  }
+  Flow visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringIterNext(StringIterNext* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringIterMove(StringIterMove* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("unimp"); }
+  Flow visitStringSliceIter(StringSliceIter* curr) {
+    WASM_UNREACHABLE("unimp");
+  }
 
   virtual void trap(const char* why) { WASM_UNREACHABLE("unimp"); }
 
@@ -2027,7 +1967,7 @@ public:
   // Flags indicating special requirements, for example whether we are just
   // evaluating (default), also going to replace the expression afterwards or
   // executing in a function-parallel scenario. See FlagValues.
-  typedef uint32_t Flags;
+  using Flags = uint32_t;
 
   // Indicates no limit of maxDepth or maxLoopIterations.
   static const Index NO_LIMIT = 0;
@@ -2248,6 +2188,10 @@ public:
     NOTE_ENTER("SIMDLoadStoreLane");
     return Flow(NONCONSTANT_FLOW);
   }
+  Flow visitArrayNewSeg(ArrayNewSeg* curr) {
+    NOTE_ENTER("ArrayNewSeg");
+    return Flow(NONCONSTANT_FLOW);
+  }
   Flow visitPop(Pop* curr) {
     NOTE_ENTER("Pop");
     return Flow(NONCONSTANT_FLOW);
@@ -2260,6 +2204,40 @@ public:
     NOTE_ENTER("Rethrow");
     return Flow(NONCONSTANT_FLOW);
   }
+  Flow visitStringNew(StringNew* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitStringConst(StringConst* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitStringMeasure(StringMeasure* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitStringEncode(StringEncode* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitStringConcat(StringConcat* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitStringEq(StringEq* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitStringAs(StringAs* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitStringWTF8Advance(StringWTF8Advance* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitStringWTF16Get(StringWTF16Get* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitStringIterNext(StringIterNext* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitStringIterMove(StringIterMove* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitStringSliceWTF(StringSliceWTF* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitStringSliceIter(StringSliceIter* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
+  Flow visitRefAs(RefAs* curr) {
+    // TODO: Remove this once interpretation is implemented.
+    if (curr->op == ExternInternalize || curr->op == ExternExternalize) {
+      return Flow(NONCONSTANT_FLOW);
+    }
+    return ExpressionRunner<SubType>::visitRefAs(curr);
+  }
 
   void trap(const char* why) override { throw NonconstantException(); }
 
@@ -2307,7 +2285,7 @@ public:
                                Literals& arguments,
                                Type result,
                                SubType& instance) = 0;
-    virtual bool growMemory(Address oldSize, Address newSize) = 0;
+    virtual bool growMemory(Name name, Address oldSize, Address newSize) = 0;
     virtual bool growTable(Name name,
                            const Literal& value,
                            Index oldSize,
@@ -2318,18 +2296,18 @@ public:
 
     // the default impls for load and store switch on the sizes. you can either
     // customize load/store, or the sub-functions which they call
-    virtual Literal load(Load* load, Address addr) {
+    virtual Literal load(Load* load, Address addr, Name memory) {
       switch (load->type.getBasic()) {
         case Type::i32: {
           switch (load->bytes) {
             case 1:
-              return load->signed_ ? Literal((int32_t)load8s(addr))
-                                   : Literal((int32_t)load8u(addr));
+              return load->signed_ ? Literal((int32_t)load8s(addr, memory))
+                                   : Literal((int32_t)load8u(addr, memory));
             case 2:
-              return load->signed_ ? Literal((int32_t)load16s(addr))
-                                   : Literal((int32_t)load16u(addr));
+              return load->signed_ ? Literal((int32_t)load16s(addr, memory))
+                                   : Literal((int32_t)load16u(addr, memory));
             case 4:
-              return Literal((int32_t)load32s(addr));
+              return Literal((int32_t)load32s(addr, memory));
             default:
               WASM_UNREACHABLE("invalid size");
           }
@@ -2338,50 +2316,45 @@ public:
         case Type::i64: {
           switch (load->bytes) {
             case 1:
-              return load->signed_ ? Literal((int64_t)load8s(addr))
-                                   : Literal((int64_t)load8u(addr));
+              return load->signed_ ? Literal((int64_t)load8s(addr, memory))
+                                   : Literal((int64_t)load8u(addr, memory));
             case 2:
-              return load->signed_ ? Literal((int64_t)load16s(addr))
-                                   : Literal((int64_t)load16u(addr));
+              return load->signed_ ? Literal((int64_t)load16s(addr, memory))
+                                   : Literal((int64_t)load16u(addr, memory));
             case 4:
-              return load->signed_ ? Literal((int64_t)load32s(addr))
-                                   : Literal((int64_t)load32u(addr));
+              return load->signed_ ? Literal((int64_t)load32s(addr, memory))
+                                   : Literal((int64_t)load32u(addr, memory));
             case 8:
-              return Literal((int64_t)load64s(addr));
+              return Literal((int64_t)load64s(addr, memory));
             default:
               WASM_UNREACHABLE("invalid size");
           }
           break;
         }
         case Type::f32:
-          return Literal(load32u(addr)).castToF32();
+          return Literal(load32u(addr, memory)).castToF32();
         case Type::f64:
-          return Literal(load64u(addr)).castToF64();
+          return Literal(load64u(addr, memory)).castToF64();
         case Type::v128:
-          return Literal(load128(addr).data());
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
+          return Literal(load128(addr, load->memory).data());
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
       }
       WASM_UNREACHABLE("invalid type");
     }
-    virtual void store(Store* store, Address addr, Literal value) {
+    virtual void store(Store* store, Address addr, Literal value, Name memory) {
       switch (store->valueType.getBasic()) {
         case Type::i32: {
           switch (store->bytes) {
             case 1:
-              store8(addr, value.geti32());
+              store8(addr, value.geti32(), memory);
               break;
             case 2:
-              store16(addr, value.geti32());
+              store16(addr, value.geti32(), memory);
               break;
             case 4:
-              store32(addr, value.geti32());
+              store32(addr, value.geti32(), memory);
               break;
             default:
               WASM_UNREACHABLE("invalid store size");
@@ -2391,16 +2364,16 @@ public:
         case Type::i64: {
           switch (store->bytes) {
             case 1:
-              store8(addr, value.geti64());
+              store8(addr, value.geti64(), memory);
               break;
             case 2:
-              store16(addr, value.geti64());
+              store16(addr, value.geti64(), memory);
               break;
             case 4:
-              store32(addr, value.geti64());
+              store32(addr, value.geti64(), memory);
               break;
             case 8:
-              store64(addr, value.geti64());
+              store64(addr, value.geti64(), memory);
               break;
             default:
               WASM_UNREACHABLE("invalid store size");
@@ -2409,50 +2382,62 @@ public:
         }
         // write floats carefully, ensuring all bits reach memory
         case Type::f32:
-          store32(addr, value.reinterpreti32());
+          store32(addr, value.reinterpreti32(), memory);
           break;
         case Type::f64:
-          store64(addr, value.reinterpreti64());
+          store64(addr, value.reinterpreti64(), memory);
           break;
         case Type::v128:
-          store128(addr, value.getv128());
+          store128(addr, value.getv128(), memory);
           break;
-        case Type::funcref:
-        case Type::anyref:
-        case Type::eqref:
-        case Type::i31ref:
-        case Type::dataref:
         case Type::none:
         case Type::unreachable:
           WASM_UNREACHABLE("unexpected type");
       }
     }
 
-    virtual int8_t load8s(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual uint8_t load8u(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual int16_t load16s(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual uint16_t load16u(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual int32_t load32s(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual uint32_t load32u(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual int64_t load64s(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual uint64_t load64u(Address addr) { WASM_UNREACHABLE("unimp"); }
-    virtual std::array<uint8_t, 16> load128(Address addr) {
+    virtual int8_t load8s(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual uint8_t load8u(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual int16_t load16s(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual uint16_t load16u(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual int32_t load32s(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual uint32_t load32u(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual int64_t load64s(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual uint64_t load64u(Address addr, Name memoryName) {
+      WASM_UNREACHABLE("unimp");
+    }
+    virtual std::array<uint8_t, 16> load128(Address addr, Name memoryName) {
       WASM_UNREACHABLE("unimp");
     }
 
-    virtual void store8(Address addr, int8_t value) {
+    virtual void store8(Address addr, int8_t value, Name memoryName) {
       WASM_UNREACHABLE("unimp");
     }
-    virtual void store16(Address addr, int16_t value) {
+    virtual void store16(Address addr, int16_t value, Name memoryName) {
       WASM_UNREACHABLE("unimp");
     }
-    virtual void store32(Address addr, int32_t value) {
+    virtual void store32(Address addr, int32_t value, Name memoryName) {
       WASM_UNREACHABLE("unimp");
     }
-    virtual void store64(Address addr, int64_t value) {
+    virtual void store64(Address addr, int64_t value, Name memoryName) {
       WASM_UNREACHABLE("unimp");
     }
-    virtual void store128(Address addr, const std::array<uint8_t, 16>&) {
+    virtual void
+    store128(Address addr, const std::array<uint8_t, 16>&, Name memoryName) {
       WASM_UNREACHABLE("unimp");
     }
 
@@ -2485,8 +2470,6 @@ public:
       externalInterface(externalInterface), linkedInstances(linkedInstances_) {
     // import globals from the outside
     externalInterface->importGlobals(globals, wasm);
-    // prepare memory
-    memorySize = wasm.memory.initial;
     // generate internal (non-imported) globals
     ModuleUtils::iterDefinedGlobals(wasm, [&](Global* global) {
       globals[global->name] = self()->visit(global->init).values;
@@ -2533,7 +2516,7 @@ public:
   std::string printFunctionStack() {
     std::string ret = "/== (binaryen interpreter stack trace)\n";
     for (int i = int(functionStack.size()) - 1; i >= 0; i--) {
-      ret += std::string("|: ") + functionStack[i].str + "\n";
+      ret += std::string("|: ") + functionStack[i].toString() + "\n";
     }
     ret += std::string("\\==\n");
     return ret;
@@ -2573,7 +2556,7 @@ private:
       if (table->type.isNullable()) {
         // Initial with nulls in a nullable table.
         auto info = getTableInterfaceInfo(table->name);
-        auto null = Literal::makeNull(table->type);
+        auto null = Literal::makeNull(table->type.getHeapType());
         for (Address i = 0; i < table->initial; i++) {
           info.interface->tableStore(info.name, i, null);
         }
@@ -2600,25 +2583,45 @@ private:
     });
   }
 
+  struct MemoryInstanceInfo {
+    // The ModuleRunner instance in which the memory is defined.
+    SubType* instance;
+    // The name the memory has in that interface.
+    Name name;
+  };
+
+  MemoryInstanceInfo getMemoryInstanceInfo(Name name) {
+    auto* memory = wasm.getMemory(name);
+    MemoryInstanceInfo memoryInterfaceInfo;
+    if (!memory->imported()) {
+      return MemoryInstanceInfo{self(), name};
+    }
+
+    auto& importedInstance = linkedInstances.at(memory->module);
+    auto* memoryExport = importedInstance->wasm.getExport(memory->base);
+    return importedInstance->getMemoryInstanceInfo(memoryExport->value);
+  }
+
   void initializeMemoryContents() {
+    initializeMemorySizes();
     Const offset;
     offset.value = Literal(uint32_t(0));
     offset.finalize();
 
     // apply active memory segments
-    for (size_t i = 0, e = wasm.memory.segments.size(); i < e; ++i) {
-      Memory::Segment& segment = wasm.memory.segments[i];
-      if (segment.isPassive) {
+    for (size_t i = 0, e = wasm.dataSegments.size(); i < e; ++i) {
+      auto& segment = wasm.dataSegments[i];
+      if (segment->isPassive) {
         continue;
       }
-
       Const size;
-      size.value = Literal(uint32_t(segment.data.size()));
+      size.value = Literal(uint32_t(segment->data.size()));
       size.finalize();
 
       MemoryInit init;
+      init.memory = segment->memory;
       init.segment = i;
-      init.dest = segment.offset;
+      init.dest = segment->offset;
       init.offset = &offset;
       init.size = &size;
       init.finalize();
@@ -2632,6 +2635,31 @@ private:
     }
   }
 
+  // in pages, used to keep track of memorySize throughout the below memops
+  std::unordered_map<Name, Address> memorySizes;
+
+  void initializeMemorySizes() {
+    for (auto& memory : wasm.memories) {
+      memorySizes[memory->name] = memory->initial;
+    }
+  }
+
+  Address getMemorySize(Name memory) {
+    auto iter = memorySizes.find(memory);
+    if (iter == memorySizes.end()) {
+      externalInterface->trap("getMemorySize called on non-existing memory");
+    }
+    return iter->second;
+  }
+
+  void setMemorySize(Name memory, Address size) {
+    auto iter = memorySizes.find(memory);
+    if (iter == memorySizes.end()) {
+      externalInterface->trap("setMemorySize called on non-existing memory");
+    }
+    memorySizes[memory] = size;
+  }
+
 public:
   class FunctionScope {
   public:
@@ -2690,15 +2718,6 @@ private:
   SmallVector<std::pair<WasmException, Name>, 4> exceptionStack;
 
 protected:
-  // Returns the instance that defines the memory used by this one.
-  SubType* getMemoryInstance() {
-    auto* inst = self();
-    while (inst->wasm.memory.imported()) {
-      inst = inst->linkedInstances.at(inst->wasm.memory.module).get();
-    }
-    return inst;
-  }
-
   // Returns a reference to the current value of a potentially imported global
   Literals& getGlobal(Name name) {
     auto* inst = self();
@@ -2723,7 +2742,14 @@ public:
     }
     auto* func = wasm.getFunction(curr->target);
     Flow ret;
-    if (func->imported()) {
+    if (Intrinsics(*self()->getModule()).isCallWithoutEffects(func)) {
+      // The call.without.effects intrinsic is a call to an import that actually
+      // calls the given function reference that is the final argument.
+      auto newArguments = arguments;
+      auto target = newArguments.back();
+      newArguments.pop_back();
+      ret.values = callFunctionInternal(target.getFunc(), newArguments);
+    } else if (func->imported()) {
       ret.values = externalInterface->callImport(func, arguments);
     } else {
       ret.values = callFunctionInternal(curr->target, arguments);
@@ -2912,12 +2938,14 @@ public:
       return flow;
     }
     NOTE_EVAL1(flow);
-    auto* inst = getMemoryInstance();
-    auto addr = inst->getFinalAddress(curr, flow.getSingleValue());
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto addr =
+      info.instance->getFinalAddress(curr, flow.getSingleValue(), memorySize);
     if (curr->isAtomic) {
-      inst->checkAtomicAddress(addr, curr->bytes);
+      info.instance->checkAtomicAddress(addr, curr->bytes, memorySize);
     }
-    auto ret = inst->externalInterface->load(curr, addr);
+    auto ret = info.instance->externalInterface->load(curr, addr, info.name);
     NOTE_EVAL1(addr);
     NOTE_EVAL1(ret);
     return ret;
@@ -2932,14 +2960,17 @@ public:
     if (value.breaking()) {
       return value;
     }
-    auto* inst = getMemoryInstance();
-    auto addr = inst->getFinalAddress(curr, ptr.getSingleValue());
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto addr =
+      info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize);
     if (curr->isAtomic) {
-      inst->checkAtomicAddress(addr, curr->bytes);
+      info.instance->checkAtomicAddress(addr, curr->bytes, memorySize);
     }
     NOTE_EVAL1(addr);
     NOTE_EVAL1(value);
-    inst->externalInterface->store(curr, addr, value.getSingleValue());
+    info.instance->externalInterface->store(
+      curr, addr, value.getSingleValue(), info.name);
     return Flow();
   }
 
@@ -2954,11 +2985,14 @@ public:
       return value;
     }
     NOTE_EVAL1(ptr);
-    auto* inst = getMemoryInstance();
-    auto addr = inst->getFinalAddress(curr, ptr.getSingleValue());
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto addr =
+      info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize);
     NOTE_EVAL1(addr);
     NOTE_EVAL1(value);
-    auto loaded = inst->doAtomicLoad(addr, curr->bytes, curr->type);
+    auto loaded = info.instance->doAtomicLoad(
+      addr, curr->bytes, curr->type, info.name, memorySize);
     NOTE_EVAL1(loaded);
     auto computed = value.getSingleValue();
     switch (curr->op) {
@@ -2980,7 +3014,8 @@ public:
       case RMWXchg:
         break;
     }
-    inst->doAtomicStore(addr, curr->bytes, computed);
+    info.instance->doAtomicStore(
+      addr, curr->bytes, computed, info.name, memorySize);
     return loaded;
   }
   Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) {
@@ -2998,16 +3033,20 @@ public:
     if (replacement.breaking()) {
       return replacement;
     }
-    auto* inst = getMemoryInstance();
-    auto addr = inst->getFinalAddress(curr, ptr.getSingleValue());
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto addr =
+      info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize);
     expected = Flow(wrapToSmallerSize(expected.getSingleValue(), curr->bytes));
     NOTE_EVAL1(addr);
     NOTE_EVAL1(expected);
     NOTE_EVAL1(replacement);
-    auto loaded = inst->doAtomicLoad(addr, curr->bytes, curr->type);
+    auto loaded = info.instance->doAtomicLoad(
+      addr, curr->bytes, curr->type, info.name, memorySize);
     NOTE_EVAL1(loaded);
     if (loaded == expected.getSingleValue()) {
-      inst->doAtomicStore(addr, curr->bytes, replacement.getSingleValue());
+      info.instance->doAtomicStore(
+        addr, curr->bytes, replacement.getSingleValue(), info.name, memorySize);
     }
     return loaded;
   }
@@ -3028,10 +3067,13 @@ public:
     if (timeout.breaking()) {
       return timeout;
     }
-    auto* inst = getMemoryInstance();
     auto bytes = curr->expectedType.getByteSize();
-    auto addr = inst->getFinalAddress(curr, ptr.getSingleValue(), bytes);
-    auto loaded = inst->doAtomicLoad(addr, bytes, curr->expectedType);
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto addr = info.instance->getFinalAddress(
+      curr, ptr.getSingleValue(), bytes, memorySize);
+    auto loaded = info.instance->doAtomicLoad(
+      addr, bytes, curr->expectedType, info.name, memorySize);
     NOTE_EVAL1(loaded);
     if (loaded != expected.getSingleValue()) {
       return Literal(int32_t(1)); // not equal
@@ -3052,10 +3094,12 @@ public:
     if (count.breaking()) {
       return count;
     }
-    auto* inst = getMemoryInstance();
-    auto addr = inst->getFinalAddress(curr, ptr.getSingleValue(), 4);
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto addr =
+      info.instance->getFinalAddress(curr, ptr.getSingleValue(), 4, memorySize);
     // Just check TODO actual threads support
-    inst->checkAtomicAddress(addr, 4);
+    info.instance->checkAtomicAddress(addr, 4, memorySize);
     return Literal(int32_t(0)); // none woken up
   }
   Flow visitSIMDLoad(SIMDLoad* curr) {
@@ -3081,6 +3125,7 @@ public:
   }
   Flow visitSIMDLoadSplat(SIMDLoad* curr) {
     Load load;
+    load.memory = curr->memory;
     load.type = Type::i32;
     load.bytes = curr->getMemBytes();
     load.signed_ = false;
@@ -3120,30 +3165,37 @@ public:
     }
     NOTE_EVAL1(flow);
     Address src(uint32_t(flow.getSingleValue().geti32()));
-    auto* inst = getMemoryInstance();
+    auto info = getMemoryInstanceInfo(curr->memory);
     auto loadLane = [&](Address addr) {
       switch (curr->op) {
         case Load8x8SVec128:
-          return Literal(int32_t(inst->externalInterface->load8s(addr)));
+          return Literal(
+            int32_t(info.instance->externalInterface->load8s(addr, info.name)));
         case Load8x8UVec128:
-          return Literal(int32_t(inst->externalInterface->load8u(addr)));
+          return Literal(
+            int32_t(info.instance->externalInterface->load8u(addr, info.name)));
         case Load16x4SVec128:
-          return Literal(int32_t(inst->externalInterface->load16s(addr)));
+          return Literal(int32_t(
+            info.instance->externalInterface->load16s(addr, info.name)));
         case Load16x4UVec128:
-          return Literal(int32_t(inst->externalInterface->load16u(addr)));
+          return Literal(int32_t(
+            info.instance->externalInterface->load16u(addr, info.name)));
         case Load32x2SVec128:
-          return Literal(int64_t(inst->externalInterface->load32s(addr)));
+          return Literal(int64_t(
+            info.instance->externalInterface->load32s(addr, info.name)));
         case Load32x2UVec128:
-          return Literal(int64_t(inst->externalInterface->load32u(addr)));
+          return Literal(int64_t(
+            info.instance->externalInterface->load32u(addr, info.name)));
         default:
           WASM_UNREACHABLE("unexpected op");
       }
       WASM_UNREACHABLE("invalid op");
     };
+    auto memorySize = info.instance->getMemorySize(info.name);
     auto fillLanes = [&](auto lanes, size_t laneBytes) {
       for (auto& lane : lanes) {
-        lane = loadLane(
-          inst->getFinalAddress(curr, Literal(uint32_t(src)), laneBytes));
+        lane = loadLane(info.instance->getFinalAddress(
+          curr, Literal(uint32_t(src)), laneBytes, memorySize));
         src = Address(uint32_t(src) + laneBytes);
       }
       return Literal(lanes);
@@ -3175,16 +3227,19 @@ public:
       return flow;
     }
     NOTE_EVAL1(flow);
-    auto* inst = getMemoryInstance();
-    Address src =
-      inst->getFinalAddress(curr, flow.getSingleValue(), curr->getMemBytes());
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    Address src = info.instance->getFinalAddress(
+      curr, flow.getSingleValue(), curr->getMemBytes(), memorySize);
     auto zero =
       Literal::makeZero(curr->op == Load32ZeroVec128 ? Type::i32 : Type::i64);
     if (curr->op == Load32ZeroVec128) {
-      auto val = Literal(inst->externalInterface->load32u(src));
+      auto val =
+        Literal(info.instance->externalInterface->load32u(src, info.name));
       return Literal(std::array<Literal, 4>{{val, zero, zero, zero}});
     } else {
-      auto val = Literal(inst->externalInterface->load64u(src));
+      auto val =
+        Literal(info.instance->externalInterface->load64u(src, info.name));
       return Literal(std::array<Literal, 2>{{val, zero}});
     }
   }
@@ -3195,9 +3250,10 @@ public:
       return flow;
     }
     NOTE_EVAL1(flow);
-    auto* inst = getMemoryInstance();
-    Address addr =
-      inst->getFinalAddress(curr, flow.getSingleValue(), curr->getMemBytes());
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    Address addr = info.instance->getFinalAddress(
+      curr, flow.getSingleValue(), curr->getMemBytes(), memorySize);
     flow = self()->visit(curr->vec);
     if (flow.breaking()) {
       return flow;
@@ -3208,10 +3264,12 @@ public:
       case Store8LaneVec128: {
         std::array<Literal, 16> lanes = vec.getLanesUI8x16();
         if (curr->isLoad()) {
-          lanes[curr->index] = Literal(inst->externalInterface->load8u(addr));
+          lanes[curr->index] =
+            Literal(info.instance->externalInterface->load8u(addr, info.name));
           return Literal(lanes);
         } else {
-          inst->externalInterface->store8(addr, lanes[curr->index].geti32());
+          info.instance->externalInterface->store8(
+            addr, lanes[curr->index].geti32(), info.name);
           return {};
         }
       }
@@ -3219,10 +3277,12 @@ public:
       case Store16LaneVec128: {
         std::array<Literal, 8> lanes = vec.getLanesUI16x8();
         if (curr->isLoad()) {
-          lanes[curr->index] = Literal(inst->externalInterface->load16u(addr));
+          lanes[curr->index] =
+            Literal(info.instance->externalInterface->load16u(addr, info.name));
           return Literal(lanes);
         } else {
-          inst->externalInterface->store16(addr, lanes[curr->index].geti32());
+          info.instance->externalInterface->store16(
+            addr, lanes[curr->index].geti32(), info.name);
           return {};
         }
       }
@@ -3230,10 +3290,12 @@ public:
       case Store32LaneVec128: {
         std::array<Literal, 4> lanes = vec.getLanesI32x4();
         if (curr->isLoad()) {
-          lanes[curr->index] = Literal(inst->externalInterface->load32u(addr));
+          lanes[curr->index] =
+            Literal(info.instance->externalInterface->load32u(addr, info.name));
           return Literal(lanes);
         } else {
-          inst->externalInterface->store32(addr, lanes[curr->index].geti32());
+          info.instance->externalInterface->store32(
+            addr, lanes[curr->index].geti32(), info.name);
           return {};
         }
       }
@@ -3241,10 +3303,12 @@ public:
       case Load64LaneVec128: {
         std::array<Literal, 2> lanes = vec.getLanesI64x2();
         if (curr->isLoad()) {
-          lanes[curr->index] = Literal(inst->externalInterface->load64u(addr));
+          lanes[curr->index] =
+            Literal(info.instance->externalInterface->load64u(addr, info.name));
           return Literal(lanes);
         } else {
-          inst->externalInterface->store64(addr, lanes[curr->index].geti64());
+          info.instance->externalInterface->store64(
+            addr, lanes[curr->index].geti64(), info.name);
           return {};
         }
       }
@@ -3253,38 +3317,44 @@ public:
   }
   Flow visitMemorySize(MemorySize* curr) {
     NOTE_ENTER("MemorySize");
-    auto* inst = getMemoryInstance();
-    return Literal::makeFromInt64(inst->memorySize,
-                                  inst->wasm.memory.indexType);
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto* memory = info.instance->wasm.getMemory(info.name);
+    return Literal::makeFromInt64(memorySize, memory->indexType);
   }
   Flow visitMemoryGrow(MemoryGrow* curr) {
     NOTE_ENTER("MemoryGrow");
-    auto* inst = getMemoryInstance();
-    auto indexType = inst->wasm.memory.indexType;
-    auto fail = Literal::makeFromInt64(-1, indexType);
     Flow flow = self()->visit(curr->delta);
     if (flow.breaking()) {
       return flow;
     }
-    Flow ret = Literal::makeFromInt64(inst->memorySize, indexType);
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    auto* memory = info.instance->wasm.getMemory(info.name);
+    auto indexType = memory->indexType;
+    auto fail = Literal::makeFromInt64(-1, memory->indexType);
+    Flow ret = Literal::makeFromInt64(memorySize, indexType);
     uint64_t delta = flow.getSingleValue().getUnsigned();
     if (delta > uint32_t(-1) / Memory::kPageSize && indexType == Type::i32) {
       return fail;
     }
-    if (inst->memorySize >= uint32_t(-1) - delta && indexType == Type::i32) {
+    if (memorySize >= uint32_t(-1) - delta && indexType == Type::i32) {
       return fail;
     }
-    auto newSize = inst->memorySize + delta;
-    if (newSize > inst->wasm.memory.max) {
+    auto newSize = memorySize + delta;
+    if (newSize > memory->max) {
       return fail;
     }
-    if (!inst->externalInterface->growMemory(
-          inst->memorySize * Memory::kPageSize, newSize * Memory::kPageSize)) {
+    if (!info.instance->externalInterface->growMemory(
+          info.name,
+          memorySize * Memory::kPageSize,
+          newSize * Memory::kPageSize)) {
       // We failed to grow the memory in practice, even though it was valid
       // to try to do so.
       return fail;
     }
-    inst->memorySize = newSize;
+    memorySize = newSize;
+    info.instance->setMemorySize(info.name, memorySize);
     return ret;
   }
   Flow visitMemoryInit(MemoryInit* curr) {
@@ -3305,8 +3375,8 @@ public:
     NOTE_EVAL1(offset);
     NOTE_EVAL1(size);
 
-    assert(curr->segment < wasm.memory.segments.size());
-    Memory::Segment& segment = wasm.memory.segments[curr->segment];
+    assert(curr->segment < wasm.dataSegments.size());
+    auto& segment = wasm.dataSegments[curr->segment];
 
     Address destVal(dest.getSingleValue().getUnsigned());
     Address offsetVal(uint32_t(offset.getSingleValue().geti32()));
@@ -3315,18 +3385,20 @@ public:
     if (offsetVal + sizeVal > 0 && droppedSegments.count(curr->segment)) {
       trap("out of bounds segment access in memory.init");
     }
-    if ((uint64_t)offsetVal + sizeVal > segment.data.size()) {
+    if ((uint64_t)offsetVal + sizeVal > segment->data.size()) {
       trap("out of bounds segment access in memory.init");
     }
-    auto* inst = getMemoryInstance();
-    if (destVal + sizeVal > inst->memorySize * Memory::kPageSize) {
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
+    if (destVal + sizeVal > memorySize * Memory::kPageSize) {
       trap("out of bounds memory access in memory.init");
     }
     for (size_t i = 0; i < sizeVal; ++i) {
       Literal addr(destVal + i);
-      inst->externalInterface->store8(
-        inst->getFinalAddressWithoutOffset(addr, 1),
-        segment.data[offsetVal + i]);
+      info.instance->externalInterface->store8(
+        info.instance->getFinalAddressWithoutOffset(addr, 1, memorySize),
+        segment->data[offsetVal + i],
+        info.name);
     }
     return {};
   }
@@ -3356,9 +3428,12 @@ public:
     Address sourceVal(source.getSingleValue().getUnsigned());
     Address sizeVal(size.getSingleValue().getUnsigned());
 
-    auto* inst = getMemoryInstance();
-    if (sourceVal + sizeVal > inst->memorySize * Memory::kPageSize ||
-        destVal + sizeVal > inst->memorySize * Memory::kPageSize ||
+    auto destInfo = getMemoryInstanceInfo(curr->destMemory);
+    auto sourceInfo = getMemoryInstanceInfo(curr->sourceMemory);
+    auto destMemorySize = destInfo.instance->getMemorySize(destInfo.name);
+    auto sourceMemorySize = sourceInfo.instance->getMemorySize(sourceInfo.name);
+    if (sourceVal + sizeVal > sourceMemorySize * Memory::kPageSize ||
+        destVal + sizeVal > destMemorySize * Memory::kPageSize ||
         // FIXME: better/cheaper way to detect wrapping?
         sourceVal + sizeVal < sourceVal || sourceVal + sizeVal < sizeVal ||
         destVal + sizeVal < destVal || destVal + sizeVal < sizeVal) {
@@ -3375,10 +3450,14 @@ public:
       step = -1;
     }
     for (int64_t i = start; i != end; i += step) {
-      inst->externalInterface->store8(
-        inst->getFinalAddressWithoutOffset(Literal(destVal + i), 1),
-        inst->externalInterface->load8s(
-          inst->getFinalAddressWithoutOffset(Literal(sourceVal + i), 1)));
+      destInfo.instance->externalInterface->store8(
+        destInfo.instance->getFinalAddressWithoutOffset(
+          Literal(destVal + i), 1, destMemorySize),
+        sourceInfo.instance->externalInterface->load8s(
+          sourceInfo.instance->getFinalAddressWithoutOffset(
+            Literal(sourceVal + i), 1, sourceMemorySize),
+          sourceInfo.name),
+        destInfo.name);
     }
     return {};
   }
@@ -3402,20 +3481,83 @@ public:
     Address destVal(dest.getSingleValue().getUnsigned());
     Address sizeVal(size.getSingleValue().getUnsigned());
 
-    auto* inst = getMemoryInstance();
+    auto info = getMemoryInstanceInfo(curr->memory);
+    auto memorySize = info.instance->getMemorySize(info.name);
     // FIXME: cheaper wrapping detection?
-    if (destVal > inst->memorySize * Memory::kPageSize ||
-        sizeVal > inst->memorySize * Memory::kPageSize ||
-        destVal + sizeVal > inst->memorySize * Memory::kPageSize) {
+    if (destVal > memorySize * Memory::kPageSize ||
+        sizeVal > memorySize * Memory::kPageSize ||
+        destVal + sizeVal > memorySize * Memory::kPageSize) {
       trap("out of bounds memory access in memory.fill");
     }
     uint8_t val(value.getSingleValue().geti32());
     for (size_t i = 0; i < sizeVal; ++i) {
-      inst->externalInterface->store8(
-        inst->getFinalAddressWithoutOffset(Literal(destVal + i), 1), val);
+      info.instance->externalInterface->store8(
+        info.instance->getFinalAddressWithoutOffset(
+          Literal(destVal + i), 1, memorySize),
+        val,
+        info.name);
     }
     return {};
   }
+  Flow visitArrayNewSeg(ArrayNewSeg* curr) {
+    NOTE_ENTER("ArrayNewSeg");
+    auto offsetFlow = self()->visit(curr->offset);
+    if (offsetFlow.breaking()) {
+      return offsetFlow;
+    }
+    auto sizeFlow = self()->visit(curr->size);
+    if (sizeFlow.breaking()) {
+      return sizeFlow;
+    }
+
+    uint64_t offset = offsetFlow.getSingleValue().getUnsigned();
+    uint64_t size = sizeFlow.getSingleValue().getUnsigned();
+
+    auto heapType = curr->type.getHeapType();
+    const auto& element = heapType.getArray().element;
+    auto elemType = heapType.getArray().element.type;
+    WASM_UNUSED(elemType);
+
+    Literals contents;
+
+    switch (curr->op) {
+      case NewData: {
+        assert(curr->segment < wasm.dataSegments.size());
+        assert(elemType.isNumber());
+        const auto& seg = *wasm.dataSegments[curr->segment];
+        auto elemBytes = element.getByteSize();
+        auto end = offset + size * elemBytes;
+        if ((size != 0ull && droppedSegments.count(curr->segment)) ||
+            end > seg.data.size()) {
+          trap("out of bounds segment access in array.new_data");
+        }
+        contents.reserve(size);
+        for (Index i = offset; i < end; i += elemBytes) {
+          auto addr = (void*)&seg.data[i];
+          contents.push_back(Literal::makeFromMemory(addr, element));
+        }
+        break;
+      }
+      case NewElem: {
+        assert(curr->segment < wasm.elementSegments.size());
+        const auto& seg = *wasm.elementSegments[curr->segment];
+        auto end = offset + size;
+        // TODO: Handle dropped element segments once we support those.
+        if (end > seg.data.size()) {
+          trap("out of bounds segment access in array.new_elem");
+        }
+        contents.reserve(size);
+        for (Index i = offset; i < end; ++i) {
+          auto val = self()->visit(seg.data[i]).getSingleValue();
+          contents.push_back(val);
+        }
+        break;
+      }
+      default:
+        WASM_UNREACHABLE("unexpected op");
+    }
+    return Literal(std::make_shared<GCData>(heapType, contents), heapType);
+  }
   Flow visitTry(Try* curr) {
     NOTE_ENTER("Try");
     try {
@@ -3425,7 +3567,7 @@ public:
       // the delegation, don't handle it and just rethrow.
       if (scope->currDelegateTarget.is()) {
         if (scope->currDelegateTarget == curr->name) {
-          scope->currDelegateTarget.clear();
+          scope->currDelegateTarget = Name{};
         } else {
           throw;
         }
@@ -3588,8 +3730,6 @@ public:
   static const Index maxDepth = 250;
 
 protected:
-  Address memorySize; // in pages
-
   void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) {
     if (lhs > rhs) {
       std::stringstream ss;
@@ -3599,34 +3739,37 @@ protected:
   }
 
   template<class LS>
-  Address getFinalAddress(LS* curr, Literal ptr, Index bytes) {
+  Address
+  getFinalAddress(LS* curr, Literal ptr, Index bytes, Address memorySize) {
     Address memorySizeBytes = memorySize * Memory::kPageSize;
     uint64_t addr = ptr.type == Type::i32 ? ptr.geti32() : ptr.geti64();
     trapIfGt(curr->offset, memorySizeBytes, "offset > memory");
     trapIfGt(addr, memorySizeBytes - curr->offset, "final > memory");
     addr += curr->offset;
     trapIfGt(bytes, memorySizeBytes, "bytes > memory");
-    checkLoadAddress(addr, bytes);
+    checkLoadAddress(addr, bytes, memorySize);
     return addr;
   }
 
-  template<class LS> Address getFinalAddress(LS* curr, Literal ptr) {
-    return getFinalAddress(curr, ptr, curr->bytes);
+  template<class LS>
+  Address getFinalAddress(LS* curr, Literal ptr, Address memorySize) {
+    return getFinalAddress(curr, ptr, curr->bytes, memorySize);
   }
 
-  Address getFinalAddressWithoutOffset(Literal ptr, Index bytes) {
+  Address
+  getFinalAddressWithoutOffset(Literal ptr, Index bytes, Address memorySize) {
     uint64_t addr = ptr.type == Type::i32 ? ptr.geti32() : ptr.geti64();
-    checkLoadAddress(addr, bytes);
+    checkLoadAddress(addr, bytes, memorySize);
     return addr;
   }
 
-  void checkLoadAddress(Address addr, Index bytes) {
+  void checkLoadAddress(Address addr, Index bytes, Address memorySize) {
     Address memorySizeBytes = memorySize * Memory::kPageSize;
     trapIfGt(addr, memorySizeBytes - bytes, "highest > memory");
   }
 
-  void checkAtomicAddress(Address addr, Index bytes) {
-    checkLoadAddress(addr, bytes);
+  void checkAtomicAddress(Address addr, Index bytes, Address memorySize) {
+    checkLoadAddress(addr, bytes, memorySize);
     // Unaligned atomics trap.
     if (bytes > 1) {
       if (addr & (bytes - 1)) {
@@ -3635,8 +3778,9 @@ protected:
     }
   }
 
-  Literal doAtomicLoad(Address addr, Index bytes, Type type) {
-    checkAtomicAddress(addr, bytes);
+  Literal doAtomicLoad(
+    Address addr, Index bytes, Type type, Name memoryName, Address memorySize) {
+    checkAtomicAddress(addr, bytes, memorySize);
     Const ptr;
     ptr.value = Literal(int32_t(addr));
     ptr.type = Type::i32;
@@ -3649,11 +3793,16 @@ protected:
     load.isAtomic = true; // understatement
     load.ptr = &ptr;
     load.type = type;
-    return externalInterface->load(&load, addr);
+    load.memory = memoryName;
+    return externalInterface->load(&load, addr, memoryName);
   }
 
-  void doAtomicStore(Address addr, Index bytes, Literal toStore) {
-    checkAtomicAddress(addr, bytes);
+  void doAtomicStore(Address addr,
+                     Index bytes,
+                     Literal toStore,
+                     Name memoryName,
+                     Address memorySize) {
+    checkAtomicAddress(addr, bytes, memorySize);
     Const ptr;
     ptr.value = Literal(int32_t(addr));
     ptr.type = Type::i32;
@@ -3667,7 +3816,8 @@ protected:
     store.ptr = &ptr;
     store.value = &value;
     store.valueType = value.type;
-    return externalInterface->store(&store, addr, toStore);
+    store.memory = memoryName;
+    return externalInterface->store(&store, addr, toStore, memoryName);
   }
 
   ExternalInterface* externalInterface;
diff --git a/src/wasm-io.h b/src/wasm-io.h
index 1e28b5f..ae66c39 100644
--- a/src/wasm-io.h
+++ b/src/wasm-io.h
@@ -27,6 +27,10 @@
 
 namespace wasm {
 
+// TODO: Remove this after switching to the new WAT parser by default and
+// removing the old one.
+extern bool useNewWATParser;
+
 class ModuleIOBase {
 protected:
   bool debugInfo;
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index 61383f0..524bc5a 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -31,12 +31,10 @@ namespace wasm {
 
 class SourceLocation {
 public:
-  cashew::IString filename;
+  IString filename;
   uint32_t line;
   uint32_t column;
-  SourceLocation(cashew::IString filename_,
-                 uint32_t line_,
-                 uint32_t column_ = 0)
+  SourceLocation(IString filename_, uint32_t line_, uint32_t column_ = 0)
     : filename(filename_), line(line_), column(column_) {}
 };
 
@@ -44,11 +42,11 @@ public:
 // An element in an S-Expression: a list or a string
 //
 class Element {
-  typedef ArenaVector<Element*> List;
+  using List = ArenaVector<Element*>;
 
   bool isList_ = true;
   List list_;
-  cashew::IString str_;
+  IString str_;
   bool dollared_;
   bool quoted_;
 
@@ -74,9 +72,9 @@ public:
   List::Iterator end() { return list().end(); }
 
   // string methods
-  cashew::IString str() const;
-  const char* c_str() const;
-  Element* setString(cashew::IString str__, bool dollared__, bool quoted__);
+  IString str() const;
+  std::string toString() const;
+  Element* setString(IString str__, bool dollared__, bool quoted__);
   Element* setMetadata(size_t line_, size_t col_, SourceLocation* startLoc_);
 
   // comparisons
@@ -126,6 +124,7 @@ class SExpressionWasmBuilder {
 
   std::vector<Name> functionNames;
   std::vector<Name> tableNames;
+  std::vector<Name> memoryNames;
   std::vector<Name> globalNames;
   std::vector<Name> tagNames;
   int functionCounter = 0;
@@ -134,9 +133,10 @@ class SExpressionWasmBuilder {
   int tableCounter = 0;
   int elemCounter = 0;
   int memoryCounter = 0;
+  int dataCounter = 0;
   // we need to know function return types before we parse their contents
   std::map<Name, HeapType> functionTypes;
-  std::unordered_map<cashew::IString, Index> debugInfoFileIndices;
+  std::unordered_map<IString, Index> debugInfoFileIndices;
 
   // Maps type indexes to a mapping of field index => name. This is not the same
   // as the field names stored on the wasm object, as that maps types after
@@ -157,6 +157,7 @@ private:
   void preParseFunctionType(Element& s);
   bool isImport(Element& curr);
   void preParseImports(Element& curr);
+  void preParseMemory(Element& curr);
   void parseModuleElement(Element& curr);
 
   // function parsing state
@@ -165,32 +166,37 @@ private:
 
   UniqueNameMapper nameMapper;
 
+  int parseIndex(Element& s);
+
   Name getFunctionName(Element& s);
   Name getTableName(Element& s);
+  Name getMemoryName(Element& s);
   Name getGlobalName(Element& s);
   Name getTagName(Element& s);
   void parseStart(Element& s) { wasm.addStart(getFunctionName(*s[1])); }
 
+  Name getMemoryNameAtIdx(Index i);
+  bool isMemory64(Name memoryName);
+  bool hasMemoryIdx(Element& s, Index defaultSize, Index i);
+
   // returns the next index in s
   size_t parseFunctionNames(Element& s, Name& name, Name& exportName);
   void parseFunction(Element& s, bool preParseImport = false);
 
-  Type stringToType(cashew::IString str,
-                    bool allowError = false,
-                    bool prefix = false) {
+  Type stringToType(IString str, bool allowError = false, bool prefix = false) {
     return stringToType(str.str, allowError, prefix);
   }
-  Type
-  stringToType(const char* str, bool allowError = false, bool prefix = false);
-  HeapType stringToHeapType(cashew::IString str, bool prefix = false) {
+  Type stringToType(std::string_view str,
+                    bool allowError = false,
+                    bool prefix = false);
+  HeapType stringToHeapType(IString str, bool prefix = false) {
     return stringToHeapType(str.str, prefix);
   }
-  HeapType stringToHeapType(const char* str, bool prefix = false);
+  HeapType stringToHeapType(std::string_view str, bool prefix = false);
   Type elementToType(Element& s);
+  // TODO: Use std::string_view for this and similar functions.
   Type stringToLaneType(const char* str);
-  bool isType(cashew::IString str) {
-    return stringToType(str, true) != Type::none;
-  }
+  bool isType(IString str) { return stringToType(str, true) != Type::none; }
   HeapType getFunctionType(Name name, Element& s);
 
 public:
@@ -218,13 +224,12 @@ private:
   Expression* makeBlock(Element& s);
   Expression* makeThenOrElse(Element& s);
   Expression* makeConst(Element& s, Type type);
-  Expression* makeLoad(Element& s, Type type, bool isAtomic);
-  Expression* makeStore(Element& s, Type type, bool isAtomic);
-  Expression* makeAtomicRMWOrCmpxchg(Element& s, Type type);
   Expression*
-  makeAtomicRMW(Element& s, Type type, uint8_t bytes, const char* extra);
+  makeLoad(Element& s, Type type, bool signed_, int bytes, bool isAtomic);
+  Expression* makeStore(Element& s, Type type, int bytes, bool isAtomic);
   Expression*
-  makeAtomicCmpxchg(Element& s, Type type, uint8_t bytes, const char* extra);
+  makeAtomicRMW(Element& s, AtomicRMWOp op, Type type, uint8_t bytes);
+  Expression* makeAtomicCmpxchg(Element& s, Type type, uint8_t bytes);
   Expression* makeAtomicWait(Element& s, Type type);
   Expression* makeAtomicNotify(Element& s);
   Expression* makeAtomicFence(Element& s);
@@ -233,13 +238,13 @@ private:
   Expression* makeSIMDShuffle(Element& s);
   Expression* makeSIMDTernary(Element& s, SIMDTernaryOp op);
   Expression* makeSIMDShift(Element& s, SIMDShiftOp op);
-  Expression* makeSIMDLoad(Element& s, SIMDLoadOp op);
-  Expression* makeSIMDLoadStoreLane(Element& s, SIMDLoadStoreLaneOp op);
+  Expression* makeSIMDLoad(Element& s, SIMDLoadOp op, int bytes);
+  Expression*
+  makeSIMDLoadStoreLane(Element& s, SIMDLoadStoreLaneOp op, int bytes);
   Expression* makeMemoryInit(Element& s);
   Expression* makeDataDrop(Element& s);
   Expression* makeMemoryCopy(Element& s);
   Expression* makeMemoryFill(Element& s);
-  Expression* makePush(Element& s);
   Expression* makePop(Element& s);
   Expression* makeIf(Element& s);
   Expression* makeMaybeBlock(Element& s, size_t i, Type type);
@@ -278,35 +283,45 @@ private:
   Expression* makeCallRef(Element& s, bool isReturn);
   Expression* makeI31New(Element& s);
   Expression* makeI31Get(Element& s, bool signed_);
-  Expression* makeRefTest(Element& s);
   Expression* makeRefTestStatic(Element& s);
-  Expression* makeRefCast(Element& s);
   Expression* makeRefCastStatic(Element& s);
   Expression* makeRefCastNopStatic(Element& s);
   Expression* makeBrOn(Element& s, BrOnOp op);
   Expression* makeBrOnStatic(Element& s, BrOnOp op);
-  Expression* makeRttCanon(Element& s);
-  Expression* makeRttSub(Element& s);
-  Expression* makeRttFreshSub(Element& s);
-  Expression* makeStructNew(Element& s, bool default_);
   Expression* makeStructNewStatic(Element& s, bool default_);
   Index getStructIndex(Element& type, Element& field);
   Expression* makeStructGet(Element& s, bool signed_ = false);
   Expression* makeStructSet(Element& s);
-  Expression* makeArrayNew(Element& s, bool default_);
   Expression* makeArrayNewStatic(Element& s, bool default_);
-  Expression* makeArrayInit(Element& s);
+  Expression* makeArrayNewSeg(Element& s, ArrayNewSegOp op);
   Expression* makeArrayInitStatic(Element& s);
   Expression* makeArrayGet(Element& s, bool signed_ = false);
   Expression* makeArraySet(Element& s);
   Expression* makeArrayLen(Element& s);
   Expression* makeArrayCopy(Element& s);
   Expression* makeRefAs(Element& s, RefAsOp op);
+  Expression* makeStringNew(Element& s, StringNewOp op);
+  Expression* makeStringConst(Element& s);
+  Expression* makeStringMeasure(Element& s, StringMeasureOp op);
+  Expression* makeStringEncode(Element& s, StringEncodeOp op);
+  Expression* makeStringConcat(Element& s);
+  Expression* makeStringEq(Element& s);
+  Expression* makeStringAs(Element& s, StringAsOp op);
+  Expression* makeStringWTF8Advance(Element& s);
+  Expression* makeStringWTF16Get(Element& s);
+  Expression* makeStringIterNext(Element& s);
+  Expression* makeStringIterMove(Element& s, StringIterMoveOp op);
+  Expression* makeStringSliceWTF(Element& s, StringSliceWTFOp op);
+  Expression* makeStringSliceIter(Element& s);
 
   // Helper functions
   Type parseOptionalResultType(Element& s, Index& i);
-  Index parseMemoryLimits(Element& s, Index i);
-  Index parseMemoryIndex(Element& s, Index i);
+  Index parseMemoryLimits(Element& s, Index i, std::unique_ptr<Memory>& memory);
+  Index parseMemoryIndex(Element& s, Index i, std::unique_ptr<Memory>& memory);
+  Index parseMemoryForInstruction(const std::string& instrName,
+                                  Memory& memory,
+                                  Element& s,
+                                  Index i);
   std::vector<Type> parseParamOrLocal(Element& s);
   std::vector<NameType> parseParamOrLocal(Element& s, size_t& localIndex);
   std::vector<Type> parseResults(Element& s);
@@ -317,11 +332,11 @@ private:
                       std::vector<NameType>& namedParams);
   size_t parseTypeUse(Element& s, size_t startPos, HeapType& functionType);
 
-  void stringToBinary(const char* input, size_t size, std::vector<char>& data);
+  void
+  stringToBinary(Element& s, std::string_view str, std::vector<char>& data);
   void parseMemory(Element& s, bool preParseImport = false);
   void parseData(Element& s);
-  void parseInnerData(
-    Element& s, Index i, Name name, Expression* offset, bool isPassive);
+  void parseInnerData(Element& s, Index i, std::unique_ptr<DataSegment>& seg);
   void parseExport(Element& s);
   void parseImport(Element& s);
   void parseGlobal(Element& s, bool preParseImport = false);
@@ -341,7 +356,7 @@ private:
 
   // Struct/Array instructions have an unnecessary heap type that is just for
   // validation (except for the case of unreachability, but that's not a problem
-  // anyhow, we can ignore it there). That is, we also have a reference / rtt
+  // anyhow, we can ignore it there). That is, we also have a reference typed
   // child from which we can infer the type anyhow, and we just need to check
   // that type is the same.
   void
diff --git a/src/wasm-stack.h b/src/wasm-stack.h
index bd527b5..8f72c13 100644
--- a/src/wasm-stack.h
+++ b/src/wasm-stack.h
@@ -122,7 +122,10 @@ public:
   MappedLocals mappedLocals;
 
 private:
-  void emitMemoryAccess(size_t alignment, size_t bytes, uint32_t offset);
+  void emitMemoryAccess(size_t alignment,
+                        size_t bytes,
+                        uint64_t offset,
+                        Name memory);
   int32_t getBreakIndex(Name name);
 
   WasmBinaryWriter& parent;
@@ -197,10 +200,21 @@ template<typename SubType> void BinaryenIRWriter<SubType>::write() {
   emitFunctionEnd();
 }
 
-// emits a node, but if it is a block with no name, emit a list of its contents
+// Emits a node in a position that can contain a list of contents, like an if
+// arm. This will emit the node, but if it is a block with no name, just emit
+// its contents. This is ok to do because a list of contents is ok in the wasm
+// binary format in such positions anyhow. When we read such code in Binaryen
+// we will end up creating a block for it (note that while doing so we create a
+// block without a name, since nothing branches to it, which makes it easy to
+// handle in optimization passes and when writing the binary out again).
 template<typename SubType>
 void BinaryenIRWriter<SubType>::visitPossibleBlockContents(Expression* curr) {
   auto* block = curr->dynCast<Block>();
+  // Even if the block has a name, check if the name is necessary (if it has no
+  // uses, it is equivalent to not having one). Scanning the children of the
+  // block means that this takes quadratic time, but it will be N^2 for a fairly
+  // small N since the number of nested non-block control flow structures tends
+  // to be very reasonable.
   if (!block || BranchUtils::BranchSeeker::has(block, block->name)) {
     visit(curr);
     return;
@@ -261,6 +275,26 @@ void BinaryenIRWriter<SubType>::visitBlock(Block* curr) {
     }
   };
 
+  // A block with no name never needs to be emitted: we can just emit its
+  // contents. In some cases that will end up as "stacky" code, which is valid
+  // in wasm but not in Binaryen IR. This is similar to what we do in
+  // visitPossibleBlockContents(), and like there, when we reload such a binary
+  // we'll end up creating a block for it then.
+  //
+  // Note that in visitPossibleBlockContents() we also optimize the case of a
+  // block with a name but the name actually has no uses - that handles more
+  // cases, but it requires more work. It is reasonable to do it in
+  // visitPossibleBlockContents() which handles the common cases of blocks that
+  // are children of control flow structures (like an if arm); doing it here
+  // would affect every block, including highly-nested block stacks, which would
+  // end up as quadratic time. In optimized code the name will not exist if it's
+  // not used anyhow, so a minor optimization for the unoptimized case that
+  // leads to potential quadratic behavior is not worth it here.
+  if (!curr->name.is()) {
+    visitChildren(curr, 0);
+    return;
+  }
+
   auto afterChildren = [this](Block* curr) {
     emitScopeEnd(curr);
     if (curr->type == Type::unreachable) {
@@ -472,6 +506,8 @@ private:
   Function* func;
 };
 
+std::ostream& printStackIR(std::ostream& o, Module* module, bool optimize);
+
 } // namespace wasm
 
 #endif // wasm_stack_h
diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h
index 7a803e2..9573bc2 100644
--- a/src/wasm-traversal.h
+++ b/src/wasm-traversal.h
@@ -50,6 +50,7 @@ template<typename SubType, typename ReturnType = void> struct Visitor {
   ReturnType visitTable(Table* curr) { return ReturnType(); }
   ReturnType visitElementSegment(ElementSegment* curr) { return ReturnType(); }
   ReturnType visitMemory(Memory* curr) { return ReturnType(); }
+  ReturnType visitDataSegment(DataSegment* curr) { return ReturnType(); }
   ReturnType visitTag(Tag* curr) { return ReturnType(); }
   ReturnType visitModule(Module* curr) { return ReturnType(); }
 
@@ -73,7 +74,7 @@ template<typename SubType, typename ReturnType = void> struct Visitor {
 // A visitor which must be overridden for each visitor that is reached.
 
 template<typename SubType, typename ReturnType = void>
-struct OverriddenVisitor {
+struct OverriddenVisitor : public Visitor<SubType, ReturnType> {
 // Expression visitors, which must be overridden
 #define DELEGATE(CLASS_TO_VISIT)                                               \
   ReturnType visit##CLASS_TO_VISIT(CLASS_TO_VISIT* curr) {                     \
@@ -85,22 +86,6 @@ struct OverriddenVisitor {
   }
 
 #include "wasm-delegations.def"
-
-  ReturnType visit(Expression* curr) {
-    assert(curr);
-
-    switch (curr->_id) {
-#define DELEGATE(CLASS_TO_VISIT)                                               \
-  case Expression::Id::CLASS_TO_VISIT##Id:                                     \
-    return static_cast<SubType*>(this)->visit##CLASS_TO_VISIT(                 \
-      static_cast<CLASS_TO_VISIT*>(curr))
-
-#include "wasm-delegations.def"
-
-      default:
-        WASM_UNREACHABLE("unexpected expression type");
-    }
-  }
 };
 
 // Visit with a single unified visitor, called on every node, instead of
@@ -204,12 +189,15 @@ struct Walker : public VisitorType {
     static_cast<SubType*>(this)->visitTable(table);
   }
 
-  void walkMemory(Memory* memory) {
-    for (auto& segment : memory->segments) {
-      if (!segment.isPassive) {
-        walk(segment.offset);
-      }
+  void walkDataSegment(DataSegment* segment) {
+    if (!segment->isPassive) {
+      walk(segment->offset);
     }
+    static_cast<SubType*>(this)->visitDataSegment(segment);
+  }
+
+  void walkMemory(Memory* memory) {
+    // TODO: This method and walkTable should walk children too, or be renamed.
     static_cast<SubType*>(this)->visitMemory(memory);
   }
 
@@ -254,11 +242,17 @@ struct Walker : public VisitorType {
     for (auto& curr : module->elementSegments) {
       self->walkElementSegment(curr.get());
     }
-    self->walkMemory(&module->memory);
+    for (auto& curr : module->memories) {
+      self->walkMemory(curr.get());
+    }
+    for (auto& curr : module->dataSegments) {
+      self->walkDataSegment(curr.get());
+    }
   }
 
   // Walks module-level code, that is, code that is not in functions.
   void walkModuleCode(Module* module) {
+    setModule(module);
     // Dispatch statically through the SubType.
     SubType* self = static_cast<SubType*>(this);
     for (auto& curr : module->globals) {
@@ -274,13 +268,19 @@ struct Walker : public VisitorType {
         self->walk(item);
       }
     }
+    for (auto& curr : module->dataSegments) {
+      if (curr->offset) {
+        self->walk(curr->offset);
+      }
+    }
+    setModule(nullptr);
   }
 
   // Walk implementation. We don't use recursion as ASTs may be highly
   // nested.
 
   // Tasks receive the this pointer and a pointer to the pointer to operate on
-  typedef void (*TaskFunc)(SubType*, Expression**);
+  using TaskFunc = void (*)(SubType*, Expression**);
 
   struct Task {
     TaskFunc func;
@@ -381,7 +381,7 @@ struct PostWalker : public Walker<SubType, VisitorType> {
 
 // Stacks of expressions tend to be limited in size (although, sometimes
 // super-nested blocks exist for br_table).
-typedef SmallVector<Expression*, 10> ExpressionStack;
+using ExpressionStack = SmallVector<Expression*, 10>;
 
 // Traversal with a control-flow stack.
 
diff --git a/src/wasm-type.h b/src/wasm-type.h
index a754668..fa365b0 100644
--- a/src/wasm-type.h
+++ b/src/wasm-type.h
@@ -21,6 +21,7 @@
 #include <optional>
 #include <ostream>
 #include <string>
+#include <unordered_map>
 #include <variant>
 #include <vector>
 
@@ -67,7 +68,6 @@ struct Signature;
 struct Field;
 struct Struct;
 struct Array;
-struct Rtt;
 
 enum Nullability { NonNullable, Nullable };
 enum Mutability { Immutable, Mutable };
@@ -106,13 +106,8 @@ public:
     f32,
     f64,
     v128,
-    funcref,
-    anyref,
-    eqref,
-    i31ref,
-    dataref,
   };
-  static constexpr BasicType _last_basic_type = dataref;
+  static constexpr BasicType _last_basic_type = v128;
 
   Type() : id(none) {}
 
@@ -133,9 +128,6 @@ public:
   // Signature, Struct or Array via implicit conversion to HeapType.
   Type(HeapType, Nullability nullable);
 
-  // Construct from rtt description
-  Type(Rtt);
-
   // Predicates
   //                 Compound Concrete
   //   Type        Basic │ Single│
@@ -152,12 +144,11 @@ public:
   // │ funcref     ║ x │   │ x │ x │ f  n  │ ┐ Ref
   // │ anyref      ║ x │   │ x │ x │ f? n  │ │  f_unc
   // │ eqref       ║ x │   │ x │ x │    n  │ │  n_ullable
-  // │ i31ref      ║ x │   │ x │ x │       │ │
-  // │ dataref     ║ x │   │ x │ x │       │ │
+  // │ i31ref      ║ x │   │ x │ x │    n  │ │
+  // │ dataref     ║ x │   │ x │ x │    n  │ │
   // ├─ Compound ──╫───┼───┼───┼───┤───────┤ │
   // │ Ref         ║   │ x │ x │ x │ f? n? │◄┘
   // │ Tuple       ║   │ x │   │ x │       │
-  // │ Rtt         ║   │ x │ x │ x │       │
   // └─────────────╨───┴───┴───┴───┴───────┘
   constexpr bool isBasic() const { return id <= _last_basic_type; }
   constexpr bool isConcrete() const { return id >= i32; }
@@ -179,18 +170,12 @@ public:
   // is irrelevant. (For that reason, this is only the negation of isNullable()
   // on references, but both return false on non-references.)
   bool isNonNullable() const;
-  bool isRtt() const;
+  // Whether this type is only inhabited by null values.
+  bool isNull() const;
   bool isStruct() const;
   bool isArray() const;
   bool isDefaultable() const;
 
-  // Check if a type is either defaultable or non-nullable. This is useful in
-  // the case where we allow non-nullable types, but we disallow other things
-  // that are non-defaultable. For example, when GC-non-nullable references are
-  // allowed we can have  a non-nullable reference, but we cannot have any other
-  // nondefaultable type.
-  bool isDefaultableOrNonNullable() const;
-
   Nullability getNullability() const;
 
 private:
@@ -242,12 +227,9 @@ public:
   const Tuple& getTuple() const;
 
   // Gets the heap type corresponding to this type, assuming that it is a
-  // reference or Rtt type.
+  // reference type.
   HeapType getHeapType() const;
 
-  // Gets the Rtt for this type, assuming that it is an Rtt type.
-  Rtt getRtt() const;
-
   // Returns a number type based on its size in bytes and whether it is a float
   // type.
   static Type get(unsigned byteSize, bool float_);
@@ -337,13 +319,22 @@ class HeapType {
 
 public:
   enum BasicHeapType : uint32_t {
+    ext,
     func,
     any,
     eq,
     i31,
     data,
+    array,
+    string,
+    stringview_wtf8,
+    stringview_wtf16,
+    stringview_iter,
+    none,
+    noext,
+    nofunc,
   };
-  static constexpr BasicHeapType _last_basic_type = data;
+  static constexpr BasicHeapType _last_basic_type = nofunc;
 
   // BasicHeapType can be implicitly upgraded to HeapType
   constexpr HeapType(BasicHeapType id) : id(id) {}
@@ -374,6 +365,7 @@ public:
   bool isSignature() const;
   bool isStruct() const;
   bool isArray() const;
+  bool isBottom() const;
 
   Signature getSignature() const;
   const Struct& getStruct() const;
@@ -387,6 +379,9 @@ public:
   // number of supertypes in its supertype chain.
   size_t getDepth() const;
 
+  // Get the bottom heap type for this heap type's hierarchy.
+  BasicHeapType getBottom() const;
+
   // Get the recursion group for this non-basic type.
   RecGroup getRecGroup() const;
   size_t getRecGroupIndex() const;
@@ -415,8 +410,8 @@ public:
   // exists.
   std::vector<HeapType> getReferencedHeapTypes() const;
 
-  // Return the LUB of two HeapTypes. The LUB always exists.
-  static HeapType getLeastUpperBound(HeapType a, HeapType b);
+  // Return the LUB of two HeapTypes, which may or may not exist.
+  static std::optional<HeapType> getLeastUpperBound(HeapType a, HeapType b);
 
   // Helper allowing the value of `print(...)` to be sent to an ostream. Stores
   // a `TypeID` because `Type` is incomplete at this point and using a reference
@@ -437,6 +432,8 @@ public:
   std::string toString() const;
 };
 
+inline bool Type::isNull() const { return isRef() && getHeapType().isBottom(); }
+
 // A recursion group consisting of one or more HeapTypes. HeapTypes with single
 // members are encoded without using any additional memory, which is why
 // `getHeapTypes` has to return a vector by value; it might have to create one
@@ -463,7 +460,7 @@ public:
   HeapType operator[](size_t i) const { return *Iterator{{this, i}}; }
 };
 
-typedef std::vector<Type> TypeList;
+using TypeList = std::vector<Type>;
 
 // Passed by reference rather than by value because it can own an unbounded
 // amount of data.
@@ -474,12 +471,6 @@ struct Tuple {
   Tuple(const TypeList& types) : types(types) { validate(); }
   Tuple(TypeList&& types) : types(std::move(types)) { validate(); }
 
-  // Allow copies when constructing.
-  Tuple(const Tuple& other) : types(other.types) { validate(); }
-
-  // Prevent accidental copies.
-  Tuple& operator=(const Tuple&) = delete;
-
   bool operator==(const Tuple& other) const { return types == other.types; }
   bool operator!=(const Tuple& other) const { return !(*this == other); }
   std::string toString() const;
@@ -540,7 +531,7 @@ struct Field {
   unsigned getByteSize() const;
 };
 
-typedef std::vector<Field> FieldList;
+using FieldList = std::vector<Field>;
 
 // Passed by reference rather than by value because it can own an unbounded
 // amount of data.
@@ -568,21 +559,6 @@ struct Array {
   std::string toString() const;
 };
 
-struct Rtt {
-  // An Rtt can have no depth specified
-  static constexpr uint32_t NoDepth = -1;
-  uint32_t depth;
-  HeapType heapType;
-  explicit Rtt(HeapType heapType) : depth(NoDepth), heapType(heapType) {}
-  Rtt(uint32_t depth, HeapType heapType) : depth(depth), heapType(heapType) {}
-  bool operator==(const Rtt& other) const {
-    return depth == other.depth && heapType == other.heapType;
-  }
-  bool operator!=(const Rtt& other) const { return !(*this == other); }
-  bool hasDepth() const { return depth != uint32_t(NoDepth); }
-  std::string toString() const;
-};
-
 // TypeBuilder - allows for the construction of recursive types. Contains a
 // table of `n` mutable HeapTypes and can construct temporary types that are
 // backed by those HeapTypes, refering to them by reference. Those temporary
@@ -630,16 +606,15 @@ struct TypeBuilder {
   HeapType getTempHeapType(size_t i);
 
   // Gets a temporary type or heap type for use in initializing the
-  // TypeBuilder's HeapTypes. For Ref and Rtt types, the HeapType may be a
-  // temporary HeapType owned by this builder or a canonical HeapType.
+  // TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary
+  // HeapType owned by this builder or a canonical HeapType.
   Type getTempTupleType(const Tuple&);
   Type getTempRefType(HeapType heapType, Nullability nullable);
-  Type getTempRttType(Rtt rtt);
 
   // In nominal mode, or for nominal types, declare the HeapType being built at
-  // index `i` to be an immediate subtype of the HeapType being built at index
-  // `j`. Does nothing for equirecursive types.
-  void setSubType(size_t i, size_t j);
+  // index `i` to be an immediate subtype of the given HeapType. Does nothing
+  // for equirecursive types.
+  void setSubType(size_t i, HeapType super);
 
   // Create a new recursion group covering slots [i, i + length). Groups must
   // not overlap or go out of bounds.
@@ -707,7 +682,7 @@ struct TypeBuilder {
     }
     Entry& subTypeOf(Entry other) {
       assert(&builder == &other.builder);
-      builder.setSubType(index, other.index);
+      builder.setSubType(index, other);
       return *this;
     }
   };
@@ -724,7 +699,6 @@ std::ostream& operator<<(std::ostream&, Signature);
 std::ostream& operator<<(std::ostream&, Field);
 std::ostream& operator<<(std::ostream&, Struct);
 std::ostream& operator<<(std::ostream&, Array);
-std::ostream& operator<<(std::ostream&, Rtt);
 std::ostream& operator<<(std::ostream&, TypeBuilder::ErrorReason);
 
 } // namespace wasm
@@ -759,10 +733,6 @@ template<> class hash<wasm::HeapType> {
 public:
   size_t operator()(const wasm::HeapType&) const;
 };
-template<> class hash<wasm::Rtt> {
-public:
-  size_t operator()(const wasm::Rtt&) const;
-};
 template<> class hash<wasm::RecGroup> {
 public:
   size_t operator()(const wasm::RecGroup&) const;
diff --git a/src/wasm-validator.h b/src/wasm-validator.h
index b7f17c1..ccef847 100644
--- a/src/wasm-validator.h
+++ b/src/wasm-validator.h
@@ -54,9 +54,13 @@ struct WasmValidator {
     Globally = 1 << 1,
     Quiet = 1 << 2
   };
-  typedef uint32_t Flags;
+  using Flags = uint32_t;
 
+  // Validate an entire module.
   bool validate(Module& module, Flags flags = Globally);
+
+  // Validate a specific function.
+  bool validate(Function* func, Module& module, Flags flags = Globally);
 };
 
 } // namespace wasm
diff --git a/src/wasm.h b/src/wasm.h
index b8e3d67..b37fdeb 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -30,6 +30,7 @@
 #include <map>
 #include <ostream>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "literal.h"
@@ -42,12 +43,12 @@
 namespace wasm {
 
 // An index in a wasm module
-typedef uint32_t Index;
+using Index = uint32_t;
 
 // An address in linear memory.
 struct Address {
-  typedef uint32_t address32_t;
-  typedef uint64_t address64_t;
+  using address32_t = uint32_t;
+  using address64_t = uint64_t;
   address64_t addr;
   constexpr Address() : addr(0) {}
   constexpr Address(uint64_t a) : addr(a) {}
@@ -474,7 +475,6 @@ enum BinaryOp {
   RelaxedMaxVecF64x2,
   RelaxedQ15MulrSVecI16x8,
   DotI8x16I7x16SToVecI16x8,
-  DotI8x16I7x16UToVecI16x8,
 
   InvalidBinary
 };
@@ -555,7 +555,6 @@ enum SIMDTernaryOp {
   LaneselectI32x4,
   LaneselectI64x2,
   DotI8x16I7x16AddSToVecI32x4,
-  DotI8x16I7x16AddUToVecI32x4,
 };
 
 enum RefIsOp {
@@ -570,6 +569,13 @@ enum RefAsOp {
   RefAsFunc,
   RefAsData,
   RefAsI31,
+  ExternInternalize,
+  ExternExternalize,
+};
+
+enum ArrayNewSegOp {
+  NewData,
+  NewElem,
 };
 
 enum BrOnOp {
@@ -585,6 +591,52 @@ enum BrOnOp {
   BrOnNonI31,
 };
 
+enum StringNewOp {
+  // Linear memory
+  StringNewUTF8,
+  StringNewWTF8,
+  StringNewReplace,
+  StringNewWTF16,
+  // GC
+  StringNewUTF8Array,
+  StringNewWTF8Array,
+  StringNewReplaceArray,
+  StringNewWTF16Array,
+};
+
+enum StringMeasureOp {
+  StringMeasureUTF8,
+  StringMeasureWTF8,
+  StringMeasureWTF16,
+  StringMeasureIsUSV,
+  StringMeasureWTF16View,
+};
+
+enum StringEncodeOp {
+  StringEncodeUTF8,
+  StringEncodeWTF8,
+  StringEncodeWTF16,
+  StringEncodeUTF8Array,
+  StringEncodeWTF8Array,
+  StringEncodeWTF16Array,
+};
+
+enum StringAsOp {
+  StringAsWTF8,
+  StringAsWTF16,
+  StringAsIter,
+};
+
+enum StringIterMoveOp {
+  StringIterMoveAdvance,
+  StringIterMoveRewind,
+};
+
+enum StringSliceWTFOp {
+  StringSliceWTF8,
+  StringSliceWTF16,
+};
+
 //
 // Expressions
 //
@@ -668,18 +720,30 @@ public:
     RefTestId,
     RefCastId,
     BrOnId,
-    RttCanonId,
-    RttSubId,
     StructNewId,
     StructGetId,
     StructSetId,
     ArrayNewId,
+    ArrayNewSegId,
     ArrayInitId,
     ArrayGetId,
     ArraySetId,
     ArrayLenId,
     ArrayCopyId,
     RefAsId,
+    StringNewId,
+    StringConstId,
+    StringMeasureId,
+    StringEncodeId,
+    StringConcatId,
+    StringEqId,
+    StringAsId,
+    StringWTF8AdvanceId,
+    StringWTF16GetId,
+    StringIterNextId,
+    StringIterMoveId,
+    StringSliceWTFId,
+    StringSliceIterId,
     NumExpressionIds
   };
   Id _id;
@@ -689,6 +753,15 @@ public:
 
   Expression(Id id) : _id(id) {}
 
+protected:
+  // An expression cannot be constructed without knowing what kind of expression
+  // it should be.
+  Expression(const Expression& other) = default;
+  Expression(Expression&& other) = default;
+  Expression& operator=(Expression& other) = default;
+  Expression& operator=(Expression&& other) = default;
+
+public:
   void finalize() {}
 
   template<class T> bool is() const {
@@ -732,7 +805,7 @@ const char* getExpressionName(Expression* curr);
 Literal getLiteralFromConstExpression(Expression* curr);
 Literals getLiteralsFromConstExpression(Expression* curr);
 
-typedef ArenaVector<Expression*> ExpressionList;
+using ExpressionList = ArenaVector<Expression*>;
 
 template<Expression::Id SID> class SpecificExpression : public Expression {
 public:
@@ -914,6 +987,7 @@ public:
   Address align;
   bool isAtomic;
   Expression* ptr;
+  Name memory;
 
   // type must be set during creation, cannot be inferred
 
@@ -932,6 +1006,7 @@ public:
   Expression* ptr;
   Expression* value;
   Type valueType;
+  Name memory;
 
   void finalize();
 };
@@ -946,6 +1021,7 @@ public:
   Address offset;
   Expression* ptr;
   Expression* value;
+  Name memory;
 
   void finalize();
 };
@@ -960,6 +1036,7 @@ public:
   Expression* ptr;
   Expression* expected;
   Expression* replacement;
+  Name memory;
 
   void finalize();
 };
@@ -974,6 +1051,7 @@ public:
   Expression* expected;
   Expression* timeout;
   Type expectedType;
+  Name memory;
 
   void finalize();
 };
@@ -986,6 +1064,7 @@ public:
   Address offset;
   Expression* ptr;
   Expression* notifyCount;
+  Name memory;
 
   void finalize();
 };
@@ -1074,6 +1153,7 @@ public:
   Address offset;
   Address align;
   Expression* ptr;
+  Name memory;
 
   Index getMemBytes();
   void finalize();
@@ -1091,6 +1171,7 @@ public:
   uint8_t index;
   Expression* ptr;
   Expression* vec;
+  Name memory;
 
   bool isStore();
   bool isLoad() { return !isStore(); }
@@ -1107,6 +1188,7 @@ public:
   Expression* dest;
   Expression* offset;
   Expression* size;
+  Name memory;
 
   void finalize();
 };
@@ -1129,6 +1211,8 @@ public:
   Expression* dest;
   Expression* source;
   Expression* size;
+  Name destMemory;
+  Name sourceMemory;
 
   void finalize();
 };
@@ -1141,6 +1225,7 @@ public:
   Expression* dest;
   Expression* value;
   Expression* size;
+  Name memory;
 
   void finalize();
 };
@@ -1224,6 +1309,7 @@ public:
   MemorySize(MixedArena& allocator) : MemorySize() {}
 
   Type ptrType = Type::i32;
+  Name memory;
 
   void make64();
   void finalize();
@@ -1236,6 +1322,7 @@ public:
 
   Expression* delta = nullptr;
   Type ptrType = Type::i32;
+  Name memory;
 
   void make64();
   void finalize();
@@ -1435,16 +1522,9 @@ public:
 
   Expression* ref;
 
-  // If rtt is provided then this is a dynamic test with an rtt. If nullptr then
-  // this is a static cast and intendedType is set, and it contains the type we
-  // intend to cast to.
-  Expression* rtt = nullptr;
   HeapType intendedType;
 
   void finalize();
-
-  // Returns the type we intend to cast to.
-  HeapType getIntendedType();
 };
 
 class RefCast : public SpecificExpression<Expression::RefCastId> {
@@ -1453,19 +1533,14 @@ public:
 
   Expression* ref;
 
-  // See above with RefTest.
-  Expression* rtt = nullptr;
   HeapType intendedType;
 
   // Support the unsafe `ref.cast_nop_static` to enable precise cast overhead
   // measurements.
   enum Safety { Safe, Unsafe };
-  Safety safety;
+  Safety safety = Safe;
 
   void finalize();
-
-  // Returns the type we intend to cast to.
-  HeapType getIntendedType();
 };
 
 class BrOn : public SpecificExpression<Expression::BrOnId> {
@@ -1476,55 +1551,18 @@ public:
   Name name;
   Expression* ref;
 
-  // BrOnCast* has, like RefCast and RefTest, either an rtt or a static intended
-  // type.
-  Expression* rtt = nullptr;
   HeapType intendedType;
 
-  // TODO: BrOnNull also has an optional extra value in the spec, which we do
-  //       not support. See also the discussion on
-  //       https://github.com/WebAssembly/function-references/issues/45
-  //       - depending on the decision there, we may want to move BrOnNull into
-  //       Break or a new class of its own.
-
   void finalize();
 
-  // Returns the type we intend to cast to. Relevant only for the cast variants.
-  HeapType getIntendedType();
-
   // Returns the type sent on the branch, if it is taken.
   Type getSentType();
 };
 
-class RttCanon : public SpecificExpression<Expression::RttCanonId> {
-public:
-  RttCanon(MixedArena& allocator) {}
-
-  void finalize();
-};
-
-class RttSub : public SpecificExpression<Expression::RttSubId> {
-public:
-  RttSub(MixedArena& allocator) {}
-
-  Expression* parent;
-
-  // rtt.fresh_sub is like rtt.sub, but never caching or canonicalizing (i.e.,
-  // it always returns a fresh RTT, non-identical to any other RTT in the
-  // system).
-  bool fresh = false;
-
-  void finalize();
-};
-
 class StructNew : public SpecificExpression<Expression::StructNewId> {
 public:
   StructNew(MixedArena& allocator) : operands(allocator) {}
 
-  // A dynamic StructNew has an rtt, while a static one declares the type using
-  // the type field.
-  Expression* rtt = nullptr;
-
   // A struct.new_with_default has empty operands. This does leave the case of a
   // struct with no fields ambiguous, but it doesn't make a difference in that
   // case, and binaryen doesn't guarantee roundtripping binaries anyhow.
@@ -1568,25 +1606,29 @@ public:
   Expression* init = nullptr;
   Expression* size;
 
-  // A dynamic ArrayNew has an rtt, while a static one declares the type using
-  // the type field.
-  Expression* rtt = nullptr;
-
   bool isWithDefault() { return !init; }
 
   void finalize();
 };
 
+class ArrayNewSeg : public SpecificExpression<Expression::ArrayNewSegId> {
+public:
+  ArrayNewSeg(MixedArena& allocator) {}
+
+  ArrayNewSegOp op;
+  Index segment;
+  Expression* offset;
+  Expression* size;
+
+  void finalize();
+};
+
 class ArrayInit : public SpecificExpression<Expression::ArrayInitId> {
 public:
   ArrayInit(MixedArena& allocator) : values(allocator) {}
 
   ExpressionList values;
 
-  // A dynamic ArrayInit has an rtt, while a static one declares the type using
-  // the type field.
-  Expression* rtt = nullptr;
-
   void finalize();
 };
 
@@ -1646,6 +1688,169 @@ public:
   void finalize();
 };
 
+class StringNew : public SpecificExpression<Expression::StringNewId> {
+public:
+  StringNew(MixedArena& allocator) {}
+
+  StringNewOp op;
+
+  // In linear memory variations this is the pointer in linear memory. In the
+  // GC variations this is an Array.
+  Expression* ptr;
+
+  // Used only in linear memory variations.
+  Expression* length = nullptr;
+
+  // Used only in GC variations.
+  Expression* start = nullptr;
+  Expression* end = nullptr;
+
+  void finalize();
+};
+
+class StringConst : public SpecificExpression<Expression::StringConstId> {
+public:
+  StringConst(MixedArena& allocator) {}
+
+  // TODO: Use a different type to allow null bytes in the middle -
+  //       ArenaVector<char> perhaps? However, Name has the benefit of being
+  //       interned and immutable (which is appropriate here).
+  Name string;
+
+  void finalize();
+};
+
+class StringMeasure : public SpecificExpression<Expression::StringMeasureId> {
+public:
+  StringMeasure(MixedArena& allocator) {}
+
+  StringMeasureOp op;
+
+  Expression* ref;
+
+  void finalize();
+};
+
+class StringEncode : public SpecificExpression<Expression::StringEncodeId> {
+public:
+  StringEncode(MixedArena& allocator) {}
+
+  StringEncodeOp op;
+
+  Expression* ref;
+
+  // In linear memory variations this is the pointer in linear memory. In the
+  // GC variations this is an Array.
+  Expression* ptr;
+
+  // Used only in GC variations, where it is the index in |ptr| to start
+  // encoding from.
+  Expression* start = nullptr;
+
+  void finalize();
+};
+
+class StringConcat : public SpecificExpression<Expression::StringConcatId> {
+public:
+  StringConcat(MixedArena& allocator) {}
+
+  Expression* left;
+  Expression* right;
+
+  void finalize();
+};
+
+class StringEq : public SpecificExpression<Expression::StringEqId> {
+public:
+  StringEq(MixedArena& allocator) {}
+
+  Expression* left;
+  Expression* right;
+
+  void finalize();
+};
+
+class StringAs : public SpecificExpression<Expression::StringAsId> {
+public:
+  StringAs(MixedArena& allocator) {}
+
+  StringAsOp op;
+
+  Expression* ref;
+
+  void finalize();
+};
+
+class StringWTF8Advance
+  : public SpecificExpression<Expression::StringWTF8AdvanceId> {
+public:
+  StringWTF8Advance(MixedArena& allocator) {}
+
+  Expression* ref;
+  Expression* pos;
+  Expression* bytes;
+
+  void finalize();
+};
+
+class StringWTF16Get : public SpecificExpression<Expression::StringWTF16GetId> {
+public:
+  StringWTF16Get(MixedArena& allocator) {}
+
+  Expression* ref;
+  Expression* pos;
+
+  void finalize();
+};
+
+class StringIterNext : public SpecificExpression<Expression::StringIterNextId> {
+public:
+  StringIterNext(MixedArena& allocator) {}
+
+  Expression* ref;
+
+  void finalize();
+};
+
+class StringIterMove : public SpecificExpression<Expression::StringIterMoveId> {
+public:
+  StringIterMove(MixedArena& allocator) {}
+
+  // Whether the movement is to advance or reverse.
+  StringIterMoveOp op;
+
+  Expression* ref;
+
+  // How many codepoints to advance or reverse.
+  Expression* num;
+
+  void finalize();
+};
+
+class StringSliceWTF : public SpecificExpression<Expression::StringSliceWTFId> {
+public:
+  StringSliceWTF(MixedArena& allocator) {}
+
+  StringSliceWTFOp op;
+
+  Expression* ref;
+  Expression* start;
+  Expression* end;
+
+  void finalize();
+};
+
+class StringSliceIter
+  : public SpecificExpression<Expression::StringSliceIterId> {
+public:
+  StringSliceIter(MixedArena& allocator) {}
+
+  Expression* ref;
+  Expression* num;
+
+  void finalize();
+};
+
 // Globals
 
 struct Named {
@@ -1794,6 +1999,7 @@ public:
 
   Name getLocalName(Index index);
   Index getLocalIndex(Name name);
+  bool hasLocalIndex(Name name) const;
   Index getVarIndexBase();
   Type getLocalType(Index index);
 
@@ -1829,12 +2035,14 @@ public:
 class ElementSegment : public Named {
 public:
   Name table;
-  Expression* offset;
-  Type type = Type::funcref;
+  Expression* offset = nullptr;
+  Type type = Type(HeapType::func, Nullable);
   std::vector<Expression*> data;
 
   ElementSegment() = default;
-  ElementSegment(Name table, Expression* offset, Type type = Type::funcref)
+  ElementSegment(Name table,
+                 Expression* offset,
+                 Type type = Type(HeapType::func, Nullable))
     : table(table), offset(offset), type(type) {}
   ElementSegment(Name table,
                  Expression* offset,
@@ -1854,7 +2062,7 @@ public:
 
   Address initial = 0;
   Address max = kMaxSize;
-  Type type = Type::funcref;
+  Type type = Type(HeapType::func, Nullable);
 
   bool hasMax() { return max != kUnlimitedSize; }
   void clear() {
@@ -1864,6 +2072,14 @@ public:
   }
 };
 
+class DataSegment : public Named {
+public:
+  Name memory;
+  bool isPassive = false;
+  Expression* offset = nullptr;
+  std::vector<char> data; // TODO: optimize
+};
+
 class Memory : public Importable {
 public:
   static const Address::address32_t kPageSize = 64 * 1024;
@@ -1871,51 +2087,21 @@ public:
   // In wasm32, the maximum memory size is limited by a 32-bit pointer: 4GB
   static const Address::address32_t kMaxSize32 =
     (uint64_t(4) * 1024 * 1024 * 1024) / kPageSize;
+  // in wasm64, the maximum number of pages
+  static const Address::address64_t kMaxSize64 = 1ull << (64 - 16);
 
-  struct Segment {
-    // For use in name section only
-    Name name;
-    bool isPassive = false;
-    Expression* offset = nullptr;
-    std::vector<char> data; // TODO: optimize
-    Segment() = default;
-    Segment(Expression* offset) : offset(offset) {}
-    Segment(Expression* offset, const char* init, Address size)
-      : offset(offset) {
-      data.resize(size);
-      std::copy_n(init, size, data.begin());
-    }
-    Segment(Expression* offset, std::vector<char>& init) : offset(offset) {
-      data.swap(init);
-    }
-    Segment(Name name,
-            bool isPassive,
-            Expression* offset,
-            const char* init,
-            Address size)
-      : name(name), isPassive(isPassive), offset(offset) {
-      data.resize(size);
-      std::copy_n(init, size, data.begin());
-    }
-  };
-
-  bool exists = false;
   Address initial = 0; // sizes are in pages
   Address max = kMaxSize32;
-  std::vector<Segment> segments;
 
   bool shared = false;
   Type indexType = Type::i32;
 
-  Memory() { name = Name::fromInt(0); }
   bool hasMax() { return max != kUnlimitedSize; }
   bool is64() { return indexType == Type::i64; }
   void clear() {
-    exists = false;
     name = "";
     initial = 0;
     max = kMaxSize32;
-    segments.clear();
     shared = false;
     indexType = Type::i32;
   }
@@ -1959,9 +2145,10 @@ public:
   std::vector<std::unique_ptr<Global>> globals;
   std::vector<std::unique_ptr<Tag>> tags;
   std::vector<std::unique_ptr<ElementSegment>> elementSegments;
+  std::vector<std::unique_ptr<Memory>> memories;
+  std::vector<std::unique_ptr<DataSegment>> dataSegments;
   std::vector<std::unique_ptr<Table>> tables;
 
-  Memory memory;
   Name start;
 
   std::vector<UserSection> userSections;
@@ -1993,7 +2180,9 @@ private:
   std::unordered_map<Name, Export*> exportsMap;
   std::unordered_map<Name, Function*> functionsMap;
   std::unordered_map<Name, Table*> tablesMap;
+  std::unordered_map<Name, Memory*> memoriesMap;
   std::unordered_map<Name, ElementSegment*> elementSegmentsMap;
+  std::unordered_map<Name, DataSegment*> dataSegmentsMap;
   std::unordered_map<Name, Global*> globalsMap;
   std::unordered_map<Name, Tag*> tagsMap;
 
@@ -2004,12 +2193,16 @@ public:
   Function* getFunction(Name name);
   Table* getTable(Name name);
   ElementSegment* getElementSegment(Name name);
+  Memory* getMemory(Name name);
+  DataSegment* getDataSegment(Name name);
   Global* getGlobal(Name name);
   Tag* getTag(Name name);
 
   Export* getExportOrNull(Name name);
   Table* getTableOrNull(Name name);
+  Memory* getMemoryOrNull(Name name);
   ElementSegment* getElementSegmentOrNull(Name name);
+  DataSegment* getDataSegmentOrNull(Name name);
   Function* getFunctionOrNull(Name name);
   Global* getGlobalOrNull(Name name);
   Tag* getTagOrNull(Name name);
@@ -2023,6 +2216,8 @@ public:
   Function* addFunction(std::unique_ptr<Function>&& curr);
   Table* addTable(std::unique_ptr<Table>&& curr);
   ElementSegment* addElementSegment(std::unique_ptr<ElementSegment>&& curr);
+  Memory* addMemory(std::unique_ptr<Memory>&& curr);
+  DataSegment* addDataSegment(std::unique_ptr<DataSegment>&& curr);
   Global* addGlobal(std::unique_ptr<Global>&& curr);
   Tag* addTag(std::unique_ptr<Tag>&& curr);
 
@@ -2032,6 +2227,8 @@ public:
   void removeFunction(Name name);
   void removeTable(Name name);
   void removeElementSegment(Name name);
+  void removeMemory(Name name);
+  void removeDataSegment(Name name);
   void removeGlobal(Name name);
   void removeTag(Name name);
 
@@ -2039,9 +2236,12 @@ public:
   void removeFunctions(std::function<bool(Function*)> pred);
   void removeTables(std::function<bool(Table*)> pred);
   void removeElementSegments(std::function<bool(ElementSegment*)> pred);
+  void removeMemories(std::function<bool(Memory*)> pred);
+  void removeDataSegments(std::function<bool(DataSegment*)> pred);
   void removeGlobals(std::function<bool(Global*)> pred);
   void removeTags(std::function<bool(Tag*)> pred);
 
+  void updateDataSegmentsMap();
   void updateMaps();
 
   void clearDebugInfo();
diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt
index 20fc2e9..7b7f856 100644
--- a/src/wasm/CMakeLists.txt
+++ b/src/wasm/CMakeLists.txt
@@ -12,6 +12,8 @@ set(wasm_SOURCES
   wasm-stack.cpp
   wasm-type.cpp
   wasm-validator.cpp
+  wat-lexer.cpp
+  wat-parser.cpp
   ${wasm_HEADERS}
 )
 # wasm-debug.cpp includes LLVM header using std::iterator (deprecated in C++17)
diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
index b7bb831..7890c7a 100644
--- a/src/wasm/literal.cpp
+++ b/src/wasm/literal.cpp
@@ -44,43 +44,35 @@ Literal::Literal(Type type) : type(type) {
         memset(&v128, 0, 16);
         return;
       case Type::none:
-        return;
       case Type::unreachable:
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref:
-        break;
+        WASM_UNREACHABLE("Invalid literal type");
+        return;
     }
   }
 
-  if (isData()) {
-    assert(!type.isNonNullable());
+  if (type.isNull()) {
+    assert(type.isNullable());
     new (&gcData) std::shared_ptr<GCData>();
-  } else if (type.isRtt()) {
-    new (this) Literal(Literal::makeCanonicalRtt(type.getHeapType()));
-  } else {
-    // For anything else, zero out all the union data.
-    memset(&v128, 0, 16);
+    return;
   }
+
+  if (type.isRef() && type.getHeapType() == HeapType::i31) {
+    assert(type.isNonNullable());
+    i32 = 0;
+    return;
+  }
+
+  WASM_UNREACHABLE("Unexpected literal type");
 }
 
 Literal::Literal(const uint8_t init[16]) : type(Type::v128) {
   memcpy(&v128, init, 16);
 }
 
-Literal::Literal(std::shared_ptr<GCData> gcData, Type type)
-  : gcData(gcData), type(type) {
-  // Null data is only allowed if nullable.
-  assert(gcData || type.isNullable());
+Literal::Literal(std::shared_ptr<GCData> gcData, HeapType type)
+  : gcData(gcData), type(type, NonNullable) {
   // The type must be a proper type for GC data.
-  assert(isData());
-}
-
-Literal::Literal(std::unique_ptr<RttSupers>&& rttSupers, Type type)
-  : rttSupers(std::move(rttSupers)), type(type) {
-  assert(type.isRtt());
+  assert((isData() && gcData) || (type.isBottom() && !gcData));
 }
 
 Literal::Literal(const Literal& other) : type(other.type) {
@@ -100,14 +92,13 @@ Literal::Literal(const Literal& other) : type(other.type) {
       case Type::none:
         return;
       case Type::unreachable:
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref:
         break;
     }
   }
+  if (other.isNull()) {
+    new (&gcData) std::shared_ptr<GCData>();
+    return;
+  }
   if (other.isData()) {
     new (&gcData) std::shared_ptr<GCData>(other.gcData);
     return;
@@ -116,24 +107,32 @@ Literal::Literal(const Literal& other) : type(other.type) {
     func = other.func;
     return;
   }
-  if (type.isRtt()) {
-    // Allocate a new RttSupers with a copy of the other's data.
-    new (&rttSupers) auto(std::make_unique<RttSupers>(*other.rttSupers));
-    return;
-  }
   if (type.isRef()) {
+    assert(!type.isNullable());
     auto heapType = type.getHeapType();
     if (heapType.isBasic()) {
       switch (heapType.getBasic()) {
-        case HeapType::any:
-        case HeapType::eq:
-          return; // null
         case HeapType::i31:
           i32 = other.i32;
           return;
+        case HeapType::none:
+        case HeapType::noext:
+        case HeapType::nofunc:
+          // Null
+          return;
+        case HeapType::ext:
+        case HeapType::any:
+          WASM_UNREACHABLE("TODO: extern literals");
+        case HeapType::eq:
         case HeapType::func:
         case HeapType::data:
+        case HeapType::array:
           WASM_UNREACHABLE("invalid type");
+        case HeapType::string:
+        case HeapType::stringview_wtf8:
+        case HeapType::stringview_wtf16:
+        case HeapType::stringview_iter:
+          WASM_UNREACHABLE("TODO: string literals");
       }
     }
   }
@@ -144,11 +143,8 @@ Literal::~Literal() {
   if (type.isBasic()) {
     return;
   }
-
-  if (isData()) {
+  if (isNull() || isData()) {
     gcData.~shared_ptr();
-  } else if (type.isRtt()) {
-    rttSupers.~unique_ptr();
   }
 }
 
@@ -160,18 +156,6 @@ Literal& Literal::operator=(const Literal& other) {
   return *this;
 }
 
-Literal Literal::makeCanonicalRtt(HeapType type) {
-  auto supers = std::make_unique<RttSupers>();
-  std::optional<HeapType> supertype;
-  for (auto curr = type; (supertype = curr.getSuperType()); curr = *supertype) {
-    supers->emplace_back(*supertype);
-  }
-  // We want the highest types to be first.
-  std::reverse(supers->begin(), supers->end());
-  size_t depth = supers->size();
-  return Literal(std::move(supers), Type(Rtt(depth, type)));
-}
-
 template<typename LaneT, int Lanes>
 static void extractBytes(uint8_t (&dest)[16], const LaneArray<Lanes>& lanes) {
   std::array<uint8_t, 16> bytes;
@@ -235,13 +219,7 @@ Literals Literal::makeNegOnes(Type type) {
 Literal Literal::makeZero(Type type) {
   assert(type.isSingle());
   if (type.isRef()) {
-    if (type == Type::i31ref) {
-      return makeI31(0);
-    } else {
-      return makeNull(type);
-    }
-  } else if (type.isRtt()) {
-    return Literal(type);
+    return makeNull(type.getHeapType());
   } else {
     return makeFromInt32(0, type);
   }
@@ -257,6 +235,71 @@ Literal Literal::makeNegOne(Type type) {
   return makeFromInt32(-1, type);
 }
 
+Literal Literal::makeFromMemory(void* p, Type type) {
+  assert(type.isNumber());
+  switch (type.getBasic()) {
+    case Type::i32: {
+      int32_t i;
+      memcpy(&i, p, sizeof(i));
+      return Literal(i);
+    }
+    case Type::i64: {
+      int64_t i;
+      memcpy(&i, p, sizeof(i));
+      return Literal(i);
+    }
+    case Type::f32: {
+      int32_t i;
+      memcpy(&i, p, sizeof(i));
+      return Literal(bit_cast<float>(i));
+    }
+    case Type::f64: {
+      int64_t i;
+      memcpy(&i, p, sizeof(i));
+      return Literal(bit_cast<double>(i));
+    }
+    case Type::v128: {
+      uint8_t bytes[16];
+      memcpy(bytes, p, sizeof(bytes));
+      return Literal(bytes);
+    }
+    default:
+      WASM_UNREACHABLE("unexpected type");
+  }
+}
+
+Literal Literal::makeFromMemory(void* p, const Field& field) {
+  switch (field.packedType) {
+    case Field::not_packed:
+      return makeFromMemory(p, field.type);
+    case Field::i8: {
+      int8_t i;
+      memcpy(&i, p, sizeof(i));
+      return Literal(int32_t(i));
+    }
+    case Field::i16: {
+      int16_t i;
+      memcpy(&i, p, sizeof(i));
+      return Literal(int32_t(i));
+    }
+  }
+  WASM_UNREACHABLE("unexpected type");
+}
+
+Literal Literal::standardizeNaN(const Literal& input) {
+  if (!std::isnan(input.getFloat())) {
+    return input;
+  }
+  // Pick a simple canonical payload, and positive.
+  if (input.type == Type::f32) {
+    return Literal(bit_cast<float>(uint32_t(0x7fc00000u)));
+  } else if (input.type == Type::f64) {
+    return Literal(bit_cast<double>(uint64_t(0x7ff8000000000000ull)));
+  } else {
+    WASM_UNREACHABLE("unexpected type");
+  }
+}
+
 std::array<uint8_t, 16> Literal::getv128() const {
   assert(type == Type::v128);
   std::array<uint8_t, 16> ret;
@@ -265,15 +308,10 @@ std::array<uint8_t, 16> Literal::getv128() const {
 }
 
 std::shared_ptr<GCData> Literal::getGCData() const {
-  assert(isData());
+  assert(isNull() || isData());
   return gcData;
 }
 
-const RttSupers& Literal::getRttSupers() const {
-  assert(type.isRtt());
-  return *rttSupers;
-}
-
 Literal Literal::castToF32() {
   assert(type == Type::i32);
   Literal ret(Type::f32);
@@ -351,63 +389,45 @@ void Literal::getBits(uint8_t (&buf)[16]) const {
       break;
     case Type::none:
     case Type::unreachable:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
       WASM_UNREACHABLE("invalid type");
   }
 }
 
 bool Literal::operator==(const Literal& other) const {
-  // The types must be identical, unless both are references - in that case,
-  // nulls of different types *do* compare equal.
-  if (type.isRef() && other.type.isRef() && (isNull() || other.isNull())) {
-    return isNull() && other.isNull();
-  }
   if (type != other.type) {
     return false;
   }
-  auto compareRef = [&]() {
-    assert(type.isRef());
-    // Note that we've already handled nulls earlier.
-    if (type.isFunction()) {
-      assert(func.is() && other.func.is());
-      return func == other.func;
-    }
-    if (type.isData()) {
-      return gcData == other.gcData;
-    }
-    // other non-null reference type literals cannot represent concrete values,
-    // i.e. there is no concrete anyref or eqref other than null.
-    WASM_UNREACHABLE("unexpected type");
-  };
   if (type.isBasic()) {
     switch (type.getBasic()) {
       case Type::none:
         return true; // special voided literal
       case Type::i32:
       case Type::f32:
-      case Type::i31ref:
         return i32 == other.i32;
       case Type::i64:
       case Type::f64:
         return i64 == other.i64;
       case Type::v128:
         return memcmp(v128, other.v128, 16) == 0;
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::dataref:
-        return compareRef();
       case Type::unreachable:
         break;
     }
   } else if (type.isRef()) {
-    return compareRef();
-  } else if (type.isRtt()) {
-    return *rttSupers == *other.rttSupers;
+    assert(type.isRef());
+    if (type.isNull()) {
+      return true;
+    }
+    if (type.isFunction()) {
+      assert(func.is() && other.func.is());
+      return func == other.func;
+    }
+    if (type.isData()) {
+      return gcData == other.gcData;
+    }
+    if (type.getHeapType() == HeapType::i31) {
+      return i32 == other.i32;
+    }
+    WASM_UNREACHABLE("unexpected type");
   }
   WASM_UNREACHABLE("unexpected type");
 }
@@ -507,49 +527,8 @@ void Literal::printVec128(std::ostream& o, const std::array<uint8_t, 16>& v) {
 
 std::ostream& operator<<(std::ostream& o, Literal literal) {
   prepareMinorColor(o);
-  if (literal.type.isFunction()) {
-    if (literal.isNull()) {
-      o << "funcref(null)";
-    } else {
-      o << "funcref(" << literal.getFunc() << ")";
-    }
-  } else if (literal.type.isRef()) {
-    if (literal.isData()) {
-      auto data = literal.getGCData();
-      if (data) {
-        o << "[ref " << data->rtt << ' ' << data->values << ']';
-      } else {
-        o << "[ref null " << literal.type << ']';
-      }
-    } else {
-      switch (literal.type.getHeapType().getBasic()) {
-        case HeapType::any:
-          assert(literal.isNull() && "unexpected non-null anyref literal");
-          o << "anyref(null)";
-          break;
-        case HeapType::eq:
-          assert(literal.isNull() && "unexpected non-null eqref literal");
-          o << "eqref(null)";
-          break;
-        case HeapType::i31:
-          o << "i31ref(" << literal.geti31() << ")";
-          break;
-        case HeapType::func:
-        case HeapType::data:
-          WASM_UNREACHABLE("type should have been handled above");
-      }
-    }
-  } else if (literal.type.isRtt()) {
-    o << "[rtt ";
-    for (auto& super : literal.getRttSupers()) {
-      o << super.type << " :> ";
-      if (super.freshPtr) {
-        o << " (fresh)";
-      }
-    }
-    o << literal.type << ']';
-  } else {
-    TODO_SINGLE_COMPOUND(literal.type);
+  assert(literal.type.isSingle());
+  if (literal.type.isBasic()) {
     switch (literal.type.getBasic()) {
       case Type::none:
         o << "?";
@@ -570,14 +549,48 @@ std::ostream& operator<<(std::ostream& o, Literal literal) {
         o << "i32x4 ";
         literal.printVec128(o, literal.getv128());
         break;
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref:
       case Type::unreachable:
         WASM_UNREACHABLE("unexpected type");
     }
+  } else {
+    assert(literal.type.isRef());
+    auto heapType = literal.type.getHeapType();
+    if (heapType.isBasic()) {
+      switch (heapType.getBasic()) {
+        case HeapType::i31:
+          o << "i31ref(" << literal.geti31() << ")";
+          break;
+        case HeapType::none:
+          o << "nullref";
+          break;
+        case HeapType::noext:
+          o << "nullexternref";
+          break;
+        case HeapType::nofunc:
+          o << "nullfuncref";
+          break;
+        case HeapType::ext:
+        case HeapType::any:
+          WASM_UNREACHABLE("TODO: extern literals");
+        case HeapType::eq:
+        case HeapType::func:
+        case HeapType::data:
+        case HeapType::array:
+          WASM_UNREACHABLE("invalid type");
+        case HeapType::string:
+        case HeapType::stringview_wtf8:
+        case HeapType::stringview_wtf16:
+        case HeapType::stringview_iter:
+          WASM_UNREACHABLE("TODO: string literals");
+      }
+    } else if (heapType.isSignature()) {
+      o << "funcref(" << literal.getFunc() << ")";
+    } else {
+      assert(literal.isData());
+      auto data = literal.getGCData();
+      assert(data);
+      o << "[ref " << data->type << ' ' << data->values << ']';
+    }
   }
   restoreNormalColor(o);
   return o;
@@ -793,11 +806,6 @@ Literal Literal::eqz() const {
     case Type::f64:
       return eq(Literal(double(0)));
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -816,11 +824,6 @@ Literal Literal::neg() const {
     case Type::f64:
       return Literal(int64_t(i64 ^ 0x8000000000000000ULL)).castToF64();
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -839,11 +842,6 @@ Literal Literal::abs() const {
     case Type::f64:
       return Literal(int64_t(i64 & 0x7fffffffffffffffULL)).castToF64();
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -934,40 +932,6 @@ Literal Literal::demote() const {
   return Literal(float(getf64()));
 }
 
-// Wasm has nondeterministic rules for NaN propagation in some operations. For
-// example. f32.neg is deterministic and just flips the sign, even of a NaN, but
-// f32.add is nondeterministic, and if one or more of the inputs is a NaN, then
-//
-//  * if all NaNs are canonical NaNs, the output is some arbitrary canonical NaN
-//  * otherwise the output is some arbitrary arithmetic NaN
-//
-// (canonical = NaN payload is 1000..000; arithmetic: 1???..???, that is, the
-// high bit is 1 and all others can be 0 or 1)
-//
-// For many things we don't need to care, and can just do a normal C++ add for
-// an f32.add, for example - the wasm rules are specified so that things like
-// that just work (in order for such math to be fast). However, for our
-// optimizer, it is useful to "standardize" NaNs when there is nondeterminism.
-// That is, when there are multiple valid outputs, it's nice to emit the same
-// one consistently, so that it doesn't look like the optimization changed
-// something. In other words, if the valid output of an expression is a set of
-// valid NaNs, and after optimization the output is still that same set, then
-// the optimization is valid. And if the interpreter picks the same NaN in both
-// cases from that identical set then nothing looks wrong to the fuzzer.
-template<typename T> static Literal standardizeNaN(T result) {
-  if (!std::isnan(result)) {
-    return Literal(result);
-  }
-  // Pick a simple canonical payload, and positive.
-  if (sizeof(T) == 4) {
-    return Literal(Literal(uint32_t(0x7fc00000u)).reinterpretf32());
-  } else if (sizeof(T) == 8) {
-    return Literal(Literal(uint64_t(0x7ff8000000000000ull)).reinterpretf64());
-  } else {
-    WASM_UNREACHABLE("invalid float");
-  }
-}
-
 Literal Literal::add(const Literal& other) const {
   switch (type.getBasic()) {
     case Type::i32:
@@ -975,15 +939,10 @@ Literal Literal::add(const Literal& other) const {
     case Type::i64:
       return Literal(uint64_t(i64) + uint64_t(other.i64));
     case Type::f32:
-      return standardizeNaN(getf32() + other.getf32());
+      return standardizeNaN(Literal(getf32() + other.getf32()));
     case Type::f64:
-      return standardizeNaN(getf64() + other.getf64());
+      return standardizeNaN(Literal(getf64() + other.getf64()));
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -998,15 +957,10 @@ Literal Literal::sub(const Literal& other) const {
     case Type::i64:
       return Literal(uint64_t(i64) - uint64_t(other.i64));
     case Type::f32:
-      return standardizeNaN(getf32() - other.getf32());
+      return standardizeNaN(Literal(getf32() - other.getf32()));
     case Type::f64:
-      return standardizeNaN(getf64() - other.getf64());
+      return standardizeNaN(Literal(getf64() - other.getf64()));
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -1100,15 +1054,10 @@ Literal Literal::mul(const Literal& other) const {
     case Type::i64:
       return Literal(uint64_t(i64) * uint64_t(other.i64));
     case Type::f32:
-      return standardizeNaN(getf32() * other.getf32());
+      return standardizeNaN(Literal(getf32() * other.getf32()));
     case Type::f64:
-      return standardizeNaN(getf64() * other.getf64());
+      return standardizeNaN(Literal(getf64() * other.getf64()));
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -1126,7 +1075,7 @@ Literal Literal::div(const Literal& other) const {
           switch (std::fpclassify(lhs)) {
             case FP_NAN:
             case FP_ZERO:
-              return standardizeNaN(lhs / rhs);
+              return standardizeNaN(Literal(lhs / rhs));
             case FP_NORMAL:    // fallthrough
             case FP_SUBNORMAL: // fallthrough
             case FP_INFINITE:
@@ -1139,7 +1088,7 @@ Literal Literal::div(const Literal& other) const {
         case FP_INFINITE: // fallthrough
         case FP_NORMAL:   // fallthrough
         case FP_SUBNORMAL:
-          return standardizeNaN(lhs / rhs);
+          return standardizeNaN(Literal(lhs / rhs));
         default:
           WASM_UNREACHABLE("invalid fp classification");
       }
@@ -1152,7 +1101,7 @@ Literal Literal::div(const Literal& other) const {
           switch (std::fpclassify(lhs)) {
             case FP_NAN:
             case FP_ZERO:
-              return standardizeNaN(lhs / rhs);
+              return standardizeNaN(Literal(lhs / rhs));
             case FP_NORMAL:    // fallthrough
             case FP_SUBNORMAL: // fallthrough
             case FP_INFINITE:
@@ -1165,7 +1114,7 @@ Literal Literal::div(const Literal& other) const {
         case FP_INFINITE: // fallthrough
         case FP_NORMAL:   // fallthrough
         case FP_SUBNORMAL:
-          return standardizeNaN(lhs / rhs);
+          return standardizeNaN(Literal(lhs / rhs));
         default:
           WASM_UNREACHABLE("invalid fp classification");
       }
@@ -1339,11 +1288,6 @@ Literal Literal::eq(const Literal& other) const {
     case Type::f64:
       return Literal(getf64() == other.getf64());
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -1362,11 +1306,6 @@ Literal Literal::ne(const Literal& other) const {
     case Type::f64:
       return Literal(getf64() != other.getf64());
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -1511,10 +1450,10 @@ Literal Literal::min(const Literal& other) const {
     case Type::f32: {
       auto l = getf32(), r = other.getf32();
       if (std::isnan(l)) {
-        return standardizeNaN(l);
+        return standardizeNaN(Literal(l));
       }
       if (std::isnan(r)) {
-        return standardizeNaN(r);
+        return standardizeNaN(Literal(r));
       }
       if (l == r && l == 0) {
         return Literal(std::signbit(l) ? l : r);
@@ -1524,10 +1463,10 @@ Literal Literal::min(const Literal& other) const {
     case Type::f64: {
       auto l = getf64(), r = other.getf64();
       if (std::isnan(l)) {
-        return standardizeNaN(l);
+        return standardizeNaN(Literal(l));
       }
       if (std::isnan(r)) {
-        return standardizeNaN(r);
+        return standardizeNaN(Literal(r));
       }
       if (l == r && l == 0) {
         return Literal(std::signbit(l) ? l : r);
@@ -1544,10 +1483,10 @@ Literal Literal::max(const Literal& other) const {
     case Type::f32: {
       auto l = getf32(), r = other.getf32();
       if (std::isnan(l)) {
-        return standardizeNaN(l);
+        return standardizeNaN(Literal(l));
       }
       if (std::isnan(r)) {
-        return standardizeNaN(r);
+        return standardizeNaN(Literal(r));
       }
       if (l == r && l == 0) {
         return Literal(std::signbit(l) ? r : l);
@@ -1557,10 +1496,10 @@ Literal Literal::max(const Literal& other) const {
     case Type::f64: {
       auto l = getf64(), r = other.getf64();
       if (std::isnan(l)) {
-        return standardizeNaN(l);
+        return standardizeNaN(Literal(l));
       }
       if (std::isnan(r)) {
-        return standardizeNaN(r);
+        return standardizeNaN(Literal(r));
       }
       if (l == r && l == 0) {
         return Literal(std::signbit(l) ? r : l);
@@ -2624,31 +2563,4 @@ Literal Literal::relaxedFmsF64x2(const Literal& left,
   return ternary<2, &Literal::getLanesF64x2, &Literal::fms>(*this, left, right);
 }
 
-bool Literal::isSubRtt(const Literal& other) const {
-  assert(type.isRtt() && other.type.isRtt());
-  // For this literal to be a sub-rtt of the other rtt, the supers must be a
-  // superset. That is, if other is a->b->c then we should be a->b->c as well
-  // with possibly ->d->.. added. The rttSupers array represents those chains,
-  // but only the supers, which means the last item in the chain is simply the
-  // type of the literal.
-  const auto& supers = getRttSupers();
-  const auto& otherSupers = other.getRttSupers();
-  if (otherSupers.size() > supers.size()) {
-    return false;
-  }
-  for (Index i = 0; i < otherSupers.size(); i++) {
-    if (supers[i] != otherSupers[i]) {
-      return false;
-    }
-  }
-  // If we have more supers than other, compare that extra super. Otherwise,
-  // we have the same amount of supers, and must be completely identical to
-  // other.
-  if (otherSupers.size() < supers.size()) {
-    return other.type.getHeapType() == supers[otherSupers.size()].type;
-  } else {
-    return other.type == type;
-  }
-}
-
 } // namespace wasm
diff --git a/src/wasm/parsing.cpp b/src/wasm/parsing.cpp
index dc0d332..46ec398 100644
--- a/src/wasm/parsing.cpp
+++ b/src/wasm/parsing.cpp
@@ -55,7 +55,7 @@ Name UniqueNameMapper::getPrefixedName(Name prefix) {
   }
   // make sure to return a unique name not already on the stack
   while (1) {
-    Name ret = Name(prefix.str + std::to_string(otherIndex++));
+    Name ret = prefix.toString() + std::to_string(otherIndex++);
     if (reverseLabelMapping.find(ret) == reverseLabelMapping.end()) {
       return ret;
     }
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 9cd7e79..7829795 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -52,8 +52,11 @@ void WasmBinaryWriter::write() {
   writeImports();
   writeFunctionSignatures();
   writeTableDeclarations();
-  writeMemory();
+  writeMemories();
   writeTags();
+  if (wasm->features.hasStrings()) {
+    writeStrings();
+  }
   writeGlobals();
   writeExports();
   writeStart();
@@ -200,18 +203,21 @@ void WasmBinaryWriter::writeStart() {
   finishSection(start);
 }
 
-void WasmBinaryWriter::writeMemory() {
-  if (!wasm->memory.exists || wasm->memory.imported()) {
+void WasmBinaryWriter::writeMemories() {
+  if (importInfo->getNumDefinedMemories() == 0) {
     return;
   }
-  BYN_TRACE("== writeMemory\n");
+  BYN_TRACE("== writeMemories\n");
   auto start = startSection(BinaryConsts::Section::Memory);
-  o << U32LEB(1); // Define 1 memory
-  writeResizableLimits(wasm->memory.initial,
-                       wasm->memory.max,
-                       wasm->memory.hasMax(),
-                       wasm->memory.shared,
-                       wasm->memory.is64());
+  auto num = importInfo->getNumDefinedMemories();
+  o << U32LEB(num);
+  ModuleUtils::iterDefinedMemories(*wasm, [&](Memory* memory) {
+    writeResizableLimits(memory->initial,
+                         memory->max,
+                         memory->hasMax(),
+                         memory->shared,
+                         memory->is64());
+  });
   finishSection(start);
 }
 
@@ -223,7 +229,12 @@ void WasmBinaryWriter::writeTypes() {
   // the type section. With nominal typing there is always one group and with
   // equirecursive typing there is one group per type.
   size_t numGroups = 0;
-  switch (getTypeSystem()) {
+  // MVP types are structural and do not use recursion groups.
+  TypeSystem typeSystem = getTypeSystem();
+  if (!wasm->features.hasGC()) {
+    typeSystem = TypeSystem::Equirecursive;
+  }
+  switch (typeSystem) {
     case TypeSystem::Equirecursive:
       numGroups = indexedTypes.types.size();
       break;
@@ -242,7 +253,7 @@ void WasmBinaryWriter::writeTypes() {
   BYN_TRACE("== writeTypes\n");
   auto start = startSection(BinaryConsts::Section::Type);
   o << U32LEB(numGroups);
-  if (getTypeSystem() == TypeSystem::Nominal) {
+  if (typeSystem == TypeSystem::Nominal) {
     // The nominal recursion group contains every type.
     o << S32LEB(BinaryConsts::EncodedType::Rec)
       << U32LEB(indexedTypes.types.size());
@@ -325,16 +336,16 @@ void WasmBinaryWriter::writeImports() {
     o << uint8_t(0); // Reserved 'attribute' field. Always 0.
     o << U32LEB(getTypeIndex(tag->sig));
   });
-  if (wasm->memory.imported()) {
+  ModuleUtils::iterImportedMemories(*wasm, [&](Memory* memory) {
     BYN_TRACE("write one memory\n");
-    writeImportHeader(&wasm->memory);
+    writeImportHeader(memory);
     o << U32LEB(int32_t(ExternalKind::Memory));
-    writeResizableLimits(wasm->memory.initial,
-                         wasm->memory.max,
-                         wasm->memory.hasMax(),
-                         wasm->memory.shared,
-                         wasm->memory.is64());
-  }
+    writeResizableLimits(memory->initial,
+                         memory->max,
+                         memory->hasMax(),
+                         memory->shared,
+                         memory->is64());
+  });
   ModuleUtils::iterImportedTables(*wasm, [&](Table* table) {
     BYN_TRACE("write one table\n");
     writeImportHeader(table);
@@ -442,10 +453,78 @@ void WasmBinaryWriter::writeFunctions() {
     tableOfContents.functionBodies.emplace_back(
       func->name, sizePos + sizeFieldSize, size);
     binaryLocationTrackedExpressionsForFunc.clear();
+
+    if (func->getParams().size() > WebLimitations::MaxFunctionParams) {
+      std::cerr << "Some VMs may not accept this binary because it has a large "
+                << "number of parameters in function " << func->name << ".\n";
+    }
   });
   finishSection(sectionStart);
 }
 
+void WasmBinaryWriter::writeStrings() {
+  assert(wasm->features.hasStrings());
+
+  // Scan the entire wasm to find the relevant strings.
+  // To find all the string literals we must scan all the code.
+  using StringSet = std::unordered_set<Name>;
+
+  struct StringWalker : public PostWalker<StringWalker> {
+    StringSet& strings;
+
+    StringWalker(StringSet& strings) : strings(strings) {}
+
+    void visitStringConst(StringConst* curr) { strings.insert(curr->string); }
+  };
+
+  ModuleUtils::ParallelFunctionAnalysis<StringSet> analysis(
+    *wasm, [&](Function* func, StringSet& strings) {
+      if (!func->imported()) {
+        StringWalker(strings).walk(func->body);
+      }
+    });
+
+  // Also walk the global module code (for simplicity, also add it to the
+  // function map, using a "function" key of nullptr).
+  auto& globalStrings = analysis.map[nullptr];
+  StringWalker(globalStrings).walkModuleCode(wasm);
+
+  // Generate the indexes from the combined set of necessary strings,
+  // which we sort for determinism.
+  StringSet allStrings;
+  for (auto& [func, strings] : analysis.map) {
+    for (auto& string : strings) {
+      allStrings.insert(string);
+    }
+  }
+  std::vector<Name> sorted;
+  for (auto& string : allStrings) {
+    sorted.push_back(string);
+  }
+  std::sort(sorted.begin(), sorted.end());
+  for (Index i = 0; i < sorted.size(); i++) {
+    stringIndexes[sorted[i]] = i;
+  }
+
+  auto num = sorted.size();
+  if (num == 0) {
+    return;
+  }
+
+  auto start = startSection(BinaryConsts::Section::Strings);
+
+  // Placeholder for future use in the spec.
+  o << U32LEB(0);
+
+  // The number of strings and then their contents.
+  o << U32LEB(num);
+  for (auto& string : sorted) {
+    writeInlineString(string.str);
+  }
+
+  finishSection(start);
+}
+
 void WasmBinaryWriter::writeGlobals() {
   if (importInfo->getNumDefinedGlobals() == 0) {
     return;
@@ -492,10 +571,10 @@ void WasmBinaryWriter::writeExports() {
         o << U32LEB(getFunctionIndex(curr->value));
         break;
       case ExternalKind::Table:
-        o << U32LEB(0);
+        o << U32LEB(getTableIndex(curr->value));
         break;
       case ExternalKind::Memory:
-        o << U32LEB(0);
+        o << U32LEB(getMemoryIndex(curr->value));
         break;
       case ExternalKind::Global:
         o << U32LEB(getGlobalIndex(curr->value));
@@ -511,36 +590,36 @@ void WasmBinaryWriter::writeExports() {
 }
 
 void WasmBinaryWriter::writeDataCount() {
-  if (!wasm->features.hasBulkMemory() || !wasm->memory.segments.size()) {
+  if (!wasm->features.hasBulkMemory() || !wasm->dataSegments.size()) {
     return;
   }
   auto start = startSection(BinaryConsts::Section::DataCount);
-  o << U32LEB(wasm->memory.segments.size());
+  o << U32LEB(wasm->dataSegments.size());
   finishSection(start);
 }
 
 void WasmBinaryWriter::writeDataSegments() {
-  if (wasm->memory.segments.size() == 0) {
+  if (wasm->dataSegments.size() == 0) {
     return;
   }
-  if (wasm->memory.segments.size() > WebLimitations::MaxDataSegments) {
+  if (wasm->dataSegments.size() > WebLimitations::MaxDataSegments) {
     std::cerr << "Some VMs may not accept this binary because it has a large "
               << "number of data segments. Run the limit-segments pass to "
               << "merge segments.\n";
   }
   auto start = startSection(BinaryConsts::Section::Data);
-  o << U32LEB(wasm->memory.segments.size());
-  for (auto& segment : wasm->memory.segments) {
+  o << U32LEB(wasm->dataSegments.size());
+  for (auto& segment : wasm->dataSegments) {
     uint32_t flags = 0;
-    if (segment.isPassive) {
+    if (segment->isPassive) {
       flags |= BinaryConsts::IsPassive;
     }
     o << U32LEB(flags);
-    if (!segment.isPassive) {
-      writeExpression(segment.offset);
+    if (!segment->isPassive) {
+      writeExpression(segment->offset);
       o << int8_t(BinaryConsts::End);
     }
-    writeInlineBuffer(segment.data.data(), segment.data.size());
+    writeInlineBuffer(segment->data.data(), segment->data.size());
   }
   finishSection(start);
 }
@@ -557,6 +636,12 @@ uint32_t WasmBinaryWriter::getTableIndex(Name name) const {
   return it->second;
 }
 
+uint32_t WasmBinaryWriter::getMemoryIndex(Name name) const {
+  auto it = indexes.memoryIndexes.find(name);
+  assert(it != indexes.memoryIndexes.end());
+  return it->second;
+}
+
 uint32_t WasmBinaryWriter::getGlobalIndex(Name name) const {
   auto it = indexes.globalIndexes.find(name);
   assert(it != indexes.globalIndexes.end());
@@ -580,6 +665,12 @@ uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const {
   return it->second;
 }
 
+uint32_t WasmBinaryWriter::getStringIndex(Name string) const {
+  auto it = stringIndexes.find(string);
+  assert(it != stringIndexes.end());
+  return it->second;
+}
+
 void WasmBinaryWriter::writeTableDeclarations() {
   if (importInfo->getNumDefinedTables() == 0) {
     // std::cerr << std::endl << "(WasmBinaryWriter::writeTableDeclarations) No
@@ -615,6 +706,7 @@ void WasmBinaryWriter::writeElementSegments() {
   auto start = startSection(BinaryConsts::Section::Element);
   o << U32LEB(elemCount);
 
+  Type funcref = Type(HeapType::func, Nullable);
   for (auto& segment : wasm->elementSegments) {
     Index tableIdx = 0;
 
@@ -630,7 +722,7 @@ void WasmBinaryWriter::writeElementSegments() {
     if (!isPassive) {
       tableIdx = getTableIndex(segment->table);
       hasTableIndex =
-        tableIdx > 0 || wasm->getTable(segment->table)->type != Type::funcref;
+        tableIdx > 0 || wasm->getTable(segment->table)->type != funcref;
     }
 
     uint32_t flags = 0;
@@ -770,28 +862,30 @@ void WasmBinaryWriter::writeNames() {
         // Pairs of (local index in IR, name).
         std::vector<std::pair<Index, Name>> localsWithNames;
         auto numLocals = func->getNumLocals();
-        for (Index i = 0; i < numLocals; ++i) {
-          if (func->hasLocalName(i)) {
-            localsWithNames.push_back({i, func->getLocalName(i)});
+        for (Index indexInFunc = 0; indexInFunc < numLocals; ++indexInFunc) {
+          if (func->hasLocalName(indexInFunc)) {
+            Index indexInBinary;
+            auto iter = funcMappedLocals.find(func->name);
+            if (iter != funcMappedLocals.end()) {
+              // TODO: handle multivalue
+              indexInBinary = iter->second[{indexInFunc, 0}];
+            } else {
+              // No data on funcMappedLocals. That is only possible if we are an
+              // imported function, where there are no locals to map, and in
+              // that case the index is unchanged anyhow: parameters always have
+              // the same index, they are not mapped in any way.
+              assert(func->imported());
+              indexInBinary = indexInFunc;
+            }
+            localsWithNames.push_back(
+              {indexInBinary, func->getLocalName(indexInFunc)});
           }
         }
         assert(localsWithNames.size());
+        std::sort(localsWithNames.begin(), localsWithNames.end());
         o << U32LEB(index);
         o << U32LEB(localsWithNames.size());
-        for (auto& [indexInFunc, name] : localsWithNames) {
-          // TODO: handle multivalue
-          Index indexInBinary;
-          auto iter = funcMappedLocals.find(func->name);
-          if (iter != funcMappedLocals.end()) {
-            indexInBinary = iter->second[{indexInFunc, 0}];
-          } else {
-            // No data on funcMappedLocals. That is only possible if we are an
-            // imported function, where there are no locals to map, and in that
-            // case the index is unchanged anyhow: parameters always have the
-            // same index, they are not mapped in any way.
-            assert(func->imported());
-            indexInBinary = indexInFunc;
-          }
+        for (auto& [indexInBinary, name] : localsWithNames) {
           o << U32LEB(indexInBinary);
           writeEscapedName(name.str);
         }
@@ -851,12 +945,28 @@ void WasmBinaryWriter::writeNames() {
   }
 
   // memory names
-  if (wasm->memory.exists && wasm->memory.hasExplicitName) {
-    auto substart =
-      startSubsection(BinaryConsts::UserSections::Subsection::NameMemory);
-    o << U32LEB(1) << U32LEB(0); // currently exactly 1 memory at index 0
-    writeEscapedName(wasm->memory.name.str);
-    finishSubsection(substart);
+  {
+    std::vector<std::pair<Index, Memory*>> memoriesWithNames;
+    Index checked = 0;
+    auto check = [&](Memory* curr) {
+      if (curr->hasExplicitName) {
+        memoriesWithNames.push_back({checked, curr});
+      }
+      checked++;
+    };
+    ModuleUtils::iterImportedMemories(*wasm, check);
+    ModuleUtils::iterDefinedMemories(*wasm, check);
+    assert(checked == indexes.memoryIndexes.size());
+    if (memoriesWithNames.size() > 0) {
+      auto substart =
+        startSubsection(BinaryConsts::UserSections::Subsection::NameMemory);
+      o << U32LEB(memoriesWithNames.size());
+      for (auto& [index, memory] : memoriesWithNames) {
+        o << U32LEB(index);
+        writeEscapedName(memory->name.str);
+      }
+      finishSubsection(substart);
+    }
   }
 
   // global names
@@ -911,10 +1021,10 @@ void WasmBinaryWriter::writeNames() {
   }
 
   // data segment names
-  if (wasm->memory.exists) {
+  if (!wasm->memories.empty()) {
     Index count = 0;
-    for (auto& seg : wasm->memory.segments) {
-      if (seg.name.is()) {
+    for (auto& seg : wasm->dataSegments) {
+      if (seg->hasExplicitName) {
         count++;
       }
     }
@@ -923,11 +1033,11 @@ void WasmBinaryWriter::writeNames() {
       auto substart =
         startSubsection(BinaryConsts::UserSections::Subsection::NameData);
       o << U32LEB(count);
-      for (Index i = 0; i < wasm->memory.segments.size(); i++) {
-        auto& seg = wasm->memory.segments[i];
-        if (seg.name.is()) {
+      for (Index i = 0; i < wasm->dataSegments.size(); i++) {
+        auto& seg = wasm->dataSegments[i];
+        if (seg->name.is()) {
           o << U32LEB(i);
-          writeEscapedName(seg.name.str);
+          writeEscapedName(seg->name.str);
         }
       }
       finishSubsection(substart);
@@ -965,6 +1075,30 @@ void WasmBinaryWriter::writeNames() {
     }
   }
 
+  // tag names
+  if (!wasm->tags.empty()) {
+    Index count = 0;
+    for (auto& tag : wasm->tags) {
+      if (tag->hasExplicitName) {
+        count++;
+      }
+    }
+
+    if (count) {
+      auto substart =
+        startSubsection(BinaryConsts::UserSections::Subsection::NameTag);
+      o << U32LEB(count);
+      for (Index i = 0; i < wasm->tags.size(); i++) {
+        auto& tag = wasm->tags[i];
+        if (tag->hasExplicitName) {
+          o << U32LEB(i);
+          writeEscapedName(tag->name.str);
+        }
+      }
+      finishSubsection(substart);
+    }
+  }
+
   finishSection(start);
 }
 
@@ -1089,12 +1223,14 @@ void WasmBinaryWriter::writeFeaturesSection() {
         return BinaryConsts::UserSections::GCFeature;
       case FeatureSet::Memory64:
         return BinaryConsts::UserSections::Memory64Feature;
-      case FeatureSet::TypedFunctionReferences:
-        return BinaryConsts::UserSections::TypedFunctionReferencesFeature;
       case FeatureSet::RelaxedSIMD:
         return BinaryConsts::UserSections::RelaxedSIMDFeature;
       case FeatureSet::ExtendedConst:
         return BinaryConsts::UserSections::ExtendedConstFeature;
+      case FeatureSet::Strings:
+        return BinaryConsts::UserSections::StringsFeature;
+      case FeatureSet::MultiMemories:
+        return BinaryConsts::UserSections::MultiMemoriesFeature;
       default:
         WASM_UNREACHABLE("unexpected feature flag");
     }
@@ -1127,7 +1263,7 @@ void WasmBinaryWriter::writeLegacyDylinkSection() {
   o << U32LEB(wasm->dylinkSection->tableAlignment);
   o << U32LEB(wasm->dylinkSection->neededDynlibs.size());
   for (auto& neededDynlib : wasm->dylinkSection->neededDynlibs) {
-    writeInlineString(neededDynlib.c_str());
+    writeInlineString(neededDynlib.str);
   }
   finishSection(start);
 }
@@ -1158,7 +1294,7 @@ void WasmBinaryWriter::writeDylinkSection() {
       startSubsection(BinaryConsts::UserSections::Subsection::DylinkNeeded);
     o << U32LEB(wasm->dylinkSection->neededDynlibs.size());
     for (auto& neededDynlib : wasm->dylinkSection->neededDynlibs) {
-      writeInlineString(neededDynlib.c_str());
+      writeInlineString(neededDynlib.str);
     }
     finishSubsection(substart);
   }
@@ -1214,10 +1350,9 @@ void WasmBinaryWriter::writeData(const char* data, size_t size) {
   }
 }
 
-void WasmBinaryWriter::writeInlineString(const char* name) {
-  int32_t size = strlen(name);
-  o << U32LEB(size);
-  writeData(name, size);
+void WasmBinaryWriter::writeInlineString(std::string_view name) {
+  o << U32LEB(name.size());
+  writeData(name.data(), name.size());
 }
 
 static bool isHexDigit(char ch) {
@@ -1229,19 +1364,17 @@ static int decodeHexNibble(char ch) {
   return ch <= '9' ? ch & 15 : (ch & 15) + 9;
 }
 
-void WasmBinaryWriter::writeEscapedName(const char* name) {
-  assert(name);
-  if (!strpbrk(name, "\\")) {
+void WasmBinaryWriter::writeEscapedName(std::string_view name) {
+  if (name.find('\\') == std::string_view::npos) {
     writeInlineString(name);
     return;
   }
   // decode escaped by escapeName (see below) function names
   std::string unescaped;
-  int32_t size = strlen(name);
-  for (int32_t i = 0; i < size;) {
+  for (size_t i = 0; i < name.size();) {
     char ch = name[i++];
     // support only `\xx` escapes; ignore invalid or unsupported escapes
-    if (ch != '\\' || i + 1 >= size || !isHexDigit(name[i]) ||
+    if (ch != '\\' || i + 1 >= name.size() || !isHexDigit(name[i]) ||
         !isHexDigit(name[i + 1])) {
       unescaped.push_back(ch);
       continue;
@@ -1250,7 +1383,7 @@ void WasmBinaryWriter::writeEscapedName(const char* name) {
       char((decodeHexNibble(name[i]) << 4) | decodeHexNibble(name[i + 1])));
     i += 2;
   }
-  writeInlineString(unescaped.c_str());
+  writeInlineString({unescaped.data(), unescaped.size()});
 }
 
 void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {
@@ -1259,7 +1392,64 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {
 }
 
 void WasmBinaryWriter::writeType(Type type) {
-  if (type.isRef() && !type.isBasic()) {
+  if (type.isRef()) {
+    auto heapType = type.getHeapType();
+    if (heapType.isBasic() && type.isNullable()) {
+      switch (heapType.getBasic()) {
+        case HeapType::ext:
+          o << S32LEB(BinaryConsts::EncodedType::externref);
+          return;
+        case HeapType::any:
+          o << S32LEB(BinaryConsts::EncodedType::anyref);
+          return;
+        case HeapType::func:
+          o << S32LEB(BinaryConsts::EncodedType::funcref);
+          return;
+        case HeapType::eq:
+          o << S32LEB(BinaryConsts::EncodedType::eqref);
+          return;
+        case HeapType::i31:
+          o << S32LEB(BinaryConsts::EncodedType::i31ref);
+          return;
+        case HeapType::data:
+          o << S32LEB(BinaryConsts::EncodedType::dataref);
+          return;
+        case HeapType::array:
+          o << S32LEB(BinaryConsts::EncodedType::arrayref);
+          return;
+        case HeapType::string:
+          o << S32LEB(BinaryConsts::EncodedType::stringref);
+          return;
+        case HeapType::stringview_wtf8:
+          o << S32LEB(BinaryConsts::EncodedType::stringview_wtf8);
+          return;
+        case HeapType::stringview_wtf16:
+          o << S32LEB(BinaryConsts::EncodedType::stringview_wtf16);
+          return;
+        case HeapType::stringview_iter:
+          o << S32LEB(BinaryConsts::EncodedType::stringview_iter);
+          return;
+        case HeapType::none:
+          o << S32LEB(BinaryConsts::EncodedType::nullref);
+          return;
+        case HeapType::noext:
+          // See comment on writeHeapType.
+          if (!wasm->features.hasGC()) {
+            o << S32LEB(BinaryConsts::EncodedType::externref);
+          } else {
+            o << S32LEB(BinaryConsts::EncodedType::nullexternref);
+          }
+          return;
+        case HeapType::nofunc:
+          // See comment on writeHeapType.
+          if (!wasm->features.hasGC()) {
+            o << S32LEB(BinaryConsts::EncodedType::funcref);
+          } else {
+            o << S32LEB(BinaryConsts::EncodedType::nullfuncref);
+          }
+          return;
+      }
+    }
     if (type.isNullable()) {
       o << S32LEB(BinaryConsts::EncodedType::nullable);
     } else {
@@ -1268,17 +1458,6 @@ void WasmBinaryWriter::writeType(Type type) {
     writeHeapType(type.getHeapType());
     return;
   }
-  if (type.isRtt()) {
-    auto rtt = type.getRtt();
-    if (rtt.hasDepth()) {
-      o << S32LEB(BinaryConsts::EncodedType::rtt_n);
-      o << U32LEB(rtt.depth);
-    } else {
-      o << S32LEB(BinaryConsts::EncodedType::rtt);
-    }
-    writeIndexedHeapType(rtt.heapType);
-    return;
-  }
   int ret = 0;
   TODO_SINGLE_COMPOUND(type);
   switch (type.getBasic()) {
@@ -1301,21 +1480,6 @@ void WasmBinaryWriter::writeType(Type type) {
     case Type::v128:
       ret = BinaryConsts::EncodedType::v128;
       break;
-    case Type::funcref:
-      ret = BinaryConsts::EncodedType::funcref;
-      break;
-    case Type::anyref:
-      ret = BinaryConsts::EncodedType::anyref;
-      break;
-    case Type::eqref:
-      ret = BinaryConsts::EncodedType::eqref;
-      break;
-    case Type::i31ref:
-      ret = BinaryConsts::EncodedType::i31ref;
-      break;
-    case Type::dataref:
-      ret = BinaryConsts::EncodedType::dataref;
-      break;
     default:
       WASM_UNREACHABLE("unexpected type");
   }
@@ -1323,31 +1487,66 @@ void WasmBinaryWriter::writeType(Type type) {
 }
 
 void WasmBinaryWriter::writeHeapType(HeapType type) {
+  // ref.null always has a bottom heap type in Binaryen IR, but those types are
+  // only actually valid with GC enabled. When GC is not enabled, emit the
+  // corresponding valid top types instead.
+  if (!wasm->features.hasGC()) {
+    if (type == HeapType::nofunc || type.isSignature()) {
+      type = HeapType::func;
+    } else if (type == HeapType::noext) {
+      type = HeapType::ext;
+    }
+  }
+
   if (type.isSignature() || type.isStruct() || type.isArray()) {
     o << S64LEB(getTypeIndex(type)); // TODO: Actually s33
     return;
   }
   int ret = 0;
-  if (type.isBasic()) {
-    switch (type.getBasic()) {
-      case HeapType::func:
-        ret = BinaryConsts::EncodedHeapType::func;
-        break;
-      case HeapType::any:
-        ret = BinaryConsts::EncodedHeapType::any;
-        break;
-      case HeapType::eq:
-        ret = BinaryConsts::EncodedHeapType::eq;
-        break;
-      case HeapType::i31:
-        ret = BinaryConsts::EncodedHeapType::i31;
-        break;
-      case HeapType::data:
-        ret = BinaryConsts::EncodedHeapType::data;
-        break;
-    }
-  } else {
-    WASM_UNREACHABLE("TODO: compound GC types");
+  assert(type.isBasic());
+  switch (type.getBasic()) {
+    case HeapType::ext:
+      ret = BinaryConsts::EncodedHeapType::ext;
+      break;
+    case HeapType::func:
+      ret = BinaryConsts::EncodedHeapType::func;
+      break;
+    case HeapType::any:
+      ret = BinaryConsts::EncodedHeapType::any;
+      break;
+    case HeapType::eq:
+      ret = BinaryConsts::EncodedHeapType::eq;
+      break;
+    case HeapType::i31:
+      ret = BinaryConsts::EncodedHeapType::i31;
+      break;
+    case HeapType::data:
+      ret = BinaryConsts::EncodedHeapType::data;
+      break;
+    case HeapType::array:
+      ret = BinaryConsts::EncodedHeapType::array;
+      break;
+    case HeapType::string:
+      ret = BinaryConsts::EncodedHeapType::string;
+      break;
+    case HeapType::stringview_wtf8:
+      ret = BinaryConsts::EncodedHeapType::stringview_wtf8_heap;
+      break;
+    case HeapType::stringview_wtf16:
+      ret = BinaryConsts::EncodedHeapType::stringview_wtf16_heap;
+      break;
+    case HeapType::stringview_iter:
+      ret = BinaryConsts::EncodedHeapType::stringview_iter_heap;
+      break;
+    case HeapType::none:
+      ret = BinaryConsts::EncodedHeapType::none;
+      break;
+    case HeapType::noext:
+      ret = BinaryConsts::EncodedHeapType::noext;
+      break;
+    case HeapType::nofunc:
+      ret = BinaryConsts::EncodedHeapType::nofunc;
+      break;
   }
   o << S64LEB(ret); // TODO: Actually s33
 }
@@ -1446,7 +1645,7 @@ void WasmBinaryBuilder::read() {
         readStart();
         break;
       case BinaryConsts::Section::Memory:
-        readMemory();
+        readMemories();
         break;
       case BinaryConsts::Section::Type:
         readTypes();
@@ -1469,6 +1668,9 @@ void WasmBinaryBuilder::read() {
       case BinaryConsts::Section::Element:
         readElementSegments();
         break;
+      case BinaryConsts::Section::Strings:
+        readStrings();
+        break;
       case BinaryConsts::Section::Global:
         readGlobals();
         break;
@@ -1476,7 +1678,7 @@ void WasmBinaryBuilder::read() {
         readDataSegments();
         break;
       case BinaryConsts::Section::DataCount:
-        readDataCount();
+        readDataSegmentCount();
         break;
       case BinaryConsts::Section::Table:
         readTableDeclarations();
@@ -1540,17 +1742,16 @@ void WasmBinaryBuilder::readUserSection(size_t payloadLen) {
     auto& section = wasm.userSections.back();
     section.name = sectionName.str;
     auto data = getByteView(payloadLen);
-    section.data = {data.first, data.second};
+    section.data = {data.begin(), data.end()};
   }
 }
 
-std::pair<const char*, const char*>
-WasmBinaryBuilder::getByteView(size_t size) {
+std::string_view WasmBinaryBuilder::getByteView(size_t size) {
   if (size > input.size() || pos > input.size() - size) {
     throwError("unexpected end of input");
   }
   pos += size;
-  return {input.data() + (pos - size), input.data() + pos};
+  return {input.data() + (pos - size), size};
 }
 
 uint8_t WasmBinaryBuilder::getInt8() {
@@ -1657,10 +1858,6 @@ int64_t WasmBinaryBuilder::getS64LEB() {
   return ret.value;
 }
 
-uint64_t WasmBinaryBuilder::getUPtrLEB() {
-  return wasm.memory.is64() ? getU64LEB() : getU32LEB();
-}
-
 bool WasmBinaryBuilder::getBasicType(int32_t code, Type& out) {
   switch (code) {
     case BinaryConsts::EncodedType::i32:
@@ -1679,19 +1876,46 @@ bool WasmBinaryBuilder::getBasicType(int32_t code, Type& out) {
       out = Type::v128;
       return true;
     case BinaryConsts::EncodedType::funcref:
-      out = Type::funcref;
+      out = Type(HeapType::func, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::externref:
+      out = Type(HeapType::ext, Nullable);
       return true;
     case BinaryConsts::EncodedType::anyref:
-      out = Type::anyref;
+      out = Type(HeapType::any, Nullable);
       return true;
     case BinaryConsts::EncodedType::eqref:
-      out = Type::eqref;
+      out = Type(HeapType::eq, Nullable);
       return true;
     case BinaryConsts::EncodedType::i31ref:
-      out = Type(HeapType::i31, NonNullable);
+      out = Type(HeapType::i31, Nullable);
       return true;
     case BinaryConsts::EncodedType::dataref:
-      out = Type(HeapType::data, NonNullable);
+      out = Type(HeapType::data, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::arrayref:
+      out = Type(HeapType::array, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::stringref:
+      out = Type(HeapType::string, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::stringview_wtf8:
+      out = Type(HeapType::stringview_wtf8, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::stringview_wtf16:
+      out = Type(HeapType::stringview_wtf16, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::stringview_iter:
+      out = Type(HeapType::stringview_iter, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::nullref:
+      out = Type(HeapType::none, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::nullexternref:
+      out = Type(HeapType::noext, Nullable);
+      return true;
+    case BinaryConsts::EncodedType::nullfuncref:
+      out = Type(HeapType::nofunc, Nullable);
       return true;
     default:
       return false;
@@ -1703,6 +1927,9 @@ bool WasmBinaryBuilder::getBasicHeapType(int64_t code, HeapType& out) {
     case BinaryConsts::EncodedHeapType::func:
       out = HeapType::func;
       return true;
+    case BinaryConsts::EncodedHeapType::ext:
+      out = HeapType::ext;
+      return true;
     case BinaryConsts::EncodedHeapType::any:
       out = HeapType::any;
       return true;
@@ -1715,6 +1942,30 @@ bool WasmBinaryBuilder::getBasicHeapType(int64_t code, HeapType& out) {
     case BinaryConsts::EncodedHeapType::data:
       out = HeapType::data;
       return true;
+    case BinaryConsts::EncodedHeapType::array:
+      out = HeapType::array;
+      return true;
+    case BinaryConsts::EncodedHeapType::string:
+      out = HeapType::string;
+      return true;
+    case BinaryConsts::EncodedHeapType::stringview_wtf8_heap:
+      out = HeapType::stringview_wtf8;
+      return true;
+    case BinaryConsts::EncodedHeapType::stringview_wtf16_heap:
+      out = HeapType::stringview_wtf16;
+      return true;
+    case BinaryConsts::EncodedHeapType::stringview_iter_heap:
+      out = HeapType::stringview_iter;
+      return true;
+    case BinaryConsts::EncodedHeapType::none:
+      out = HeapType::none;
+      return true;
+    case BinaryConsts::EncodedHeapType::noext:
+      out = HeapType::noext;
+      return true;
+    case BinaryConsts::EncodedHeapType::nofunc:
+      out = HeapType::nofunc;
+      return true;
     default:
       return false;
   }
@@ -1738,14 +1989,6 @@ Type WasmBinaryBuilder::getType(int initial) {
       return Type(getHeapType(), Nullable);
     case BinaryConsts::EncodedType::nonnullable:
       return Type(getHeapType(), NonNullable);
-    case BinaryConsts::EncodedType::rtt_n: {
-      auto depth = getU32LEB();
-      auto heapType = getIndexedHeapType();
-      return Type(Rtt(depth, heapType));
-    }
-    case BinaryConsts::EncodedType::rtt: {
-      return Type(Rtt(getIndexedHeapType()));
-    }
     default:
       throwError("invalid wasm type: " + std::to_string(initial));
   }
@@ -1791,17 +2034,10 @@ Type WasmBinaryBuilder::getConcreteType() {
 Name WasmBinaryBuilder::getInlineString() {
   BYN_TRACE("<==\n");
   auto len = getU32LEB();
-
   auto data = getByteView(len);
 
-  std::string str(data.first, data.second);
-  if (str.find('\0') != std::string::npos) {
-    throwError(
-      "inline string contains NULL (0). that is technically valid in wasm, "
-      "but you shouldn't do it, and it's not supported in binaryen");
-  }
-  BYN_TRACE("getInlineString: " << str << " ==>\n");
-  return Name(str);
+  BYN_TRACE("getInlineString: " << data << " ==>\n");
+  return Name(data);
 }
 
 void WasmBinaryBuilder::verifyInt8(int8_t x) {
@@ -1843,32 +2079,25 @@ void WasmBinaryBuilder::readStart() {
   startIndex = getU32LEB();
 }
 
-void WasmBinaryBuilder::readMemory() {
-  BYN_TRACE("== readMemory\n");
-  auto numMemories = getU32LEB();
-  if (!numMemories) {
-    return;
-  }
-  if (numMemories != 1) {
-    throwError("Must be exactly 1 memory");
-  }
-  if (wasm.memory.exists) {
-    throwError("Memory cannot be both imported and defined");
+void WasmBinaryBuilder::readMemories() {
+  BYN_TRACE("== readMemories\n");
+  auto num = getU32LEB();
+  BYN_TRACE("num: " << num << std::endl);
+  for (size_t i = 0; i < num; i++) {
+    BYN_TRACE("read one\n");
+    auto memory = Builder::makeMemory(Name::fromInt(i));
+    getResizableLimits(memory->initial,
+                       memory->max,
+                       memory->shared,
+                       memory->indexType,
+                       Memory::kUnlimitedSize);
+    wasm.addMemory(std::move(memory));
   }
-  wasm.memory.exists = true;
-  getResizableLimits(wasm.memory.initial,
-                     wasm.memory.max,
-                     wasm.memory.shared,
-                     wasm.memory.indexType,
-                     Memory::kUnlimitedSize);
 }
 
 void WasmBinaryBuilder::readTypes() {
   BYN_TRACE("== readTypes\n");
   TypeBuilder builder(getU32LEB());
-  if (getTypeSystem() == TypeSystem::Nominal && builder.size() > 1) {
-    throwError("Nominal type sections must have a single element");
-  }
   BYN_TRACE("num: " << builder.size() << std::endl);
 
   auto makeType = [&](int32_t typeCode) {
@@ -1893,16 +2122,6 @@ void WasmBinaryBuilder::readTypes() {
         }
         return builder.getTempRefType(builder[size_t(htCode)], nullability);
       }
-      case BinaryConsts::EncodedType::rtt_n:
-      case BinaryConsts::EncodedType::rtt: {
-        auto depth = typeCode == BinaryConsts::EncodedType::rtt ? Rtt::NoDepth
-                                                                : getU32LEB();
-        auto htCode = getU32LEB();
-        if (size_t(htCode) >= builder.size()) {
-          throwError("invalid type index: " + std::to_string(htCode));
-        }
-        return builder.getTempRttType(Rtt(depth, builder[htCode]));
-      }
       default:
         throwError("unexpected type index: " + std::to_string(typeCode));
     }
@@ -2064,6 +2283,13 @@ Name WasmBinaryBuilder::getTableName(Index index) {
   return wasm.tables[index]->name;
 }
 
+Name WasmBinaryBuilder::getMemoryName(Index index) {
+  if (index >= wasm.memories.size()) {
+    throwError("invalid memory index");
+  }
+  return wasm.memories[index]->name;
+}
+
 Name WasmBinaryBuilder::getGlobalName(Index index) {
   if (index >= wasm.globals.size()) {
     throwError("invalid global index");
@@ -2078,6 +2304,13 @@ Name WasmBinaryBuilder::getTagName(Index index) {
   return wasm.tags[index]->name;
 }
 
+Memory* WasmBinaryBuilder::getMemory(Index index) {
+  if (index < wasm.memories.size()) {
+    return wasm.memories[index].get();
+  }
+  throwError("Memory index out of range.");
+}
+
 void WasmBinaryBuilder::getResizableLimits(Address& initial,
                                            Address& max,
                                            bool& shared,
@@ -2125,14 +2358,13 @@ void WasmBinaryBuilder::readImports() {
         functionTypes.push_back(getTypeByIndex(index));
         auto type = getTypeByIndex(index);
         if (!type.isSignature()) {
-          throwError(std::string("Imported function ") + module.str + '.' +
-                     base.str +
+          throwError(std::string("Imported function ") + module.toString() +
+                     '.' + base.toString() +
                      "'s type must be a signature. Given: " + type.toString());
         }
         auto curr = builder.makeFunction(name, type, {});
         curr->module = module;
         curr->base = base;
-        functionImports.push_back(curr.get());
         wasm.addFunction(std::move(curr));
         break;
       }
@@ -2157,21 +2389,20 @@ void WasmBinaryBuilder::readImports() {
           throwError("Tables may not be 64-bit");
         }
 
-        tableImports.push_back(table.get());
         wasm.addTable(std::move(table));
         break;
       }
       case ExternalKind::Memory: {
         Name name(std::string("mimport$") + std::to_string(memoryCounter++));
-        wasm.memory.module = module;
-        wasm.memory.base = base;
-        wasm.memory.name = name;
-        wasm.memory.exists = true;
-        getResizableLimits(wasm.memory.initial,
-                           wasm.memory.max,
-                           wasm.memory.shared,
-                           wasm.memory.indexType,
+        auto memory = builder.makeMemory(name);
+        memory->module = module;
+        memory->base = base;
+        getResizableLimits(memory->initial,
+                           memory->max,
+                           memory->shared,
+                           memory->indexType,
                            Memory::kUnlimitedSize);
+        wasm.addMemory(std::move(memory));
         break;
       }
       case ExternalKind::Global: {
@@ -2185,7 +2416,6 @@ void WasmBinaryBuilder::readImports() {
                              mutable_ ? Builder::Mutable : Builder::Immutable);
         curr->module = module;
         curr->base = base;
-        globalImports.push_back(curr.get());
         wasm.addGlobal(std::move(curr));
         break;
       }
@@ -2263,8 +2493,9 @@ Signature WasmBinaryBuilder::getSignatureByFunctionIndex(Index index) {
 
 void WasmBinaryBuilder::readFunctions() {
   BYN_TRACE("== readFunctions\n");
+  auto numImports = wasm.functions.size();
   size_t total = getU32LEB();
-  if (total != functionTypes.size() - functionImports.size()) {
+  if (total != functionTypes.size() - numImports) {
     throwError("invalid function section size, must equal types");
   }
   for (size_t i = 0; i < total; i++) {
@@ -2278,7 +2509,7 @@ void WasmBinaryBuilder::readFunctions() {
 
     auto* func = new Function;
     func->name = Name::fromInt(i);
-    func->type = getTypeByFunctionIndex(functionImports.size() + i);
+    func->type = getTypeByFunctionIndex(numImports + i);
     currFunction = func;
 
     if (DWARF) {
@@ -2307,14 +2538,13 @@ void WasmBinaryBuilder::readFunctions() {
       assert(exceptionTargetNames.empty());
       assert(expressionStack.empty());
       assert(controlFlowStack.empty());
-      assert(letStack.empty());
       assert(depth == 0);
       // Even if we are skipping function bodies we need to not skip the start
       // function. That contains important code for wasm-emscripten-finalize in
       // the form of pthread-related segment initializations. As this is just
       // one function, it doesn't add significant time, so the optimization of
       // skipping bodies is still very useful.
-      auto currFunctionIndex = functionImports.size() + functions.size();
+      auto currFunctionIndex = wasm.functions.size();
       bool isStart = startIndex == currFunctionIndex;
       if (!skipFunctionBodies || isStart) {
         func->body = getBlockOrSingleton(func->getResults());
@@ -2335,7 +2565,6 @@ void WasmBinaryBuilder::readFunctions() {
         throwError("stack not empty on function exit");
       }
       assert(controlFlowStack.empty());
-      assert(letStack.empty());
       if (pos != endOfFunction) {
         throwError("binary offset at function exit not at expected location");
       }
@@ -2348,7 +2577,7 @@ void WasmBinaryBuilder::readFunctions() {
     std::swap(func->epilogLocation, debugLocation);
     currFunction = nullptr;
     debugLocation.clear();
-    functions.push_back(func);
+    wasm.addFunction(func);
   }
   BYN_TRACE(" end function bodies\n");
 }
@@ -2571,6 +2800,18 @@ Expression* WasmBinaryBuilder::readExpression() {
   return ret;
 }
 
+void WasmBinaryBuilder::readStrings() {
+  auto reserved = getU32LEB();
+  if (reserved != 0) {
+    throwError("unexpected reserved value in strings");
+  }
+  size_t num = getU32LEB();
+  for (size_t i = 0; i < num; i++) {
+    auto string = getInlineString();
+    strings.push_back(string);
+  }
+}
+
 void WasmBinaryBuilder::readGlobals() {
   BYN_TRACE("== readGlobals\n");
   size_t num = getU32LEB();
@@ -2583,7 +2824,7 @@ void WasmBinaryBuilder::readGlobals() {
       throwError("Global mutability must be 0 or 1");
     }
     auto* init = readExpression();
-    globals.push_back(
+    wasm.addGlobal(
       Builder::makeGlobal("global$" + std::to_string(i),
                           type,
                           init,
@@ -2663,7 +2904,14 @@ void WasmBinaryBuilder::skipUnreachableCode() {
       expressionStack = savedStack;
       return;
     }
-    pushExpression(curr);
+    if (curr->type == Type::unreachable) {
+      // Nothing before this unreachable should be available to future
+      // expressions. They will get `(unreachable)`s if they try to pop past
+      // this point.
+      expressionStack.clear();
+    } else {
+      pushExpression(curr);
+    }
   }
 }
 
@@ -2788,25 +3036,12 @@ Expression* WasmBinaryBuilder::popTypedExpression(Type type) {
 }
 
 void WasmBinaryBuilder::validateBinary() {
-  if (hasDataCount && wasm.memory.segments.size() != dataCount) {
+  if (hasDataCount && wasm.dataSegments.size() != dataCount) {
     throwError("Number of segments does not agree with DataCount section");
   }
 }
 
 void WasmBinaryBuilder::processNames() {
-  for (auto* func : functions) {
-    wasm.addFunction(func);
-  }
-  for (auto& global : globals) {
-    wasm.addGlobal(std::move(global));
-  }
-  for (auto& table : tables) {
-    wasm.addTable(std::move(table));
-  }
-  for (auto& segment : elementSegments) {
-    wasm.addElementSegment(std::move(segment));
-  }
-
   // now that we have names, apply things
 
   if (startIndex != static_cast<Index>(-1)) {
@@ -2824,7 +3059,7 @@ void WasmBinaryBuilder::processNames() {
         curr->value = getTableName(index);
         break;
       case ExternalKind::Memory:
-        curr->value = wasm.memory.name;
+        curr->value = getMemoryName(index);
         break;
       case ExternalKind::Global:
         curr->value = getGlobalName(index);
@@ -2840,43 +3075,27 @@ void WasmBinaryBuilder::processNames() {
 
   for (auto& [index, refs] : functionRefs) {
     for (auto* ref : refs) {
-      if (auto* call = ref->dynCast<Call>()) {
-        call->target = getFunctionName(index);
-      } else if (auto* refFunc = ref->dynCast<RefFunc>()) {
-        refFunc->func = getFunctionName(index);
-      } else {
-        WASM_UNREACHABLE("Invalid type in function references");
-      }
+      *ref = getFunctionName(index);
     }
   }
-
   for (auto& [index, refs] : tableRefs) {
     for (auto* ref : refs) {
-      if (auto* callIndirect = ref->dynCast<CallIndirect>()) {
-        callIndirect->table = getTableName(index);
-      } else if (auto* get = ref->dynCast<TableGet>()) {
-        get->table = getTableName(index);
-      } else if (auto* set = ref->dynCast<TableSet>()) {
-        set->table = getTableName(index);
-      } else if (auto* size = ref->dynCast<TableSize>()) {
-        size->table = getTableName(index);
-      } else if (auto* grow = ref->dynCast<TableGrow>()) {
-        grow->table = getTableName(index);
-      } else {
-        WASM_UNREACHABLE("Invalid type in table references");
-      }
+      *ref = getTableName(index);
+    }
+  }
+  for (auto& [index, refs] : memoryRefs) {
+    for (auto ref : refs) {
+      *ref = getMemoryName(index);
     }
   }
-
   for (auto& [index, refs] : globalRefs) {
     for (auto* ref : refs) {
-      if (auto* get = ref->dynCast<GlobalGet>()) {
-        get->name = getGlobalName(index);
-      } else if (auto* set = ref->dynCast<GlobalSet>()) {
-        set->name = getGlobalName(index);
-      } else {
-        WASM_UNREACHABLE("Invalid type in global references");
-      }
+      *ref = getGlobalName(index);
+    }
+  }
+  for (auto& [index, refs] : tagRefs) {
+    for (auto* ref : refs) {
+      *ref = getTagName(index);
     }
   }
 
@@ -2885,8 +3104,8 @@ void WasmBinaryBuilder::processNames() {
   wasm.updateMaps();
 }
 
-void WasmBinaryBuilder::readDataCount() {
-  BYN_TRACE("== readDataCount\n");
+void WasmBinaryBuilder::readDataSegmentCount() {
+  BYN_TRACE("== readDataSegmentCount\n");
   hasDataCount = true;
   dataCount = getU32LEB();
 }
@@ -2895,26 +3114,29 @@ void WasmBinaryBuilder::readDataSegments() {
   BYN_TRACE("== readDataSegments\n");
   auto num = getU32LEB();
   for (size_t i = 0; i < num; i++) {
-    Memory::Segment curr;
+    auto curr = Builder::makeDataSegment();
     uint32_t flags = getU32LEB();
     if (flags > 2) {
       throwError("bad segment flags, must be 0, 1, or 2, not " +
                  std::to_string(flags));
     }
-    curr.isPassive = flags & BinaryConsts::IsPassive;
-    if (flags & BinaryConsts::HasIndex) {
-      auto memIndex = getU32LEB();
-      if (memIndex != 0) {
-        throwError("nonzero memory index");
+    curr->setName(Name::fromInt(i), false);
+    curr->isPassive = flags & BinaryConsts::IsPassive;
+    if (curr->isPassive) {
+      curr->memory = Name();
+      curr->offset = nullptr;
+    } else {
+      Index memIdx = 0;
+      if (flags & BinaryConsts::HasIndex) {
+        memIdx = getU32LEB();
       }
-    }
-    if (!curr.isPassive) {
-      curr.offset = readExpression();
+      memoryRefs[memIdx].push_back(&curr->memory);
+      curr->offset = readExpression();
     }
     auto size = getU32LEB();
     auto data = getByteView(size);
-    curr.data = {data.first, data.second};
-    wasm.memory.segments.push_back(std::move(curr));
+    curr->data = {data.begin(), data.end()};
+    wasm.addDataSegment(std::move(curr));
   }
 }
 
@@ -2939,7 +3161,7 @@ void WasmBinaryBuilder::readTableDeclarations() {
       throwError("Tables may not be 64-bit");
     }
 
-    tables.push_back(std::move(table));
+    wasm.addTable(std::move(table));
   }
 }
 
@@ -2978,17 +3200,10 @@ void WasmBinaryBuilder::readElementSegments() {
         tableIdx = getU32LEB();
       }
 
-      Table* table = nullptr;
-      auto numTableImports = tableImports.size();
-      if (tableIdx < numTableImports) {
-        table = tableImports[tableIdx];
-      } else if (tableIdx - numTableImports < tables.size()) {
-        table = tables[tableIdx - numTableImports].get();
-      }
-      if (!table) {
+      if (tableIdx >= wasm.tables.size()) {
         throwError("Table index out of range.");
       }
-
+      auto* table = wasm.tables[tableIdx].get();
       segment->table = table->name;
       segment->offset = readExpression();
     }
@@ -3019,12 +3234,12 @@ void WasmBinaryBuilder::readElementSegments() {
         auto sig = getTypeByFunctionIndex(index);
         // Use a placeholder name for now
         auto* refFunc = Builder(wasm).makeRefFunc(Name::fromInt(index), sig);
-        functionRefs[index].push_back(refFunc);
+        functionRefs[index].push_back(&refFunc->func);
         segmentData.push_back(refFunc);
       }
     }
 
-    elementSegments.push_back(std::move(segment));
+    wasm.addElementSegment(std::move(segment));
   }
 }
 
@@ -3056,24 +3271,25 @@ static char formatNibble(int nibble) {
 
 Name WasmBinaryBuilder::escape(Name name) {
   bool allIdChars = true;
-  for (const char* p = name.str; allIdChars && *p; p++) {
-    allIdChars = isIdChar(*p);
+  for (char c : name.str) {
+    if (!(allIdChars = isIdChar(c))) {
+      break;
+    }
   }
   if (allIdChars) {
     return name;
   }
   // encode name, if at least one non-idchar (per WebAssembly spec) was found
   std::string escaped;
-  for (const char* p = name.str; *p; p++) {
-    char ch = *p;
-    if (isIdChar(ch)) {
-      escaped.push_back(ch);
+  for (char c : name.str) {
+    if (isIdChar(c)) {
+      escaped.push_back(c);
       continue;
     }
     // replace non-idchar with `\xx` escape
     escaped.push_back('\\');
-    escaped.push_back(formatNibble(ch >> 4));
-    escaped.push_back(formatNibble(ch & 15));
+    escaped.push_back(formatNibble(c >> 4));
+    escaped.push_back(formatNibble(c & 15));
   }
   return escaped;
 }
@@ -3122,11 +3338,8 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
         auto index = getU32LEB();
         auto rawName = getInlineString();
         auto name = processor.process(rawName);
-        auto numFunctionImports = functionImports.size();
-        if (index < numFunctionImports) {
-          functionImports[index]->setExplicitName(name);
-        } else if (index - numFunctionImports < functions.size()) {
-          functions[index - numFunctionImports]->setExplicitName(name);
+        if (index < wasm.functions.size()) {
+          wasm.functions[index]->setExplicitName(name);
         } else {
           std::cerr << "warning: function index out of bounds in name section, "
                        "function subsection: "
@@ -3136,14 +3349,11 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
       }
     } else if (nameType == BinaryConsts::UserSections::Subsection::NameLocal) {
       auto numFuncs = getU32LEB();
-      auto numFunctionImports = functionImports.size();
       for (size_t i = 0; i < numFuncs; i++) {
         auto funcIndex = getU32LEB();
         Function* func = nullptr;
-        if (funcIndex < numFunctionImports) {
-          func = functionImports[funcIndex];
-        } else if (funcIndex - numFunctionImports < functions.size()) {
-          func = functions[funcIndex - numFunctionImports];
+        if (funcIndex < wasm.functions.size()) {
+          func = wasm.functions[funcIndex].get();
         } else {
           std::cerr
             << "warning: function index out of bounds in name section, local "
@@ -3197,20 +3407,15 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
         auto index = getU32LEB();
         auto rawName = getInlineString();
         auto name = processor.process(rawName);
-        auto numTableImports = tableImports.size();
-        auto setTableName = [&](Table* table) {
-          for (auto& segment : elementSegments) {
+
+        if (index < wasm.tables.size()) {
+          auto* table = wasm.tables[index].get();
+          for (auto& segment : wasm.elementSegments) {
             if (segment->table == table->name) {
               segment->table = name;
             }
           }
           table->setExplicitName(name);
-        };
-
-        if (index < numTableImports) {
-          setTableName(tableImports[index]);
-        } else if (index - numTableImports < tables.size()) {
-          setTableName(tables[index - numTableImports].get());
         } else {
           std::cerr << "warning: table index out of bounds in name section, "
                        "table subsection: "
@@ -3226,8 +3431,8 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
         auto rawName = getInlineString();
         auto name = processor.process(rawName);
 
-        if (index < elementSegments.size()) {
-          elementSegments[index]->setExplicitName(name);
+        if (index < wasm.elementSegments.size()) {
+          wasm.elementSegments[index]->setExplicitName(name);
         } else {
           std::cerr << "warning: elem index out of bounds in name section, "
                        "elem subsection: "
@@ -3237,11 +3442,13 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
       }
     } else if (nameType == BinaryConsts::UserSections::Subsection::NameMemory) {
       auto num = getU32LEB();
+      NameProcessor processor;
       for (size_t i = 0; i < num; i++) {
         auto index = getU32LEB();
         auto rawName = getInlineString();
-        if (index == 0) {
-          wasm.memory.setExplicitName(escape(rawName));
+        auto name = processor.process(rawName);
+        if (index < wasm.memories.size()) {
+          wasm.memories[index]->setExplicitName(name);
         } else {
           std::cerr << "warning: memory index out of bounds in name section, "
                        "memory subsection: "
@@ -3251,14 +3458,16 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
       }
     } else if (nameType == BinaryConsts::UserSections::Subsection::NameData) {
       auto num = getU32LEB();
+      NameProcessor processor;
       for (size_t i = 0; i < num; i++) {
         auto index = getU32LEB();
         auto rawName = getInlineString();
-        if (index < wasm.memory.segments.size()) {
-          wasm.memory.segments[i].name = rawName;
+        auto name = processor.process(rawName);
+        if (index < wasm.dataSegments.size()) {
+          wasm.dataSegments[i]->setExplicitName(name);
         } else {
-          std::cerr << "warning: memory index out of bounds in name section, "
-                       "memory subsection: "
+          std::cerr << "warning: data index out of bounds in name section, "
+                       "data subsection: "
                     << std::string(rawName.str) << " at index "
                     << std::to_string(index) << std::endl;
         }
@@ -3270,11 +3479,8 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
         auto index = getU32LEB();
         auto rawName = getInlineString();
         auto name = processor.process(rawName);
-        auto numGlobalImports = globalImports.size();
-        if (index < numGlobalImports) {
-          globalImports[index]->setExplicitName(name);
-        } else if (index - numGlobalImports < globals.size()) {
-          globals[index - numGlobalImports]->setExplicitName(name);
+        if (index < wasm.globals.size()) {
+          wasm.globals[index]->setExplicitName(name);
         } else {
           std::cerr << "warning: global index out of bounds in name section, "
                        "global subsection: "
@@ -3302,6 +3508,22 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
           }
         }
       }
+    } else if (nameType == BinaryConsts::UserSections::Subsection::NameTag) {
+      auto num = getU32LEB();
+      NameProcessor processor;
+      for (size_t i = 0; i < num; i++) {
+        auto index = getU32LEB();
+        auto rawName = getInlineString();
+        auto name = processor.process(rawName);
+        if (index < wasm.tags.size()) {
+          wasm.tags[index]->setExplicitName(name);
+        } else {
+          std::cerr << "warning: tag index out of bounds in name section, "
+                       "tag subsection: "
+                    << std::string(rawName.str) << " at index "
+                    << std::to_string(index) << std::endl;
+        }
+      }
     } else {
       std::cerr << "warning: unknown name subsection with id "
                 << std::to_string(nameType) << " at " << pos << std::endl;
@@ -3365,13 +3587,14 @@ void WasmBinaryBuilder::readFeatures(size_t payloadLen) {
       feature = FeatureSet::GC;
     } else if (name == BinaryConsts::UserSections::Memory64Feature) {
       feature = FeatureSet::Memory64;
-    } else if (name ==
-               BinaryConsts::UserSections::TypedFunctionReferencesFeature) {
-      feature = FeatureSet::TypedFunctionReferences;
     } else if (name == BinaryConsts::UserSections::RelaxedSIMDFeature) {
       feature = FeatureSet::RelaxedSIMD;
     } else if (name == BinaryConsts::UserSections::ExtendedConstFeature) {
       feature = FeatureSet::ExtendedConst;
+    } else if (name == BinaryConsts::UserSections::StringsFeature) {
+      feature = FeatureSet::Strings;
+    } else if (name == BinaryConsts::UserSections::MultiMemoriesFeature) {
+      feature = FeatureSet::MultiMemories;
     } else {
       // Silently ignore unknown features (this may be and old binaryen running
       // on a new wasm).
@@ -3445,7 +3668,7 @@ void WasmBinaryBuilder::readDylink0(size_t payloadLen) {
       pos = oldPos;
       size_t remaining = (sectionPos + payloadLen) - pos;
       auto tail = getByteView(remaining);
-      wasm.dylinkSection->tail = {tail.first, tail.second};
+      wasm.dylinkSection->tail = {tail.begin(), tail.end()};
       break;
     }
     if (pos != subsectionPos + subsectionSize) {
@@ -3613,36 +3836,24 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
       break;
     case BinaryConsts::MemorySize: {
       auto size = allocator.alloc<MemorySize>();
-      if (wasm.memory.is64()) {
-        size->make64();
-      }
       curr = size;
       visitMemorySize(size);
       break;
     }
     case BinaryConsts::MemoryGrow: {
       auto grow = allocator.alloc<MemoryGrow>();
-      if (wasm.memory.is64()) {
-        grow->make64();
-      }
       curr = grow;
       visitMemoryGrow(grow);
       break;
     }
     case BinaryConsts::CallRef:
-      visitCallRef((curr = allocator.alloc<CallRef>())->cast<CallRef>());
-      break;
     case BinaryConsts::RetCallRef: {
       auto call = allocator.alloc<CallRef>();
-      call->isReturn = true;
+      call->isReturn = code == BinaryConsts::RetCallRef;
       curr = call;
       visitCallRef(call);
       break;
     }
-    case BinaryConsts::Let: {
-      visitLet((curr = allocator.alloc<Block>())->cast<Block>());
-      break;
-    }
     case BinaryConsts::AtomicPrefix: {
       code = static_cast<uint8_t>(getU32LEB());
       if (maybeVisitLoad(curr, code, /*isAtomic=*/true)) {
@@ -3750,12 +3961,6 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
       if (maybeVisitBrOn(curr, opcode)) {
         break;
       }
-      if (maybeVisitRttCanon(curr, opcode)) {
-        break;
-      }
-      if (maybeVisitRttSub(curr, opcode)) {
-        break;
-      }
       if (maybeVisitStructNew(curr, opcode)) {
         break;
       }
@@ -3768,6 +3973,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
       if (maybeVisitArrayNew(curr, opcode)) {
         break;
       }
+      if (maybeVisitArrayNewSeg(curr, opcode)) {
+        break;
+      }
       if (maybeVisitArrayInit(curr, opcode)) {
         break;
       }
@@ -3783,6 +3991,45 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
       if (maybeVisitArrayCopy(curr, opcode)) {
         break;
       }
+      if (maybeVisitStringNew(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringConst(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringMeasure(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringEncode(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringConcat(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringEq(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringAs(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringWTF8Advance(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringWTF16Get(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringIterNext(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringIterMove(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringSliceWTF(curr, opcode)) {
+        break;
+      }
+      if (maybeVisitStringSliceIter(curr, opcode)) {
+        break;
+      }
       if (opcode == BinaryConsts::RefIsFunc ||
           opcode == BinaryConsts::RefIsData ||
           opcode == BinaryConsts::RefIsI31) {
@@ -3791,7 +4038,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
       }
       if (opcode == BinaryConsts::RefAsFunc ||
           opcode == BinaryConsts::RefAsData ||
-          opcode == BinaryConsts::RefAsI31) {
+          opcode == BinaryConsts::RefAsI31 ||
+          opcode == BinaryConsts::ExternInternalize ||
+          opcode == BinaryConsts::ExternExternalize) {
         visitRefAs((curr = allocator.alloc<RefAs>())->cast<RefAs>(), opcode);
         break;
       }
@@ -3834,36 +4083,6 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
   return BinaryConsts::ASTNodes(code);
 }
 
-Index WasmBinaryBuilder::getAbsoluteLocalIndex(Index index) {
-  // Wasm binaries put each let at the bottom of the index space, which may be
-  // good for binary size as often the uses of the let variables are close to
-  // the let itself. However, in Binaryen IR we just have a simple flat index
-  // space of absolute values, which we add to as we parse, and we depend on
-  // later optimizations to reorder locals for size.
-  //
-  // For example, if we have $x, then we add a let with $y, the binary would map
-  // 0 => y, 1 => x, while in Binaryen IR $x always stays at 0, and $y is added
-  // at 1.
-  //
-  // Compute the relative index in the let we were added. We start by looking at
-  // the last let added, and if we belong to it, we are already relative to it.
-  // We will continue relativizing as we go down, til we find our let.
-  int64_t relative = index;
-  for (auto i = int64_t(letStack.size()) - 1; i >= 0; i--) {
-    auto& info = letStack[i];
-    int64_t currNum = info.num;
-    // There were |currNum| let items added in this let. Check if we were one of
-    // them.
-    if (relative < currNum) {
-      return info.absoluteStart + relative;
-    }
-    relative -= currNum;
-  }
-  // We were not a let, but a normal var from the beginning. In that case, after
-  // we subtracted the let items, we have the proper absolute index.
-  return relative;
-}
-
 void WasmBinaryBuilder::startControlFlow(Expression* curr) {
   if (DWARF && currFunction) {
     controlFlowStack.push_back(curr);
@@ -4119,7 +4338,8 @@ void WasmBinaryBuilder::visitCall(Call* curr) {
     curr->operands[num - i - 1] = popNonVoidExpression();
   }
   curr->type = sig.results;
-  functionRefs[index].push_back(curr); // we don't know function names yet
+  // We don't know function names yet.
+  functionRefs[index].push_back(&curr->target);
   curr->finalize();
 }
 
@@ -4136,14 +4356,14 @@ void WasmBinaryBuilder::visitCallIndirect(CallIndirect* curr) {
     curr->operands[num - i - 1] = popNonVoidExpression();
   }
   // Defer setting the table name for later, when we know it.
-  tableRefs[tableIdx].push_back(curr);
+  tableRefs[tableIdx].push_back(&curr->table);
   curr->finalize();
 }
 
 void WasmBinaryBuilder::visitLocalGet(LocalGet* curr) {
   BYN_TRACE("zz node: LocalGet " << pos << std::endl);
   requireFunctionContext("local.get");
-  curr->index = getAbsoluteLocalIndex(getU32LEB());
+  curr->index = getU32LEB();
   if (curr->index >= currFunction->getNumLocals()) {
     throwError("bad local.get index");
   }
@@ -4154,7 +4374,7 @@ void WasmBinaryBuilder::visitLocalGet(LocalGet* curr) {
 void WasmBinaryBuilder::visitLocalSet(LocalSet* curr, uint8_t code) {
   BYN_TRACE("zz node: Set|LocalTee\n");
   requireFunctionContext("local.set outside of function");
-  curr->index = getAbsoluteLocalIndex(getU32LEB());
+  curr->index = getU32LEB();
   if (curr->index >= currFunction->getNumLocals()) {
     throwError("bad local.set index");
   }
@@ -4170,47 +4390,54 @@ void WasmBinaryBuilder::visitLocalSet(LocalSet* curr, uint8_t code) {
 void WasmBinaryBuilder::visitGlobalGet(GlobalGet* curr) {
   BYN_TRACE("zz node: GlobalGet " << pos << std::endl);
   auto index = getU32LEB();
-  if (index < globalImports.size()) {
-    auto* import = globalImports[index];
-    curr->name = import->name;
-    curr->type = import->type;
-  } else {
-    Index adjustedIndex = index - globalImports.size();
-    if (adjustedIndex >= globals.size()) {
-      throwError("invalid global index");
-    }
-    auto& glob = globals[adjustedIndex];
-    curr->name = glob->name;
-    curr->type = glob->type;
+  if (index >= wasm.globals.size()) {
+    throwError("invalid global index");
   }
-  globalRefs[index].push_back(curr); // we don't know the final name yet
+  auto* global = wasm.globals[index].get();
+  curr->name = global->name;
+  curr->type = global->type;
+  globalRefs[index].push_back(&curr->name); // we don't know the final name yet
 }
 
 void WasmBinaryBuilder::visitGlobalSet(GlobalSet* curr) {
   BYN_TRACE("zz node: GlobalSet\n");
   auto index = getU32LEB();
-  if (index < globalImports.size()) {
-    auto* import = globalImports[index];
-    curr->name = import->name;
-  } else {
-    Index adjustedIndex = index - globalImports.size();
-    if (adjustedIndex >= globals.size()) {
-      throwError("invalid global index");
-    }
-    curr->name = globals[adjustedIndex]->name;
+  if (index >= wasm.globals.size()) {
+    throwError("invalid global index");
   }
+  curr->name = wasm.globals[index]->name;
   curr->value = popNonVoidExpression();
-  globalRefs[index].push_back(curr); // we don't know the final name yet
+  globalRefs[index].push_back(&curr->name); // we don't know the final name yet
   curr->finalize();
 }
 
-void WasmBinaryBuilder::readMemoryAccess(Address& alignment, Address& offset) {
+Index WasmBinaryBuilder::readMemoryAccess(Address& alignment, Address& offset) {
   auto rawAlignment = getU32LEB();
-  if (rawAlignment > 4) {
+  bool hasMemIdx = false;
+  Index memIdx = 0;
+  // Check bit 6 in the alignment to know whether a memory index is present per:
+  // https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md
+  if (rawAlignment & (1 << (6))) {
+    hasMemIdx = true;
+    // Clear the bit before we parse alignment
+    rawAlignment = rawAlignment & ~(1 << 6);
+  }
+
+  if (rawAlignment > 8) {
     throwError("Alignment must be of a reasonable size");
   }
+
   alignment = Bits::pow2(rawAlignment);
-  offset = getUPtrLEB();
+  if (hasMemIdx) {
+    memIdx = getU32LEB();
+  }
+  if (memIdx >= wasm.memories.size()) {
+    throwError("Memory index out of range while reading memory alignment.");
+  }
+  auto* memory = wasm.memories[memIdx].get();
+  offset = memory->indexType == Type::i32 ? getU32LEB() : getU64LEB();
+
+  return memIdx;
 }
 
 bool WasmBinaryBuilder::maybeVisitLoad(Expression*& out,
@@ -4345,7 +4572,8 @@ bool WasmBinaryBuilder::maybeVisitLoad(Expression*& out,
   }
 
   curr->isAtomic = isAtomic;
-  readMemoryAccess(curr->align, curr->offset);
+  Index memIdx = readMemoryAccess(curr->align, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   curr->ptr = popNonVoidExpression();
   curr->finalize();
   out = curr;
@@ -4450,7 +4678,8 @@ bool WasmBinaryBuilder::maybeVisitStore(Expression*& out,
 
   curr->isAtomic = isAtomic;
   BYN_TRACE("zz node: Store\n");
-  readMemoryAccess(curr->align, curr->offset);
+  Index memIdx = readMemoryAccess(curr->align, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   curr->value = popNonVoidExpression();
   curr->ptr = popNonVoidExpression();
   curr->finalize();
@@ -4510,7 +4739,8 @@ bool WasmBinaryBuilder::maybeVisitAtomicRMW(Expression*& out, uint8_t code) {
 
   BYN_TRACE("zz node: AtomicRMW\n");
   Address readAlign;
-  readMemoryAccess(readAlign, curr->offset);
+  Index memIdx = readMemoryAccess(readAlign, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   if (readAlign != curr->bytes) {
     throwError("Align of AtomicRMW must match size");
   }
@@ -4562,7 +4792,8 @@ bool WasmBinaryBuilder::maybeVisitAtomicCmpxchg(Expression*& out,
 
   BYN_TRACE("zz node: AtomicCmpxchg\n");
   Address readAlign;
-  readMemoryAccess(readAlign, curr->offset);
+  Index memIdx = readMemoryAccess(readAlign, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   if (readAlign != curr->bytes) {
     throwError("Align of AtomicCpxchg must match size");
   }
@@ -4597,7 +4828,8 @@ bool WasmBinaryBuilder::maybeVisitAtomicWait(Expression*& out, uint8_t code) {
   curr->expected = popNonVoidExpression();
   curr->ptr = popNonVoidExpression();
   Address readAlign;
-  readMemoryAccess(readAlign, curr->offset);
+  Index memIdx = readMemoryAccess(readAlign, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   if (readAlign != curr->expectedType.getByteSize()) {
     throwError("Align of AtomicWait must match size");
   }
@@ -4617,7 +4849,8 @@ bool WasmBinaryBuilder::maybeVisitAtomicNotify(Expression*& out, uint8_t code) {
   curr->notifyCount = popNonVoidExpression();
   curr->ptr = popNonVoidExpression();
   Address readAlign;
-  readMemoryAccess(readAlign, curr->offset);
+  Index memIdx = readMemoryAccess(readAlign, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   if (readAlign != curr->type.getByteSize()) {
     throwError("Align of AtomicNotify must match size");
   }
@@ -4948,10 +5181,9 @@ bool WasmBinaryBuilder::maybeVisitMemoryInit(Expression*& out, uint32_t code) {
   curr->offset = popNonVoidExpression();
   curr->dest = popNonVoidExpression();
   curr->segment = getU32LEB();
-  if (getInt8() != 0) {
-    throwError("Unexpected nonzero memory index");
-  }
+  Index memIdx = getU32LEB();
   curr->finalize();
+  memoryRefs[memIdx].push_back(&curr->memory);
   out = curr;
   return true;
 }
@@ -4975,10 +5207,11 @@ bool WasmBinaryBuilder::maybeVisitMemoryCopy(Expression*& out, uint32_t code) {
   curr->size = popNonVoidExpression();
   curr->source = popNonVoidExpression();
   curr->dest = popNonVoidExpression();
-  if (getInt8() != 0 || getInt8() != 0) {
-    throwError("Unexpected nonzero memory index");
-  }
+  Index destIdx = getU32LEB();
+  Index sourceIdx = getU32LEB();
   curr->finalize();
+  memoryRefs[destIdx].push_back(&curr->destMemory);
+  memoryRefs[sourceIdx].push_back(&curr->sourceMemory);
   out = curr;
   return true;
 }
@@ -4991,10 +5224,9 @@ bool WasmBinaryBuilder::maybeVisitMemoryFill(Expression*& out, uint32_t code) {
   curr->size = popNonVoidExpression();
   curr->value = popNonVoidExpression();
   curr->dest = popNonVoidExpression();
-  if (getInt8() != 0) {
-    throwError("Unexpected nonzero memory index");
-  }
+  Index memIdx = getU32LEB();
   curr->finalize();
+  memoryRefs[memIdx].push_back(&curr->memory);
   out = curr;
   return true;
 }
@@ -5004,13 +5236,13 @@ bool WasmBinaryBuilder::maybeVisitTableSize(Expression*& out, uint32_t code) {
     return false;
   }
   Index tableIdx = getU32LEB();
-  if (tableIdx >= tables.size()) {
+  if (tableIdx >= wasm.tables.size()) {
     throwError("bad table index");
   }
   auto* curr = allocator.alloc<TableSize>();
   curr->finalize();
   // Defer setting the table name for later, when we know it.
-  tableRefs[tableIdx].push_back(curr);
+  tableRefs[tableIdx].push_back(&curr->table);
   out = curr;
   return true;
 }
@@ -5020,7 +5252,7 @@ bool WasmBinaryBuilder::maybeVisitTableGrow(Expression*& out, uint32_t code) {
     return false;
   }
   Index tableIdx = getU32LEB();
-  if (tableIdx >= tables.size()) {
+  if (tableIdx >= wasm.tables.size()) {
     throwError("bad table index");
   }
   auto* curr = allocator.alloc<TableGrow>();
@@ -5028,7 +5260,7 @@ bool WasmBinaryBuilder::maybeVisitTableGrow(Expression*& out, uint32_t code) {
   curr->value = popNonVoidExpression();
   curr->finalize();
   // Defer setting the table name for later, when we know it.
-  tableRefs[tableIdx].push_back(curr);
+  tableRefs[tableIdx].push_back(&curr->table);
   out = curr;
   return true;
 }
@@ -5622,10 +5854,6 @@ bool WasmBinaryBuilder::maybeVisitSIMDBinary(Expression*& out, uint32_t code) {
       curr = allocator.alloc<Binary>();
       curr->op = DotI8x16I7x16SToVecI16x8;
       break;
-    case BinaryConsts::I16x8DotI8x16I7x16U:
-      curr = allocator.alloc<Binary>();
-      curr->op = DotI8x16I7x16UToVecI16x8;
-      break;
     default:
       return false;
   }
@@ -5942,7 +6170,8 @@ bool WasmBinaryBuilder::maybeVisitSIMDStore(Expression*& out, uint32_t code) {
   auto* curr = allocator.alloc<Store>();
   curr->bytes = 16;
   curr->valueType = Type::v128;
-  readMemoryAccess(curr->align, curr->offset);
+  Index memIdx = readMemoryAccess(curr->align, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   curr->isAtomic = false;
   curr->value = popNonVoidExpression();
   curr->ptr = popNonVoidExpression();
@@ -6104,10 +6333,6 @@ bool WasmBinaryBuilder::maybeVisitSIMDTernary(Expression*& out, uint32_t code) {
       curr = allocator.alloc<SIMDTernary>();
       curr->op = DotI8x16I7x16AddSToVecI32x4;
       break;
-    case BinaryConsts::I32x4DotI8x16I7x16AddU:
-      curr = allocator.alloc<SIMDTernary>();
-      curr->op = DotI8x16I7x16AddUToVecI32x4;
-      break;
     default:
       return false;
   }
@@ -6185,7 +6410,8 @@ bool WasmBinaryBuilder::maybeVisitSIMDLoad(Expression*& out, uint32_t code) {
     auto* curr = allocator.alloc<Load>();
     curr->type = Type::v128;
     curr->bytes = 16;
-    readMemoryAccess(curr->align, curr->offset);
+    Index memIdx = readMemoryAccess(curr->align, curr->offset);
+    memoryRefs[memIdx].push_back(&curr->memory);
     curr->isAtomic = false;
     curr->ptr = popNonVoidExpression();
     curr->finalize();
@@ -6245,7 +6471,8 @@ bool WasmBinaryBuilder::maybeVisitSIMDLoad(Expression*& out, uint32_t code) {
     default:
       return false;
   }
-  readMemoryAccess(curr->align, curr->offset);
+  Index memIdx = readMemoryAccess(curr->align, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   curr->ptr = popNonVoidExpression();
   curr->finalize();
   out = curr;
@@ -6294,7 +6521,8 @@ bool WasmBinaryBuilder::maybeVisitSIMDLoadStoreLane(Expression*& out,
   }
   auto* curr = allocator.alloc<SIMDLoadStoreLane>();
   curr->op = op;
-  readMemoryAccess(curr->align, curr->offset);
+  Index memIdx = readMemoryAccess(curr->align, curr->offset);
+  memoryRefs[memIdx].push_back(&curr->memory);
   curr->index = getLaneIndex(lanes);
   curr->vec = popNonVoidExpression();
   curr->ptr = popNonVoidExpression();
@@ -6335,21 +6563,22 @@ void WasmBinaryBuilder::visitReturn(Return* curr) {
 
 void WasmBinaryBuilder::visitMemorySize(MemorySize* curr) {
   BYN_TRACE("zz node: MemorySize\n");
-  auto reserved = getU32LEB();
-  if (reserved != 0) {
-    throwError("Invalid reserved field on memory.size");
+  Index index = getU32LEB();
+  if (getMemory(index)->is64()) {
+    curr->make64();
   }
   curr->finalize();
+  memoryRefs[index].push_back(&curr->memory);
 }
 
 void WasmBinaryBuilder::visitMemoryGrow(MemoryGrow* curr) {
   BYN_TRACE("zz node: MemoryGrow\n");
   curr->delta = popNonVoidExpression();
-  auto reserved = getU32LEB();
-  if (reserved != 0) {
-    throwError("Invalid reserved field on memory.grow");
+  Index index = getU32LEB();
+  if (getMemory(index)->is64()) {
+    curr->make64();
   }
-  curr->finalize();
+  memoryRefs[index].push_back(&curr->memory);
 }
 
 void WasmBinaryBuilder::visitNop(Nop* curr) { BYN_TRACE("zz node: Nop\n"); }
@@ -6366,7 +6595,7 @@ void WasmBinaryBuilder::visitDrop(Drop* curr) {
 
 void WasmBinaryBuilder::visitRefNull(RefNull* curr) {
   BYN_TRACE("zz node: RefNull\n");
-  curr->finalize(getHeapType());
+  curr->finalize(getHeapType().getBottom());
 }
 
 void WasmBinaryBuilder::visitRefIs(RefIs* curr, uint8_t code) {
@@ -6399,7 +6628,7 @@ void WasmBinaryBuilder::visitRefFunc(RefFunc* curr) {
   // be verified in the next line. (Also, note that functionRefs[index] may
   // write to an odd place in the functionRefs map if index is invalid, but that
   // is harmless.)
-  functionRefs[index].push_back(curr);
+  functionRefs[index].push_back(&curr->func);
   // To support typed function refs, we give the reference not just a general
   // funcref, but a specific subtype with the actual signature.
   curr->finalize(Type(getTypeByFunctionIndex(index), NonNullable));
@@ -6415,27 +6644,27 @@ void WasmBinaryBuilder::visitRefEq(RefEq* curr) {
 void WasmBinaryBuilder::visitTableGet(TableGet* curr) {
   BYN_TRACE("zz node: TableGet\n");
   Index tableIdx = getU32LEB();
-  if (tableIdx >= tables.size()) {
+  if (tableIdx >= wasm.tables.size()) {
     throwError("bad table index");
   }
   curr->index = popNonVoidExpression();
-  curr->type = tables[tableIdx]->type;
+  curr->type = wasm.tables[tableIdx]->type;
   curr->finalize();
   // Defer setting the table name for later, when we know it.
-  tableRefs[tableIdx].push_back(curr);
+  tableRefs[tableIdx].push_back(&curr->table);
 }
 
 void WasmBinaryBuilder::visitTableSet(TableSet* curr) {
   BYN_TRACE("zz node: TableSet\n");
   Index tableIdx = getU32LEB();
-  if (tableIdx >= tables.size()) {
+  if (tableIdx >= wasm.tables.size()) {
     throwError("bad table index");
   }
   curr->value = popNonVoidExpression();
   curr->index = popNonVoidExpression();
   curr->finalize();
   // Defer setting the table name for later, when we know it.
-  tableRefs[tableIdx].push_back(curr);
+  tableRefs[tableIdx].push_back(&curr->table);
 }
 
 void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
@@ -6473,6 +6702,11 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
     }
   };
 
+  // We cannot immediately update tagRefs in the loop below, as catchTags is
+  // being grown, an so references would get invalidated. Store the indexes
+  // here, then do that later.
+  std::vector<Index> tagIndexes;
+
   while (lastSeparator == BinaryConsts::Catch ||
          lastSeparator == BinaryConsts::CatchAll) {
     if (lastSeparator == BinaryConsts::Catch) {
@@ -6480,10 +6714,10 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
       if (index >= wasm.tags.size()) {
         throwError("bad tag index");
       }
+      tagIndexes.push_back(index);
       auto* tag = wasm.tags[index].get();
       curr->catchTags.push_back(tag->name);
       readCatchBody(tag->sig.params);
-
     } else { // catch_all
       if (curr->hasCatchAll()) {
         throwError("there should be at most one 'catch_all' clause per try");
@@ -6493,6 +6727,11 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
   }
   breakStack.pop_back();
 
+  for (Index i = 0; i < tagIndexes.size(); i++) {
+    // We don't know the final name yet.
+    tagRefs[tagIndexes[i]].push_back(&curr->catchTags[i]);
+  }
+
   if (lastSeparator == BinaryConsts::Delegate) {
     curr->delegateTarget = getExceptionTargetName(getU32LEB());
   }
@@ -6589,6 +6828,7 @@ void WasmBinaryBuilder::visitThrow(Throw* curr) {
   }
   auto* tag = wasm.tags[index].get();
   curr->tag = tag->name;
+  tagRefs[index].push_back(&curr->tag); // we don't know the final name yet
   size_t num = tag->sig.params.size();
   curr->operands.resize(num);
   for (size_t i = 0; i < num; i++) {
@@ -6603,7 +6843,7 @@ void WasmBinaryBuilder::visitRethrow(Rethrow* curr) {
   // This special target is valid only for delegates
   if (curr->target == DELEGATE_CALLER_TARGET) {
     throwError(std::string("rethrow target cannot use internal name ") +
-               DELEGATE_CALLER_TARGET.str);
+               DELEGATE_CALLER_TARGET.toString());
   }
   curr->finalize();
 }
@@ -6611,19 +6851,13 @@ void WasmBinaryBuilder::visitRethrow(Rethrow* curr) {
 void WasmBinaryBuilder::visitCallRef(CallRef* curr) {
   BYN_TRACE("zz node: CallRef\n");
   curr->target = popNonVoidExpression();
-  auto type = curr->target->type;
-  if (type == Type::unreachable) {
-    // If our input is unreachable, then we cannot even find out how many inputs
-    // we have, and just set ourselves to unreachable as well.
-    curr->finalize(type);
-    return;
+  HeapType heapType = getTypeByIndex(getU32LEB());
+  if (!Type::isSubType(curr->target->type, Type(heapType, Nullable))) {
+    throwError("Call target has invalid type: " +
+               curr->target->type.toString());
   }
-  if (!type.isRef()) {
-    throwError("Non-ref type for a call_ref: " + type.toString());
-  }
-  auto heapType = type.getHeapType();
   if (!heapType.isSignature()) {
-    throwError("Invalid reference type for a call_ref: " + type.toString());
+    throwError("Invalid reference type for a call_ref: " + heapType.toString());
   }
   auto sig = heapType.getSignature();
   auto num = sig.params.size();
@@ -6634,32 +6868,6 @@ void WasmBinaryBuilder::visitCallRef(CallRef* curr) {
   curr->finalize(sig.results);
 }
 
-void WasmBinaryBuilder::visitLet(Block* curr) {
-  // A let is lowered into a block that contains the value, and we allocate
-  // locals as needed, which works as we remove non-nullability.
-
-  startControlFlow(curr);
-  // Get the output type.
-  curr->type = getType();
-  // Get the new local types. First, get the absolute index from which we will
-  // start to allocate them.
-  requireFunctionContext("let");
-  Index absoluteStart = currFunction->vars.size();
-  readVars();
-  Index numNewVars = currFunction->vars.size() - absoluteStart;
-  // Assign the values into locals.
-  Builder builder(wasm);
-  for (Index i = 0; i < numNewVars; i++) {
-    auto* value = popNonVoidExpression();
-    curr->list.push_back(builder.makeLocalSet(absoluteStart + i, value));
-  }
-  // Read the body, with adjusted local indexes.
-  letStack.emplace_back(LetData{numNewVars, absoluteStart});
-  curr->list.push_back(getBlockOrSingleton(curr->type));
-  letStack.pop_back();
-  curr->finalize(curr->type);
-}
-
 bool WasmBinaryBuilder::maybeVisitI31New(Expression*& out, uint32_t code) {
   if (code != BinaryConsts::I31New) {
     return false;
@@ -6692,12 +6900,7 @@ bool WasmBinaryBuilder::maybeVisitI31Get(Expression*& out, uint32_t code) {
 }
 
 bool WasmBinaryBuilder::maybeVisitRefTest(Expression*& out, uint32_t code) {
-  if (code == BinaryConsts::RefTest) {
-    auto* rtt = popNonVoidExpression();
-    auto* ref = popNonVoidExpression();
-    out = Builder(wasm).makeRefTest(ref, rtt);
-    return true;
-  } else if (code == BinaryConsts::RefTestStatic) {
+  if (code == BinaryConsts::RefTestStatic) {
     auto intendedType = getIndexedHeapType();
     auto* ref = popNonVoidExpression();
     out = Builder(wasm).makeRefTest(ref, intendedType);
@@ -6707,13 +6910,8 @@ bool WasmBinaryBuilder::maybeVisitRefTest(Expression*& out, uint32_t code) {
 }
 
 bool WasmBinaryBuilder::maybeVisitRefCast(Expression*& out, uint32_t code) {
-  if (code == BinaryConsts::RefCast) {
-    auto* rtt = popNonVoidExpression();
-    auto* ref = popNonVoidExpression();
-    out = Builder(wasm).makeRefCast(ref, rtt);
-    return true;
-  } else if (code == BinaryConsts::RefCastStatic ||
-             code == BinaryConsts::RefCastNopStatic) {
+  if (code == BinaryConsts::RefCastStatic ||
+      code == BinaryConsts::RefCastNopStatic) {
     auto intendedType = getIndexedHeapType();
     auto* ref = popNonVoidExpression();
     auto safety =
@@ -6733,11 +6931,9 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) {
     case BinaryConsts::BrOnNonNull:
       op = BrOnNonNull;
       break;
-    case BinaryConsts::BrOnCast:
     case BinaryConsts::BrOnCastStatic:
       op = BrOnCast;
       break;
-    case BinaryConsts::BrOnCastFail:
     case BinaryConsts::BrOnCastStaticFail:
       op = BrOnCastFail;
       break;
@@ -6770,35 +6966,8 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) {
     out = Builder(wasm).makeBrOn(op, name, ref, intendedType);
     return true;
   }
-  Expression* rtt = nullptr;
-  if (op == BrOnCast || op == BrOnCastFail) {
-    rtt = popNonVoidExpression();
-  }
   auto* ref = popNonVoidExpression();
-  out = ValidatingBuilder(wasm, pos).validateAndMakeBrOn(op, name, ref, rtt);
-  return true;
-}
-
-bool WasmBinaryBuilder::maybeVisitRttCanon(Expression*& out, uint32_t code) {
-  if (code != BinaryConsts::RttCanon) {
-    return false;
-  }
-  auto heapType = getIndexedHeapType();
-  out = Builder(wasm).makeRttCanon(heapType);
-  return true;
-}
-
-bool WasmBinaryBuilder::maybeVisitRttSub(Expression*& out, uint32_t code) {
-  if (code != BinaryConsts::RttSub && code != BinaryConsts::RttFreshSub) {
-    return false;
-  }
-  auto targetHeapType = getIndexedHeapType();
-  auto* parent = popNonVoidExpression();
-  if (code == BinaryConsts::RttSub) {
-    out = Builder(wasm).makeRttSub(targetHeapType, parent);
-  } else {
-    out = Builder(wasm).makeRttFreshSub(targetHeapType, parent);
-  }
+  out = ValidatingBuilder(wasm, pos).validateAndMakeBrOn(op, name, ref);
   return true;
 }
 
@@ -6816,48 +6985,34 @@ bool WasmBinaryBuilder::maybeVisitStructNew(Expression*& out, uint32_t code) {
     }
     out = Builder(wasm).makeStructNew(heapType, operands);
     return true;
-  } else if (code == BinaryConsts::StructNewWithRtt ||
-             code == BinaryConsts::StructNewDefaultWithRtt) {
-    auto heapType = getIndexedHeapType();
-    auto* rtt = popNonVoidExpression();
-    validateHeapTypeUsingChild(rtt, heapType);
-    std::vector<Expression*> operands;
-    if (code == BinaryConsts::StructNewWithRtt) {
-      auto numOperands = heapType.getStruct().fields.size();
-      operands.resize(numOperands);
-      for (Index i = 0; i < numOperands; i++) {
-        operands[numOperands - i - 1] = popNonVoidExpression();
-      }
-    }
-    out = Builder(wasm).makeStructNew(rtt, operands);
-    return true;
   }
   return false;
 }
 
 bool WasmBinaryBuilder::maybeVisitStructGet(Expression*& out, uint32_t code) {
-  StructGet* curr;
+  bool signed_ = false;
   switch (code) {
     case BinaryConsts::StructGet:
-      curr = allocator.alloc<StructGet>();
+    case BinaryConsts::StructGetU:
       break;
     case BinaryConsts::StructGetS:
-      curr = allocator.alloc<StructGet>();
-      curr->signed_ = true;
-      break;
-    case BinaryConsts::StructGetU:
-      curr = allocator.alloc<StructGet>();
-      curr->signed_ = false;
+      signed_ = true;
       break;
     default:
       return false;
   }
   auto heapType = getIndexedHeapType();
-  curr->index = getU32LEB();
-  curr->ref = popNonVoidExpression();
-  validateHeapTypeUsingChild(curr->ref, heapType);
-  curr->finalize();
-  out = curr;
+  if (!heapType.isStruct()) {
+    throwError("Expected struct heaptype");
+  }
+  auto index = getU32LEB();
+  if (index >= heapType.getStruct().fields.size()) {
+    throwError("Struct field index out of bounds");
+  }
+  auto type = heapType.getStruct().fields[index].type;
+  auto ref = popNonVoidExpression();
+  validateHeapTypeUsingChild(ref, heapType);
+  out = Builder(wasm).makeStructGet(index, ref, type, signed_);
   return true;
 }
 
@@ -6886,17 +7041,19 @@ bool WasmBinaryBuilder::maybeVisitArrayNew(Expression*& out, uint32_t code) {
     }
     out = Builder(wasm).makeArrayNew(heapType, size, init);
     return true;
-  } else if (code == BinaryConsts::ArrayNewWithRtt ||
-             code == BinaryConsts::ArrayNewDefaultWithRtt) {
+  }
+  return false;
+}
+
+bool WasmBinaryBuilder::maybeVisitArrayNewSeg(Expression*& out, uint32_t code) {
+  if (code == BinaryConsts::ArrayNewData ||
+      code == BinaryConsts::ArrayNewElem) {
+    auto op = code == BinaryConsts::ArrayNewData ? NewData : NewElem;
     auto heapType = getIndexedHeapType();
-    auto* rtt = popNonVoidExpression();
-    validateHeapTypeUsingChild(rtt, heapType);
+    auto seg = getU32LEB();
     auto* size = popNonVoidExpression();
-    Expression* init = nullptr;
-    if (code == BinaryConsts::ArrayNewWithRtt) {
-      init = popNonVoidExpression();
-    }
-    out = Builder(wasm).makeArrayNew(rtt, size, init);
+    auto* offset = popNonVoidExpression();
+    out = Builder(wasm).makeArrayNewSeg(op, heapType, seg, offset, size);
     return true;
   }
   return false;
@@ -6912,17 +7069,6 @@ bool WasmBinaryBuilder::maybeVisitArrayInit(Expression*& out, uint32_t code) {
     }
     out = Builder(wasm).makeArrayInit(heapType, values);
     return true;
-  } else if (code == BinaryConsts::ArrayInit) {
-    auto heapType = getIndexedHeapType();
-    auto size = getU32LEB();
-    auto* rtt = popNonVoidExpression();
-    validateHeapTypeUsingChild(rtt, heapType);
-    std::vector<Expression*> values(size);
-    for (size_t i = 0; i < size; i++) {
-      values[size - i - 1] = popNonVoidExpression();
-    }
-    out = Builder(wasm).makeArrayInit(rtt, values);
-    return true;
   }
   return false;
 }
@@ -6940,10 +7086,14 @@ bool WasmBinaryBuilder::maybeVisitArrayGet(Expression*& out, uint32_t code) {
       return false;
   }
   auto heapType = getIndexedHeapType();
+  if (!heapType.isArray()) {
+    throwError("Expected array heaptype");
+  }
+  auto type = heapType.getArray().element.type;
   auto* index = popNonVoidExpression();
   auto* ref = popNonVoidExpression();
   validateHeapTypeUsingChild(ref, heapType);
-  out = Builder(wasm).makeArrayGet(ref, index, signed_);
+  out = Builder(wasm).makeArrayGet(ref, index, type, signed_);
   return true;
 }
 
@@ -6961,12 +7111,13 @@ bool WasmBinaryBuilder::maybeVisitArraySet(Expression*& out, uint32_t code) {
 }
 
 bool WasmBinaryBuilder::maybeVisitArrayLen(Expression*& out, uint32_t code) {
-  if (code != BinaryConsts::ArrayLen) {
+  if (code == BinaryConsts::ArrayLenAnnotated) {
+    // Ignore the type annotation and don't bother validating it.
+    getU32LEB();
+  } else if (code != BinaryConsts::ArrayLen) {
     return false;
   }
-  auto heapType = getIndexedHeapType();
   auto* ref = popNonVoidExpression();
-  validateHeapTypeUsingChild(ref, heapType);
   out = Builder(wasm).makeArrayLen(ref);
   return true;
 }
@@ -6989,6 +7140,274 @@ bool WasmBinaryBuilder::maybeVisitArrayCopy(Expression*& out, uint32_t code) {
   return true;
 }
 
+bool WasmBinaryBuilder::maybeVisitStringNew(Expression*& out, uint32_t code) {
+  StringNewOp op;
+  Expression* length = nullptr;
+  Expression* start = nullptr;
+  Expression* end = nullptr;
+  if (code == BinaryConsts::StringNewWTF8) {
+    if (getInt8() != 0) {
+      throwError("Unexpected nonzero memory index");
+    }
+    auto policy = getU32LEB();
+    switch (policy) {
+      case BinaryConsts::StringPolicy::UTF8:
+        op = StringNewUTF8;
+        break;
+      case BinaryConsts::StringPolicy::WTF8:
+        op = StringNewWTF8;
+        break;
+      case BinaryConsts::StringPolicy::Replace:
+        op = StringNewReplace;
+        break;
+      default:
+        throwError("bad policy for string.new");
+    }
+    length = popNonVoidExpression();
+  } else if (code == BinaryConsts::StringNewWTF16) {
+    if (getInt8() != 0) {
+      throwError("Unexpected nonzero memory index");
+    }
+    op = StringNewWTF16;
+    length = popNonVoidExpression();
+  } else if (code == BinaryConsts::StringNewWTF8Array) {
+    auto policy = getU32LEB();
+    switch (policy) {
+      case BinaryConsts::StringPolicy::UTF8:
+        op = StringNewUTF8Array;
+        break;
+      case BinaryConsts::StringPolicy::WTF8:
+        op = StringNewWTF8Array;
+        break;
+      case BinaryConsts::StringPolicy::Replace:
+        op = StringNewReplaceArray;
+        break;
+      default:
+        throwError("bad policy for string.new");
+    }
+    end = popNonVoidExpression();
+    start = popNonVoidExpression();
+  } else if (code == BinaryConsts::StringNewWTF16Array) {
+    op = StringNewWTF16Array;
+    end = popNonVoidExpression();
+    start = popNonVoidExpression();
+  } else {
+    return false;
+  }
+  auto* ptr = popNonVoidExpression();
+  if (length) {
+    out = Builder(wasm).makeStringNew(op, ptr, length);
+  } else {
+    out = Builder(wasm).makeStringNew(op, ptr, start, end);
+  }
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringConst(Expression*& out, uint32_t code) {
+  if (code != BinaryConsts::StringConst) {
+    return false;
+  }
+  auto index = getU32LEB();
+  if (index >= strings.size()) {
+    throwError("bad string index");
+  }
+  out = Builder(wasm).makeStringConst(strings[index]);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringMeasure(Expression*& out,
+                                                uint32_t code) {
+  StringMeasureOp op;
+  if (code == BinaryConsts::StringMeasureWTF8) {
+    auto policy = getU32LEB();
+    switch (policy) {
+      case BinaryConsts::StringPolicy::UTF8:
+        op = StringMeasureUTF8;
+        break;
+      case BinaryConsts::StringPolicy::WTF8:
+        op = StringMeasureWTF8;
+        break;
+      default:
+        throwError("bad policy for string.measure");
+    }
+  } else if (code == BinaryConsts::StringMeasureWTF16) {
+    op = StringMeasureWTF16;
+  } else if (code == BinaryConsts::StringIsUSV) {
+    op = StringMeasureIsUSV;
+  } else if (code == BinaryConsts::StringViewWTF16Length) {
+    op = StringMeasureWTF16View;
+  } else {
+    return false;
+  }
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringMeasure(op, ref);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringEncode(Expression*& out,
+                                               uint32_t code) {
+  StringEncodeOp op;
+  Expression* start = nullptr;
+  // TODO: share this code with string.measure?
+  if (code == BinaryConsts::StringEncodeWTF8) {
+    if (getInt8() != 0) {
+      throwError("Unexpected nonzero memory index");
+    }
+    auto policy = getU32LEB();
+    switch (policy) {
+      case BinaryConsts::StringPolicy::UTF8:
+        op = StringEncodeUTF8;
+        break;
+      case BinaryConsts::StringPolicy::WTF8:
+        op = StringEncodeWTF8;
+        break;
+      default:
+        throwError("bad policy for string.encode");
+    }
+  } else if (code == BinaryConsts::StringEncodeWTF16) {
+    if (getInt8() != 0) {
+      throwError("Unexpected nonzero memory index");
+    }
+    op = StringEncodeWTF16;
+  } else if (code == BinaryConsts::StringEncodeWTF8Array) {
+    auto policy = getU32LEB();
+    switch (policy) {
+      case BinaryConsts::StringPolicy::UTF8:
+        op = StringEncodeUTF8Array;
+        break;
+      case BinaryConsts::StringPolicy::WTF8:
+        op = StringEncodeWTF8Array;
+        break;
+      default:
+        throwError("bad policy for string.encode");
+    }
+    start = popNonVoidExpression();
+  } else if (code == BinaryConsts::StringEncodeWTF16Array) {
+    op = StringEncodeWTF16Array;
+    start = popNonVoidExpression();
+  } else {
+    return false;
+  }
+  auto* ptr = popNonVoidExpression();
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringEncode(op, ref, ptr, start);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringConcat(Expression*& out,
+                                               uint32_t code) {
+  if (code != BinaryConsts::StringConcat) {
+    return false;
+  }
+  auto* right = popNonVoidExpression();
+  auto* left = popNonVoidExpression();
+  out = Builder(wasm).makeStringConcat(left, right);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringEq(Expression*& out, uint32_t code) {
+  if (code != BinaryConsts::StringEq) {
+    return false;
+  }
+  auto* right = popNonVoidExpression();
+  auto* left = popNonVoidExpression();
+  out = Builder(wasm).makeStringEq(left, right);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringAs(Expression*& out, uint32_t code) {
+  StringAsOp op;
+  if (code == BinaryConsts::StringAsWTF8) {
+    op = StringAsWTF8;
+  } else if (code == BinaryConsts::StringAsWTF16) {
+    op = StringAsWTF16;
+  } else if (code == BinaryConsts::StringAsIter) {
+    op = StringAsIter;
+  } else {
+    return false;
+  }
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringAs(op, ref);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringWTF8Advance(Expression*& out,
+                                                    uint32_t code) {
+  if (code != BinaryConsts::StringViewWTF8Advance) {
+    return false;
+  }
+  auto* bytes = popNonVoidExpression();
+  auto* pos = popNonVoidExpression();
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringWTF8Advance(ref, pos, bytes);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringWTF16Get(Expression*& out,
+                                                 uint32_t code) {
+  if (code != BinaryConsts::StringViewWTF16GetCodePoint) {
+    return false;
+  }
+  auto* pos = popNonVoidExpression();
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringWTF16Get(ref, pos);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringIterNext(Expression*& out,
+                                                 uint32_t code) {
+  if (code != BinaryConsts::StringViewIterNext) {
+    return false;
+  }
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringIterNext(ref);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringIterMove(Expression*& out,
+                                                 uint32_t code) {
+  StringIterMoveOp op;
+  if (code == BinaryConsts::StringViewIterAdvance) {
+    op = StringIterMoveAdvance;
+  } else if (code == BinaryConsts::StringViewIterRewind) {
+    op = StringIterMoveRewind;
+  } else {
+    return false;
+  }
+  auto* num = popNonVoidExpression();
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringIterMove(op, ref, num);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringSliceWTF(Expression*& out,
+                                                 uint32_t code) {
+  StringSliceWTFOp op;
+  if (code == BinaryConsts::StringViewWTF8Slice) {
+    op = StringSliceWTF8;
+  } else if (code == BinaryConsts::StringViewWTF16Slice) {
+    op = StringSliceWTF16;
+  } else {
+    return false;
+  }
+  auto* end = popNonVoidExpression();
+  auto* start = popNonVoidExpression();
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringSliceWTF(op, ref, start, end);
+  return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitStringSliceIter(Expression*& out,
+                                                  uint32_t code) {
+  if (code != BinaryConsts::StringViewIterSlice) {
+    return false;
+  }
+  auto* num = popNonVoidExpression();
+  auto* ref = popNonVoidExpression();
+  out = Builder(wasm).makeStringSliceIter(ref, num);
+  return true;
+}
+
 void WasmBinaryBuilder::visitRefAs(RefAs* curr, uint8_t code) {
   BYN_TRACE("zz node: RefAs\n");
   switch (code) {
@@ -7004,6 +7423,12 @@ void WasmBinaryBuilder::visitRefAs(RefAs* curr, uint8_t code) {
     case BinaryConsts::RefAsI31:
       curr->op = RefAsI31;
       break;
+    case BinaryConsts::ExternInternalize:
+      curr->op = ExternInternalize;
+      break;
+    case BinaryConsts::ExternExternalize:
+      curr->op = ExternExternalize;
+      break;
     default:
       WASM_UNREACHABLE("invalid code for ref.as_*");
   }
@@ -7023,7 +7448,7 @@ void WasmBinaryBuilder::validateHeapTypeUsingChild(Expression* child,
   if (child->type == Type::unreachable) {
     return;
   }
-  if ((!child->type.isRef() && !child->type.isRtt()) ||
+  if (!child->type.isRef() ||
       !HeapType::isSubType(child->type.getHeapType(), heapType)) {
     throwError("bad heap type: expected " + heapType.toString() +
                " but found " + child->type.toString());
diff --git a/src/wasm/wasm-debug.cpp b/src/wasm/wasm-debug.cpp
index 23c2dc9..8ca06b0 100644
--- a/src/wasm/wasm-debug.cpp
+++ b/src/wasm/wasm-debug.cpp
@@ -64,6 +64,10 @@ struct BinaryenDWARFInfo {
     uint8_t addrSize = AddressSize;
     bool isLittleEndian = true;
     context = llvm::DWARFContext::create(sections, addrSize, isLittleEndian);
+    if (context->getMaxVersion() > 4) {
+      std::cerr << "warning: unsupported DWARF version ("
+                << context->getMaxVersion() << ")\n";
+    }
   }
 };
 
@@ -162,8 +166,8 @@ struct LineState {
           }
           default: {
             // An unknown opcode, ignore.
-            std::cerr << "warning: unknown subopcopde " << opcode.SubOpcode
-                      << '\n';
+            std::cerr << "warning: unknown subopcode " << opcode.SubOpcode
+                      << " (this may be an unsupported version of DWARF)\n";
           }
         }
         break;
@@ -180,7 +184,10 @@ struct LineState {
         return true;
       }
       case llvm::dwarf::DW_LNS_advance_pc: {
-        assert(table.MinInstLength == 1);
+        if (table.MinInstLength != 1) {
+          std::cerr << "warning: bad MinInstLength "
+                       "(this may be an unsupported DWARF version)";
+        }
         addr += opcode.Data;
         break;
       }
@@ -481,7 +488,7 @@ struct LocationUpdater {
   // Map start of line tables in the debug_line section to their new locations.
   std::unordered_map<BinaryLocation, BinaryLocation> debugLineMap;
 
-  typedef std::pair<BinaryLocation, BinaryLocation> OldToNew;
+  using OldToNew = std::pair<BinaryLocation, BinaryLocation>;
 
   // Map of compile unit index => old and new base offsets (i.e., in the
   // original binary and in the new one).
@@ -1065,7 +1072,8 @@ void writeDWARFSections(Module& wasm, const BinaryLocations& newLocations) {
 
   updateDebugLines(data, locationUpdater);
 
-  updateCompileUnits(info, data, locationUpdater, wasm.memory.is64());
+  bool is64 = wasm.memories.size() > 0 ? wasm.memories[0]->is64() : false;
+  updateCompileUnits(info, data, locationUpdater, is64);
 
   updateRanges(data, locationUpdater);
 
diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp
index ed2f0f4..9cc8b29 100644
--- a/src/wasm/wasm-emscripten.cpp
+++ b/src/wasm/wasm-emscripten.cpp
@@ -33,8 +33,6 @@
 
 namespace wasm {
 
-cashew::IString EM_JS_PREFIX("__em_js__");
-
 void addExportedFunction(Module& wasm, Function* function) {
   wasm.addFunction(function);
   auto export_ = new Export;
@@ -111,12 +109,12 @@ public:
   StringConstantTracker(Module& wasm) : wasm(wasm) { calcSegmentOffsets(); }
 
   const char* stringAtAddr(Address address) {
-    for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) {
-      Memory::Segment& segment = wasm.memory.segments[i];
+    for (unsigned i = 0; i < wasm.dataSegments.size(); ++i) {
+      auto& segment = wasm.dataSegments[i];
       Address offset = segmentOffsets[i];
       if (offset != UNKNOWN_OFFSET && address >= offset &&
-          address < offset + segment.data.size()) {
-        return &segment.data[address - offset];
+          address < offset + segment->data.size()) {
+        return &segment->data[address - offset];
       }
     }
     Fatal() << "unable to find data for ASM/EM_JS const at: " << address;
@@ -159,9 +157,9 @@ private:
       } searcher(passiveOffsets);
       searcher.walkModule(&wasm);
     }
-    for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) {
-      auto& segment = wasm.memory.segments[i];
-      if (segment.isPassive) {
+    for (unsigned i = 0; i < wasm.dataSegments.size(); ++i) {
+      auto& segment = wasm.dataSegments[i];
+      if (segment->isPassive) {
         auto it = passiveOffsets.find(i);
         if (it != passiveOffsets.end()) {
           segmentOffsets.push_back(it->second);
@@ -169,7 +167,7 @@ private:
           // This was a non-constant offset (perhaps TLS)
           segmentOffsets.push_back(UNKNOWN_OFFSET);
         }
-      } else if (auto* addrConst = segment.offset->dynCast<Const>()) {
+      } else if (auto* addrConst = segment->offset->dynCast<Const>()) {
         auto address = addrConst->value.getUnsigned();
         segmentOffsets.push_back(address);
       } else {
@@ -188,353 +186,36 @@ struct AsmConst {
   std::string code;
 };
 
-struct SegmentRemover : WalkerPass<PostWalker<SegmentRemover>> {
-  SegmentRemover(Index segment) : segment(segment) {}
-
-  bool isFunctionParallel() override { return true; }
-
-  Pass* create() override { return new SegmentRemover(segment); }
-
-  void visitMemoryInit(MemoryInit* curr) {
-    if (segment == curr->segment) {
-      Builder builder(*getModule());
-      replaceCurrent(builder.blockify(builder.makeDrop(curr->dest),
-                                      builder.makeDrop(curr->offset),
-                                      builder.makeDrop(curr->size)));
-    }
-  }
-
-  void visitDataDrop(DataDrop* curr) {
-    if (segment == curr->segment) {
-      Builder builder(*getModule());
-      replaceCurrent(builder.makeNop());
-    }
-  }
-
-  Index segment;
-};
-
-static void removeSegment(Module& wasm, Index segment) {
-  PassRunner runner(&wasm);
-  SegmentRemover(segment).run(&runner, &wasm);
-  // Resize the segment to zero.  In theory we should completely remove it
-  // but that would mean re-numbering the segments that follow which is
-  // non-trivial.
-  wasm.memory.segments[segment].data.resize(0);
-}
-
-static Address getExportedAddress(Module& wasm, Export* export_) {
-  Global* g = wasm.getGlobal(export_->value);
-  auto* addrConst = g->init->dynCast<Const>();
-  return addrConst->value.getUnsigned();
-}
-
-static std::vector<AsmConst> findEmAsmConsts(Module& wasm,
-                                             bool minimizeWasmChanges) {
-  // Newer version of emscripten/llvm export these symbols so we can use them to
-  // find all the EM_ASM constants.   Sadly __start_em_asm and __stop_em_asm
-  // don't alwasy mark the start and end of segment because in dynamic linking
-  // we merge all data segments into one.
-  Export* start = wasm.getExportOrNull("__start_em_asm");
-  Export* end = wasm.getExportOrNull("__stop_em_asm");
-  if (!start && !end) {
-    BYN_TRACE("findEmAsmConsts: no start/stop symbols\n");
-    return {};
-  }
-
-  if (!start || !end) {
-    Fatal() << "Found only one of __start_em_asm and __stop_em_asm";
-  }
-
-  std::vector<AsmConst> asmConsts;
-  StringConstantTracker stringTracker(wasm);
-  Address startAddress = getExportedAddress(wasm, start);
-  Address endAddress = getExportedAddress(wasm, end);
-  for (Index i = 0; i < wasm.memory.segments.size(); i++) {
-    Address segmentStart = stringTracker.segmentOffsets[i];
-    size_t segmentSize = wasm.memory.segments[i].data.size();
-    if (segmentStart <= startAddress &&
-        segmentStart + segmentSize >= endAddress) {
-      Address address = startAddress;
-      while (address < endAddress) {
-        auto code = stringTracker.stringAtAddr(address);
-        asmConsts.push_back({address, code});
-        address.addr += strlen(code) + 1;
-      }
-
-      if (segmentStart == startAddress &&
-          segmentStart + segmentSize == endAddress) {
-        removeSegment(wasm, i);
-      } else {
-        // If we can't remove the whole segment then just set the string
-        // data to zero.
-        size_t segmentOffset = startAddress - segmentStart;
-        char* startElem = &wasm.memory.segments[i].data[segmentOffset];
-        memset(startElem, 0, endAddress - startAddress);
-      }
-      break;
-    }
-  }
-
-  assert(asmConsts.size());
-  wasm.removeExport("__start_em_asm");
-  wasm.removeExport("__stop_em_asm");
-  return asmConsts;
-}
-
-struct EmJsWalker : public PostWalker<EmJsWalker> {
-  Module& wasm;
-  StringConstantTracker stringTracker;
-  std::vector<Export> toRemove;
-
-  std::map<std::string, std::string> codeByName;
-  std::map<Address, size_t> codeAddresses; // map from address to string len
-
-  EmJsWalker(Module& _wasm) : wasm(_wasm), stringTracker(_wasm) {}
-
-  void visitExport(Export* curr) {
-    if (!curr->name.startsWith(EM_JS_PREFIX.str)) {
-      return;
-    }
-
-    Address address;
-    if (curr->kind == ExternalKind::Global) {
-      auto* global = wasm.getGlobal(curr->value);
-      Const* const_ = global->init->cast<Const>();
-      address = const_->value.getUnsigned();
-    } else if (curr->kind == ExternalKind::Function) {
-      auto* func = wasm.getFunction(curr->value);
-      // An EM_JS has a single const in the body. Typically it is just returned,
-      // but in unoptimized code it might be stored to a local and loaded from
-      // there, and in relocatable code it might get added to __memory_base etc.
-      FindAll<Const> consts(func->body);
-      if (consts.list.size() != 1) {
-        Fatal() << "Unexpected generated __em_js__ function body: "
-                << curr->name;
-      }
-      auto* addrConst = consts.list[0];
-      address = addrConst->value.getUnsigned();
-    } else {
-      return;
-    }
-
-    toRemove.push_back(*curr);
-    auto code = stringTracker.stringAtAddr(address);
-    auto funcName = std::string(curr->name.stripPrefix(EM_JS_PREFIX.str));
-    codeByName[funcName] = code;
-    codeAddresses[address] = strlen(code) + 1;
-  }
-};
-
-EmJsWalker findEmJsFuncsAndReturnWalker(Module& wasm) {
-  EmJsWalker walker(wasm);
-  walker.walkModule(&wasm);
-
-  for (const Export& exp : walker.toRemove) {
-    if (exp.kind == ExternalKind::Function) {
-      wasm.removeFunction(exp.value);
-    } else {
-      wasm.removeGlobal(exp.value);
-    }
-    wasm.removeExport(exp.name);
-  }
-
-  // With newer versions of emscripten/llvm we pack all EM_JS strings into
-  // single segment.
-  // We can detect this by checking for segments that contain only JS strings.
-  // When we find such segements we remove them from the final binary.
-  for (Index i = 0; i < wasm.memory.segments.size(); i++) {
-    Address start = walker.stringTracker.segmentOffsets[i];
-    Address cur = start;
-
-    while (cur < start + wasm.memory.segments[i].data.size()) {
-      if (walker.codeAddresses.count(cur) == 0) {
-        break;
-      }
-      cur.addr += walker.codeAddresses[cur];
-    }
-
-    if (cur == start + wasm.memory.segments[i].data.size()) {
-      // Entire segment is contains JS strings.  Remove it.
-      removeSegment(wasm, i);
-    }
-  }
-  return walker;
-}
-
-std::string EmscriptenGlueGenerator::generateEmscriptenMetadata() {
-  bool commaFirst;
-  auto nextElement = [&commaFirst]() {
-    if (commaFirst) {
-      commaFirst = false;
-      return "\n    ";
-    } else {
-      return ",\n    ";
-    }
-  };
-
-  std::stringstream meta;
-  meta << "{\n";
-
-  std::vector<AsmConst> asmConsts = findEmAsmConsts(wasm, minimizeWasmChanges);
-
-  // print
-  commaFirst = true;
-  if (!asmConsts.empty()) {
-    meta << "  \"asmConsts\": {";
-    for (auto& asmConst : asmConsts) {
-      meta << nextElement();
-      meta << '"' << asmConst.id << "\": \"" << escape(asmConst.code) << "\"";
-    }
-    meta << "\n  },\n";
-  }
-
-  EmJsWalker emJsWalker = findEmJsFuncsAndReturnWalker(wasm);
-  if (!emJsWalker.codeByName.empty()) {
-    meta << "  \"emJsFuncs\": {";
-    commaFirst = true;
-    for (auto& [name, code] : emJsWalker.codeByName) {
-      meta << nextElement();
-      meta << '"' << name << "\": \"" << escape(code) << '"';
-    }
-    meta << "\n  },\n";
-  }
-
-  // Avoid adding duplicate imports to `declares' or `invokeFuncs`.  Even
-  // though we might import the same function multiple times (i.e. with
-  // different sigs) we only need to list is in the metadata once.
-  std::set<std::string> declares;
-  std::set<std::string> invokeFuncs;
-
-  // We use the `base` rather than the `name` of the imports here and below
-  // becasue this is the externally visible name that the embedder (JS) will
-  // see.
-  meta << "  \"declares\": [";
-  commaFirst = true;
-  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
-    if (emJsWalker.codeByName.count(import->base.str) == 0 &&
-        !import->base.startsWith("invoke_")) {
-      if (declares.insert(import->base.str).second) {
-        meta << nextElement() << '"' << import->base.str << '"';
-      }
-    }
-  });
-  meta << "\n  ],\n";
-
-  meta << "  \"globalImports\": [";
-  commaFirst = true;
-  ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
-    meta << nextElement() << '"' << import->base.str << '"';
-  });
-  meta << "\n  ],\n";
-
-  if (!wasm.exports.empty()) {
-    meta << "  \"exports\": [";
-    commaFirst = true;
-    for (const auto& ex : wasm.exports) {
-      if (ex->kind == ExternalKind::Function) {
-        meta << nextElement() << '"' << ex->name.str << '"';
-      }
-    }
-    meta << "\n  ],\n";
-
-    meta << "  \"namedGlobals\": {";
-    commaFirst = true;
-    for (const auto& ex : wasm.exports) {
-      if (ex->kind == ExternalKind::Global) {
-        const Global* g = wasm.getGlobal(ex->value);
-        assert(g->type == Type::i32 || g->type == Type::i64);
-        Const* init = g->init->cast<Const>();
-        uint64_t addr = init->value.getInteger();
-        meta << nextElement() << '"' << ex->name.str << "\" : \"" << addr
-             << '"';
-      }
-    }
-    meta << "\n  },\n";
-  }
-
-  meta << "  \"invokeFuncs\": [";
-  commaFirst = true;
-  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
-    if (import->module == ENV && import->base.startsWith("invoke_")) {
-      if (invokeFuncs.insert(import->base.str).second) {
-        meta << nextElement() << '"' << import->base.str << '"';
-      }
-    }
-  });
-  meta << "\n  ],\n";
-
-  // In normal mode we attempt to determine if main takes argumnts or not
-  // In standalone mode we export _start instead and rely on the presence
-  // of the __wasi_args_get and __wasi_args_sizes_get syscalls allow us to
-  // DCE to the argument handling JS code instead.
-  if (!standalone) {
-    auto mainReadsParams = false;
-    auto* exp = wasm.getExportOrNull("main");
-    if (!exp) {
-      exp = wasm.getExportOrNull("__main_argc_argv");
-    }
-    if (exp) {
-      if (exp->kind == ExternalKind::Function) {
-        auto* main = wasm.getFunction(exp->value);
-        mainReadsParams = true;
-        // If main does not read its parameters, it will just be a stub that
-        // calls __original_main (which has no parameters).
-        if (auto* call = main->body->dynCast<Call>()) {
-          if (call->operands.empty()) {
-            mainReadsParams = false;
-          }
-        }
-      }
-    }
-    meta << "  \"mainReadsParams\": " << int(mainReadsParams) << ",\n";
-  }
-
-  meta << "  \"features\": [";
-  commaFirst = true;
-  wasm.features.iterFeatures([&](FeatureSet::Feature f) {
-    meta << nextElement() << "\"--enable-" << FeatureSet::toString(f) << '"';
-  });
-  meta << "\n  ]\n";
-
-  meta << "}\n";
-
-  return meta.str();
-}
-
 void EmscriptenGlueGenerator::separateDataSegments(Output* outfile,
                                                    Address base) {
   size_t lastEnd = 0;
-  for (Memory::Segment& seg : wasm.memory.segments) {
-    if (seg.isPassive) {
+  for (auto& seg : wasm.dataSegments) {
+    if (seg->isPassive) {
       Fatal() << "separating passive segments not implemented";
     }
-    if (!seg.offset->is<Const>()) {
+    if (!seg->offset->is<Const>()) {
       Fatal() << "separating relocatable segments not implemented";
     }
-    size_t offset = seg.offset->cast<Const>()->value.getInteger();
+    size_t offset = seg->offset->cast<Const>()->value.getInteger();
     offset -= base;
     size_t fill = offset - lastEnd;
     if (fill > 0) {
       std::vector<char> buf(fill);
       outfile->write(buf.data(), fill);
     }
-    outfile->write(seg.data.data(), seg.data.size());
-    lastEnd = offset + seg.data.size();
-  }
-  wasm.memory.segments.clear();
-}
-
-void EmscriptenGlueGenerator::renameMainArgcArgv() {
-  // If an export call ed __main_argc_argv exists rename it to main
-  Export* ex = wasm.getExportOrNull("__main_argc_argv");
-  if (!ex) {
-    BYN_TRACE("renameMain: __main_argc_argv not found\n");
-    return;
+    outfile->write(seg->data.data(), seg->data.size());
+    lastEnd = offset + seg->data.size();
   }
-  ex->name = "main";
-  wasm.updateMaps();
-  ModuleUtils::renameFunction(wasm, "__main_argc_argv", "main");
+  wasm.dataSegments.clear();
+  // Remove the start/stop symbols that the PostEmscripten uses to remove
+  // em_asm/em_js data.  Since we just removed all the data segments from the
+  // file there is nothing more for that pass to do.
+  // TODO(sbc): Fix the ordering so that the removal the EM_ASM/EM_JS data comes
+  // before this pass.
+  wasm.removeExport("__start_em_asm");
+  wasm.removeExport("__stop_em_asm");
+  wasm.removeExport("__start_em_js");
+  wasm.removeExport("__stop_em_js");
 }
 
 } // namespace wasm
diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp
index b94fc84..90f267e 100644
--- a/src/wasm/wasm-io.cpp
+++ b/src/wasm/wasm-io.cpp
@@ -28,15 +28,27 @@
 #include "support/debug.h"
 #include "wasm-binary.h"
 #include "wasm-s-parser.h"
+#include "wat-parser.h"
+
 
 namespace wasm {
 
+bool useNewWATParser = false;
+
 #define DEBUG_TYPE "writer"
 
 static void readTextData(std::string& input, Module& wasm, IRProfile profile) {
-  SExpressionParser parser(const_cast<char*>(input.c_str()));
-  Element& root = *parser.root;
-  SExpressionWasmBuilder builder(wasm, *root[0], profile);
+  if (useNewWATParser) {
+    std::string_view in(input.c_str());
+    if (auto parsed = WATParser::parseModule(wasm, in);
+        auto err = parsed.getErr()) {
+      Fatal() << err->msg;
+    }
+  } else {
+    SExpressionParser parser(const_cast<char*>(input.c_str()));
+    Element& root = *parser.root;
+    SExpressionWasmBuilder builder(wasm, *root[0], profile);
+  }
 }
 
 void ModuleReader::readText(std::string filename, Module& wasm) {
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 8680ff8..ab62304 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -32,8 +32,6 @@
 #define element_assert(condition)                                              \
   assert((condition) ? true : (std::cerr << "on: " << *this << '\n' && 0));
 
-using cashew::IString;
-
 namespace {
 int unhex(char c) {
   if (c >= '0' && c <= '9') {
@@ -54,9 +52,11 @@ namespace wasm {
 static Name STRUCT("struct"), FIELD("field"), ARRAY("array"),
   FUNC_SUBTYPE("func_subtype"), STRUCT_SUBTYPE("struct_subtype"),
   ARRAY_SUBTYPE("array_subtype"), EXTENDS("extends"), REC("rec"), I8("i8"),
-  I16("i16"), RTT("rtt"), DECLARE("declare"), ITEM("item"), OFFSET("offset");
+  I16("i16"), DECLARE("declare"), ITEM("item"), OFFSET("offset");
 
-static Address getAddress(const Element* s) { return atoll(s->c_str()); }
+static Address getAddress(const Element* s) {
+  return std::stoll(s->toString());
+}
 
 static void
 checkAddress(Address a, const char* errorText, const Element* errorElem) {
@@ -97,11 +97,11 @@ IString Element::str() const {
   return str_;
 }
 
-const char* Element::c_str() const {
+std::string Element::toString() const {
   if (!isStr()) {
     throw ParseException("expected string", line, col);
   }
-  return str_.str;
+  return str_.toString();
 }
 
 Element* Element::setString(IString str__, bool dollared__, bool quoted__) {
@@ -358,11 +358,8 @@ SExpressionWasmBuilder::SExpressionWasmBuilder(Module& wasm,
   if (i < module.size() && module[i]->isStr()) {
     // these s-expressions contain a binary module, actually
     std::vector<char> data;
-    while (i < module.size()) {
-      auto str = module[i++]->c_str();
-      if (auto size = strlen(str)) {
-        stringToBinary(str, size, data);
-      }
+    for (; i < module.size(); ++i) {
+      stringToBinary(*module[i], module[i]->str().str, data);
     }
     // TODO: support applying features here
     WasmBinaryBuilder binaryBuilder(wasm, FeatureSet::MVP, data);
@@ -378,6 +375,7 @@ SExpressionWasmBuilder::SExpressionWasmBuilder(Module& wasm,
     auto& s = *module[j];
     preParseFunctionType(s);
     preParseImports(s);
+    preParseMemory(s);
     if (elementStartsWith(s, FUNC) && !isImport(s)) {
       implementedFunctions++;
     }
@@ -423,20 +421,27 @@ void SExpressionWasmBuilder::preParseImports(Element& curr) {
   }
 }
 
+void SExpressionWasmBuilder::preParseMemory(Element& curr) {
+  IString id = curr[0]->str();
+  if (id == MEMORY && !isImport(curr)) {
+    parseMemory(curr);
+  }
+}
+
 void SExpressionWasmBuilder::parseModuleElement(Element& curr) {
   if (isImport(curr)) {
     return; // already done
   }
   IString id = curr[0]->str();
+  if (id == MEMORY) {
+    return; // already done
+  }
   if (id == START) {
     return parseStart(curr);
   }
   if (id == FUNC) {
     return parseFunction(curr);
   }
-  if (id == MEMORY) {
-    return parseMemory(curr);
-  }
   if (id == DATA) {
     return parseData(curr);
   }
@@ -468,12 +473,20 @@ void SExpressionWasmBuilder::parseModuleElement(Element& curr) {
   throw ParseException("unknown module element", curr.line, curr.col);
 }
 
+int SExpressionWasmBuilder::parseIndex(Element& s) {
+  try {
+    return std::stoi(s.toString());
+  } catch (...) {
+    throw ParseException("expected integer", s.line, s.col);
+  }
+}
+
 Name SExpressionWasmBuilder::getFunctionName(Element& s) {
   if (s.dollared()) {
     return s.str();
   } else {
     // index
-    size_t offset = atoi(s.str().c_str());
+    size_t offset = parseIndex(s);
     if (offset >= functionNames.size()) {
       throw ParseException(
         "unknown function in getFunctionName", s.line, s.col);
@@ -487,7 +500,7 @@ Name SExpressionWasmBuilder::getTableName(Element& s) {
     return s.str();
   } else {
     // index
-    size_t offset = atoi(s.str().c_str());
+    size_t offset = parseIndex(s);
     if (offset >= tableNames.size()) {
       throw ParseException("unknown table in getTableName", s.line, s.col);
     }
@@ -495,12 +508,37 @@ Name SExpressionWasmBuilder::getTableName(Element& s) {
   }
 }
 
+bool SExpressionWasmBuilder::isMemory64(Name memoryName) {
+  auto* memory = wasm.getMemoryOrNull(memoryName);
+  if (!memory) {
+    throw ParseException("invalid memory name in isMemory64");
+  }
+  return memory->is64();
+}
+
+Name SExpressionWasmBuilder::getMemoryNameAtIdx(Index i) {
+  if (i >= memoryNames.size()) {
+    throw ParseException("unknown memory in getMemoryName");
+  }
+  return memoryNames[i];
+}
+
+Name SExpressionWasmBuilder::getMemoryName(Element& s) {
+  if (s.dollared()) {
+    return s.str();
+  } else {
+    // index
+    size_t offset = parseIndex(s);
+    return getMemoryNameAtIdx(offset);
+  }
+}
+
 Name SExpressionWasmBuilder::getGlobalName(Element& s) {
   if (s.dollared()) {
     return s.str();
   } else {
     // index
-    size_t offset = atoi(s.str().c_str());
+    size_t offset = parseIndex(s);
     if (offset >= globalNames.size()) {
       throw ParseException("unknown global in getGlobalName", s.line, s.col);
     }
@@ -513,7 +551,7 @@ Name SExpressionWasmBuilder::getTagName(Element& s) {
     return s.str();
   } else {
     // index
-    size_t offset = atoi(s.str().c_str());
+    size_t offset = parseIndex(s);
     if (offset >= tagNames.size()) {
       throw ParseException("unknown tag in getTagName", s.line, s.col);
     }
@@ -707,7 +745,7 @@ void SExpressionWasmBuilder::preParseHeapTypes(Element& module) {
   size_t numTypes = 0;
   forEachType([&](Element& elem, size_t) {
     if (elem[1]->dollared()) {
-      std::string name = elem[1]->c_str();
+      std::string name = elem[1]->toString();
       if (!typeIndices.insert({name, numTypes}).second) {
         throw ParseException("duplicate function type", elem.line, elem.col);
       }
@@ -738,60 +776,25 @@ void SExpressionWasmBuilder::preParseHeapTypes(Element& module) {
     auto nullable =
       elem[1]->isStr() && *elem[1] == NULL_ ? Nullable : NonNullable;
     auto& referent = nullable ? *elem[2] : *elem[1];
-    const char* name = referent.c_str();
+    auto name = referent.toString();
     if (referent.dollared()) {
       return builder.getTempRefType(builder[typeIndices[name]], nullable);
     } else if (String::isNumber(name)) {
-      size_t index = atoi(name);
+      size_t index = parseIndex(referent);
       if (index >= numTypes) {
         throw ParseException("invalid type index", elem.line, elem.col);
       }
       return builder.getTempRefType(builder[index], nullable);
     } else {
-      return Type(stringToHeapType(name), nullable);
+      return Type(stringToHeapType(referent.str()), nullable);
     }
   };
 
-  auto parseRttType = [&](Element& elem) -> Type {
-    // '(' 'rtt' depth? typeidx ')'
-    uint32_t depth;
-    Element* idx;
-    switch (elem.size()) {
-      default:
-        throw ParseException(
-          "unexpected number of rtt parameters", elem.line, elem.col);
-      case 2:
-        depth = Rtt::NoDepth;
-        idx = elem[1];
-        break;
-      case 3:
-        if (!String::isNumber(elem[1]->c_str())) {
-          throw ParseException(
-            "invalid rtt depth", elem[1]->line, elem[1]->col);
-        }
-        depth = atoi(elem[1]->c_str());
-        idx = elem[2];
-        break;
-    }
-    if (idx->dollared()) {
-      HeapType type = builder[typeIndices[idx->c_str()]];
-      return builder.getTempRttType(Rtt(depth, type));
-    } else if (String::isNumber(idx->c_str())) {
-      size_t index = atoi(idx->c_str());
-      if (index < numTypes) {
-        return builder.getTempRttType(Rtt(depth, builder[index]));
-      }
-    }
-    throw ParseException("invalid type index", idx->line, idx->col);
-  };
-
   auto parseValType = [&](Element& elem) {
     if (elem.isStr()) {
-      return stringToType(elem.c_str());
+      return stringToType(elem.str());
     } else if (*elem[0] == REF) {
       return parseRefType(elem);
-    } else if (*elem[0] == RTT) {
-      return parseRttType(elem);
     } else {
       throw ParseException("unknown valtype kind", elem[0]->line, elem[0]->col);
     }
@@ -925,7 +928,7 @@ void SExpressionWasmBuilder::preParseHeapTypes(Element& module) {
       super = extends[1];
     }
     if (super) {
-      auto it = typeIndices.find(super->c_str());
+      auto it = typeIndices.find(super->toString());
       if (it == typeIndices.end()) {
         throw ParseException("unknown supertype", super->line, super->col);
       }
@@ -1143,87 +1146,132 @@ void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
   nameMapper.clear();
 }
 
-Type SExpressionWasmBuilder::stringToType(const char* str,
+Type SExpressionWasmBuilder::stringToType(std::string_view str,
                                           bool allowError,
                                           bool prefix) {
-  if (str[0] == 'i') {
-    if (str[1] == '3' && str[2] == '2' && (prefix || str[3] == 0)) {
-      return Type::i32;
+  if (str.size() >= 3) {
+    if (str[0] == 'i') {
+      if (str[1] == '3' && str[2] == '2' && (prefix || str[3] == 0)) {
+        return Type::i32;
+      }
+      if (str[1] == '6' && str[2] == '4' && (prefix || str[3] == 0)) {
+        return Type::i64;
+      }
     }
-    if (str[1] == '6' && str[2] == '4' && (prefix || str[3] == 0)) {
-      return Type::i64;
+    if (str[0] == 'f') {
+      if (str[1] == '3' && str[2] == '2' && (prefix || str[3] == 0)) {
+        return Type::f32;
+      }
+      if (str[1] == '6' && str[2] == '4' && (prefix || str[3] == 0)) {
+        return Type::f64;
+      }
     }
   }
-  if (str[0] == 'f') {
-    if (str[1] == '3' && str[2] == '2' && (prefix || str[3] == 0)) {
-      return Type::f32;
-    }
-    if (str[1] == '6' && str[2] == '4' && (prefix || str[3] == 0)) {
-      return Type::f64;
+  if (str.size() >= 4) {
+    if (str[0] == 'v') {
+      if (str[1] == '1' && str[2] == '2' && str[3] == '8' &&
+          (prefix || str[4] == 0)) {
+        return Type::v128;
+      }
     }
   }
-  if (str[0] == 'v') {
-    if (str[1] == '1' && str[2] == '2' && str[3] == '8' &&
-        (prefix || str[4] == 0)) {
-      return Type::v128;
-    }
+  if (str.substr(0, 7) == "funcref" && (prefix || str.size() == 7)) {
+    return Type(HeapType::func, Nullable);
+  }
+  if (str.substr(0, 9) == "externref" && (prefix || str.size() == 9)) {
+    return Type(HeapType::ext, Nullable);
+  }
+  if (str.substr(0, 6) == "anyref" && (prefix || str.size() == 6)) {
+    return Type(HeapType::any, Nullable);
+  }
+  if (str.substr(0, 5) == "eqref" && (prefix || str.size() == 5)) {
+    return Type(HeapType::eq, Nullable);
+  }
+  if (str.substr(0, 6) == "i31ref" && (prefix || str.size() == 6)) {
+    return Type(HeapType::i31, Nullable);
+  }
+  if ((str.substr(0, 7) == "dataref" && (prefix || str.size() == 7)) ||
+      (str.substr(0, 9) == "structref" && (prefix || str.size() == 9))) {
+    return Type(HeapType::data, Nullable);
+  }
+  if (str.substr(0, 8) == "arrayref" && (prefix || str.size() == 8)) {
+    return Type(HeapType::array, Nullable);
+  }
+  if (str.substr(0, 9) == "stringref" && (prefix || str.size() == 9)) {
+    return Type(HeapType::string, Nullable);
   }
-  if (strncmp(str, "funcref", 7) == 0 && (prefix || str[7] == 0)) {
-    return Type::funcref;
+  if (str.substr(0, 15) == "stringview_wtf8" && (prefix || str.size() == 15)) {
+    return Type(HeapType::stringview_wtf8, Nullable);
   }
-  if ((strncmp(str, "externref", 9) == 0 && (prefix || str[9] == 0)) ||
-      (strncmp(str, "anyref", 6) == 0 && (prefix || str[6] == 0))) {
-    return Type::anyref;
+  if (str.substr(0, 16) == "stringview_wtf16" && (prefix || str.size() == 16)) {
+    return Type(HeapType::stringview_wtf16, Nullable);
   }
-  if (strncmp(str, "eqref", 5) == 0 && (prefix || str[5] == 0)) {
-    return Type::eqref;
+  if (str.substr(0, 15) == "stringview_iter" && (prefix || str.size() == 15)) {
+    return Type(HeapType::stringview_iter, Nullable);
   }
-  if (strncmp(str, "i31ref", 6) == 0 && (prefix || str[6] == 0)) {
-    return Type::i31ref;
+  if (str.substr(0, 7) == "nullref" && (prefix || str.size() == 7)) {
+    return Type(HeapType::none, Nullable);
   }
-  if (strncmp(str, "dataref", 7) == 0 && (prefix || str[7] == 0)) {
-    return Type::dataref;
+  if (str.substr(0, 13) == "nullexternref" && (prefix || str.size() == 13)) {
+    return Type(HeapType::noext, Nullable);
+  }
+  if (str.substr(0, 11) == "nullfuncref" && (prefix || str.size() == 11)) {
+    return Type(HeapType::nofunc, Nullable);
   }
   if (allowError) {
     return Type::none;
   }
-  throw ParseException(std::string("invalid wasm type: ") + str);
+  throw ParseException(std::string("invalid wasm type: ") +
+                       std::string(str.data(), str.size()));
 }
 
-HeapType SExpressionWasmBuilder::stringToHeapType(const char* str,
+HeapType SExpressionWasmBuilder::stringToHeapType(std::string_view str,
                                                   bool prefix) {
-  if (str[0] == 'f') {
-    if (str[1] == 'u' && str[2] == 'n' && str[3] == 'c' &&
-        (prefix || str[4] == 0)) {
-      return HeapType::func;
-    }
+  if (str.substr(0, 4) == "func" && (prefix || str.size() == 4)) {
+    return HeapType::func;
   }
-  if (str[0] == 'e') {
-    if (str[1] == 'q' && (prefix || str[2] == 0)) {
-      return HeapType::eq;
-    }
-    if (str[1] == 'x' && str[2] == 't' && str[3] == 'e' && str[4] == 'r' &&
-        str[5] == 'n' && (prefix || str[6] == 0)) {
-      return HeapType::any;
-    }
+  if (str.substr(0, 2) == "eq" && (prefix || str.size() == 2)) {
+    return HeapType::eq;
   }
-  if (str[0] == 'a') {
-    if (str[1] == 'n' && str[2] == 'y' && (prefix || str[3] == 0)) {
-      return HeapType::any;
-    }
+  if (str.substr(0, 6) == "extern" && (prefix || str.size() == 6)) {
+    return HeapType::ext;
   }
-  if (str[0] == 'i') {
-    if (str[1] == '3' && str[2] == '1' && (prefix || str[3] == 0)) {
-      return HeapType::i31;
-    }
+  if (str.substr(0, 3) == "any" && (prefix || str.size() == 3)) {
+    return HeapType::any;
   }
-  if (str[0] == 'd') {
-    if (str[1] == 'a' && str[2] == 't' && str[3] == 'a' &&
-        (prefix || str[4] == 0)) {
-      return HeapType::data;
-    }
+  if (str.substr(0, 3) == "i31" && (prefix || str.size() == 3)) {
+    return HeapType::i31;
+  }
+  if ((str.substr(0, 4) == "data" && (prefix || str.size() == 4)) ||
+      (str.substr(0, 6) == "struct" && (prefix || str.size() == 6))) {
+    return HeapType::data;
+  }
+  if (str.substr(0, 5) == "array" && (prefix || str.size() == 5)) {
+    return HeapType::array;
   }
-  throw ParseException(std::string("invalid wasm heap type: ") + str);
+  if (str.substr(0, 6) == "string" && (prefix || str.size() == 6)) {
+    return HeapType::string;
+  }
+  if (str.substr(0, 15) == "stringview_wtf8" && (prefix || str.size() == 15)) {
+    return HeapType::stringview_wtf8;
+  }
+  if (str.substr(0, 16) == "stringview_wtf16" && (prefix || str.size() == 16)) {
+    return HeapType::stringview_wtf16;
+  }
+  if (str.substr(0, 15) == "stringview_iter" && (prefix || str.size() == 15)) {
+    return HeapType::stringview_iter;
+  }
+  if (str.substr(0, 4) == "none" && (prefix || str.size() == 4)) {
+    return HeapType::none;
+  }
+  if (str.substr(0, 8) == "noextern" && (prefix || str.size() == 8)) {
+    return HeapType::noext;
+  }
+  if (str.substr(0, 6) == "nofunc" && (prefix || str.size() == 6)) {
+    return HeapType::nofunc;
+  }
+  throw ParseException(std::string("invalid wasm heap type: ") +
+                       std::string(str.data(), str.size()));
 }
 
 Type SExpressionWasmBuilder::elementToType(Element& s) {
@@ -1255,18 +1303,6 @@ Type SExpressionWasmBuilder::elementToType(Element& s) {
     }
     return Type(parseHeapType(*s[i]), nullable);
   }
-  if (elementStartsWith(s, RTT)) {
-    // It's an RTT, something like (rtt N $typename) or just (rtt $typename)
-    // if there is no depth.
-    if (s[1]->dollared()) {
-      auto heapType = parseHeapType(*s[1]);
-      return Type(Rtt(heapType));
-    } else {
-      auto depth = atoi(s[1]->str().c_str());
-      auto heapType = parseHeapType(*s[2]);
-      return Type(Rtt(depth, heapType));
-    }
-  }
   // It's a tuple.
   std::vector<Type> types;
   for (size_t i = 0; i < s.size(); ++i) {
@@ -1313,7 +1349,7 @@ SExpressionWasmBuilder::getDebugLocation(const SourceLocation& loc) {
   auto iter = debugInfoFileIndices.find(file);
   if (iter == debugInfoFileIndices.end()) {
     Index index = debugInfoFileNames.size();
-    debugInfoFileNames.push_back(file.c_str());
+    debugInfoFileNames.push_back(file.toString());
     debugInfoFileIndices[file] = index;
   }
   uint32_t fileIndex = debugInfoFileIndices[file];
@@ -1380,7 +1416,15 @@ Expression* SExpressionWasmBuilder::makeDrop(Element& s) {
 
 Expression* SExpressionWasmBuilder::makeMemorySize(Element& s) {
   auto ret = allocator.alloc<MemorySize>();
-  if (wasm.memory.is64()) {
+  Index i = 1;
+  Name memory;
+  if (s.size() > 1) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  if (isMemory64(memory)) {
     ret->make64();
   }
   ret->finalize();
@@ -1389,10 +1433,18 @@ Expression* SExpressionWasmBuilder::makeMemorySize(Element& s) {
 
 Expression* SExpressionWasmBuilder::makeMemoryGrow(Element& s) {
   auto ret = allocator.alloc<MemoryGrow>();
-  if (wasm.memory.is64()) {
+  Index i = 1;
+  Name memory;
+  if (s.size() > 2) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  if (isMemory64(memory)) {
     ret->make64();
   }
-  ret->delta = parseExpression(s[1]);
+  ret->delta = parseExpression(s[i]);
   ret->finalize();
   return ret;
 }
@@ -1409,7 +1461,7 @@ Index SExpressionWasmBuilder::getLocalIndex(Element& s) {
     return currFunction->getLocalIndex(ret);
   }
   // this is a numeric index
-  Index ret = atoi(s.c_str());
+  Index ret = parseIndex(s);
   if (ret >= currFunction->getNumLocals()) {
     throw ParseException("bad local index", s.line, s.col);
   }
@@ -1473,23 +1525,33 @@ Expression* SExpressionWasmBuilder::makeBlock(Element& s) {
   // incredibly deep
   auto curr = allocator.alloc<Block>();
   auto* sp = &s;
-  std::vector<std::pair<Element*, Block*>> stack;
+  // The information we need for the stack of blocks here is the element we are
+  // converting, the block we are converting it to, and whether it originally
+  // had a name or not (which will be useful later).
+  struct Info {
+    Element* element;
+    Block* block;
+    bool hadName;
+  };
+  std::vector<Info> stack;
   while (1) {
-    stack.emplace_back(sp, curr);
     auto& s = *sp;
     Index i = 1;
     Name sName;
+    bool hadName = false;
     if (i < s.size() && s[i]->isStr()) {
       // could be a name or a type
       if (s[i]->dollared() ||
           stringToType(s[i]->str(), true /* allowError */) == Type::none) {
         sName = s[i++]->str();
+        hadName = true;
       } else {
         sName = "block";
       }
     } else {
       sName = "block";
     }
+    stack.emplace_back(Info{sp, curr, hadName});
     curr->name = nameMapper.pushLabelName(sName);
     // block signature
     curr->type = parseOptionalResultType(s, i);
@@ -1510,8 +1572,9 @@ Expression* SExpressionWasmBuilder::makeBlock(Element& s) {
   }
   // we now have a stack of Blocks, with their labels, but no contents yet
   for (int t = int(stack.size()) - 1; t >= 0; t--) {
-    auto* sp = stack[t].first;
-    auto* curr = stack[t].second;
+    auto* sp = stack[t].element;
+    auto* curr = stack[t].block;
+    auto hadName = stack[t].hadName;
     auto& s = *sp;
     size_t i = 1;
     if (i < s.size()) {
@@ -1523,7 +1586,7 @@ Expression* SExpressionWasmBuilder::makeBlock(Element& s) {
       }
       if (t < int(stack.size()) - 1) {
         // first child is one of our recursions
-        curr->list.push_back(stack[t + 1].second);
+        curr->list.push_back(stack[t + 1].block);
         i++;
       }
       for (; i < s.size(); i++) {
@@ -1532,8 +1595,17 @@ Expression* SExpressionWasmBuilder::makeBlock(Element& s) {
     }
     nameMapper.popLabelName(curr->name);
     curr->finalize(curr->type);
+    // If the block never had a name, and one was not needed in practice (even
+    // if one did not exist, perhaps a break targeted it by index), then we can
+    // remove the name. Note that we only do this if it never had a name: if it
+    // did, we don't want to change anything; we just want to be the same as
+    // the code we are loading - if there was no name before, we don't want one
+    // now, so that we roundtrip text precisely.
+    if (!hadName && !BranchUtils::BranchSeeker::has(curr, curr->name)) {
+      curr->name = Name();
+    }
   }
-  return stack[0].second;
+  return stack[0].block;
 }
 
 // Similar to block, but the label is handled by the enclosing if (since there
@@ -1551,9 +1623,8 @@ Expression* SExpressionWasmBuilder::makeThenOrElse(Element& s) {
   return ret;
 }
 
-static Expression*
-parseConst(cashew::IString s, Type type, MixedArena& allocator) {
-  const char* str = s.str;
+static Expression* parseConst(IString s, Type type, MixedArena& allocator) {
+  const char* str = s.str.data();
   auto ret = allocator.alloc<Const>();
   ret->type = type;
   if (type.isFloat()) {
@@ -1568,7 +1639,6 @@ parseConst(cashew::IString s, Type type, MixedArena& allocator) {
         default:
           return nullptr;
       }
-      // std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
       return ret;
     }
     if (s == NEG_INFINITY) {
@@ -1582,7 +1652,6 @@ parseConst(cashew::IString s, Type type, MixedArena& allocator) {
         default:
           return nullptr;
       }
-      // std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
       return ret;
     }
     if (s == _NAN) {
@@ -1596,7 +1665,6 @@ parseConst(cashew::IString s, Type type, MixedArena& allocator) {
         default:
           return nullptr;
       }
-      // std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
       return ret;
     }
     bool negative = str[0] == '-';
@@ -1737,11 +1805,6 @@ parseConst(cashew::IString s, Type type, MixedArena& allocator) {
       break;
     }
     case Type::v128:
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
       WASM_UNREACHABLE("unexpected const type");
     case Type::none:
     case Type::unreachable: {
@@ -1751,7 +1814,6 @@ parseConst(cashew::IString s, Type type, MixedArena& allocator) {
   if (ret->value.type != type) {
     throw ParseException("parsed type does not match expected type");
   }
-  // std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
   return ret;
 }
 
@@ -1780,7 +1842,7 @@ Expression* SExpressionWasmBuilder::makeConst(Element& s, Type type) {
   }
 
   auto ret = allocator.alloc<Const>();
-  Type lane_t = stringToLaneType(s[1]->str().str);
+  Type lane_t = stringToLaneType(s[1]->str().str.data());
   size_t lanes = s.size() - 2;
   switch (lanes) {
     case 2: {
@@ -1823,39 +1885,14 @@ Expression* SExpressionWasmBuilder::makeConst(Element& s, Type type) {
   return ret;
 }
 
-static uint8_t parseMemBytes(const char*& s, uint8_t fallback) {
-  uint8_t ret;
-  if (s[0] == '8') {
-    ret = 1;
-    s++;
-  } else if (s[0] == '1') {
-    if (s[1] != '6') {
-      throw ParseException(std::string("expected 16 for memop size: ") + s);
-    }
-    ret = 2;
-    s += 2;
-  } else if (s[0] == '3') {
-    if (s[1] != '2') {
-      throw ParseException(std::string("expected 32 for memop size: ") + s);
-    };
-    ret = 4;
-    s += 2;
-  } else {
-    ret = fallback;
-  }
-  return ret;
-}
-
-static size_t parseMemAttributes(Element& s,
+static size_t parseMemAttributes(size_t i,
+                                 Element& s,
                                  Address& offset,
                                  Address& align,
-                                 Address fallbackAlign) {
-  size_t i = 1;
-  offset = 0;
-  align = fallbackAlign;
+                                 bool memory64) {
   // Parse "align=X" and "offset=X" arguments, bailing out on anything else.
   while (!s[i]->isList()) {
-    const char* str = s[i]->c_str();
+    const char* str = s[i]->str().str.data();
     if (strncmp(str, "align", 5) != 0 && strncmp(str, "offset", 6) != 0) {
       return i;
     }
@@ -1881,7 +1918,7 @@ static size_t parseMemAttributes(Element& s,
       }
       align = value;
     } else if (str[0] == 'o') {
-      if (value > std::numeric_limits<uint32_t>::max()) {
+      if (!memory64 && value > std::numeric_limits<uint32_t>::max()) {
         throw ParseException("bad offset", s[i]->line, s[i]->col);
       }
       offset = value;
@@ -1893,91 +1930,90 @@ static size_t parseMemAttributes(Element& s,
   return i;
 }
 
-static const char* findMemExtra(const Element& s, size_t skip, bool isAtomic) {
-  auto* str = s.c_str();
-  auto size = strlen(str);
-  auto* ret = strchr(str, '.');
-  if (!ret) {
-    throw ParseException("missing '.' in memory access", s.line, s.col);
-  }
-  ret += skip;
-  if (isAtomic) {
-    ret += 7; // after "type.atomic.load"
+bool SExpressionWasmBuilder::hasMemoryIdx(Element& s,
+                                          Index defaultSize,
+                                          Index i) {
+  if (s.size() > defaultSize && !s[i]->isList() &&
+      strncmp(s[i]->str().str.data(), "align", 5) != 0 &&
+      strncmp(s[i]->str().str.data(), "offset", 6) != 0) {
+    return true;
   }
-  if (ret > str + size) {
-    throw ParseException("memory access ends abruptly", s.line, s.col);
-  }
-  return ret;
+  return false;
 }
 
-Expression*
-SExpressionWasmBuilder::makeLoad(Element& s, Type type, bool isAtomic) {
-  const char* extra = findMemExtra(*s[0], 5 /* after "type.load" */, isAtomic);
+Expression* SExpressionWasmBuilder::makeLoad(
+  Element& s, Type type, bool signed_, int bytes, bool isAtomic) {
   auto* ret = allocator.alloc<Load>();
-  ret->isAtomic = isAtomic;
   ret->type = type;
-  ret->bytes = parseMemBytes(extra, type.getByteSize());
-  ret->signed_ = extra[0] && extra[1] == 's';
-  size_t i = parseMemAttributes(s, ret->offset, ret->align, ret->bytes);
+  ret->bytes = bytes;
+  ret->signed_ = signed_;
+  ret->offset = 0;
+  ret->align = bytes;
+  ret->isAtomic = isAtomic;
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 2, i)) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
   ret->ptr = parseExpression(s[i]);
   ret->finalize();
   return ret;
 }
 
-Expression*
-SExpressionWasmBuilder::makeStore(Element& s, Type type, bool isAtomic) {
-  const char* extra = findMemExtra(*s[0], 6 /* after "type.store" */, isAtomic);
+Expression* SExpressionWasmBuilder::makeStore(Element& s,
+                                              Type type,
+                                              int bytes,
+                                              bool isAtomic) {
   auto ret = allocator.alloc<Store>();
+  ret->bytes = bytes;
+  ret->offset = 0;
+  ret->align = bytes;
   ret->isAtomic = isAtomic;
   ret->valueType = type;
-  ret->bytes = parseMemBytes(extra, type.getByteSize());
-  size_t i = parseMemAttributes(s, ret->offset, ret->align, ret->bytes);
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 3, i)) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
   ret->ptr = parseExpression(s[i]);
   ret->value = parseExpression(s[i + 1]);
   ret->finalize();
   return ret;
 }
 
-Expression* SExpressionWasmBuilder::makeAtomicRMWOrCmpxchg(Element& s,
-                                                           Type type) {
-  const char* extra = findMemExtra(
-    *s[0], 11 /* after "type.atomic.rmw" */, /* isAtomic = */ false);
-  auto bytes = parseMemBytes(extra, type.getByteSize());
-  extra = strchr(extra, '.'); // after the optional '_u' and before the opcode
-  if (!extra) {
-    throw ParseException("malformed atomic rmw instruction", s.line, s.col);
-  }
-  extra++; // after the '.'
-  if (!strncmp(extra, "cmpxchg", 7)) {
-    return makeAtomicCmpxchg(s, type, bytes, extra);
-  }
-  return makeAtomicRMW(s, type, bytes, extra);
-}
-
 Expression* SExpressionWasmBuilder::makeAtomicRMW(Element& s,
+                                                  AtomicRMWOp op,
                                                   Type type,
-                                                  uint8_t bytes,
-                                                  const char* extra) {
+                                                  uint8_t bytes) {
   auto ret = allocator.alloc<AtomicRMW>();
   ret->type = type;
+  ret->op = op;
   ret->bytes = bytes;
-  if (!strncmp(extra, "add", 3)) {
-    ret->op = RMWAdd;
-  } else if (!strncmp(extra, "and", 3)) {
-    ret->op = RMWAnd;
-  } else if (!strncmp(extra, "or", 2)) {
-    ret->op = RMWOr;
-  } else if (!strncmp(extra, "sub", 3)) {
-    ret->op = RMWSub;
-  } else if (!strncmp(extra, "xor", 3)) {
-    ret->op = RMWXor;
-  } else if (!strncmp(extra, "xchg", 4)) {
-    ret->op = RMWXchg;
+  ret->offset = 0;
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 3, i)) {
+    memory = getMemoryName(*s[i++]);
   } else {
-    throw ParseException("bad atomic rmw operator", s.line, s.col);
+    memory = getMemoryNameAtIdx(0);
   }
-  Address align;
-  size_t i = parseMemAttributes(s, ret->offset, align, ret->bytes);
+  ret->memory = memory;
+  Address align = bytes;
+  i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
   if (align != ret->bytes) {
     throw ParseException("Align of Atomic RMW must match size", s.line, s.col);
   }
@@ -1989,13 +2025,23 @@ Expression* SExpressionWasmBuilder::makeAtomicRMW(Element& s,
 
 Expression* SExpressionWasmBuilder::makeAtomicCmpxchg(Element& s,
                                                       Type type,
-                                                      uint8_t bytes,
-                                                      const char* extra) {
+                                                      uint8_t bytes) {
   auto ret = allocator.alloc<AtomicCmpxchg>();
   ret->type = type;
   ret->bytes = bytes;
-  Address align;
-  size_t i = parseMemAttributes(s, ret->offset, align, ret->bytes);
+  ret->offset = 0;
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 4, i)) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  Address align = ret->bytes;
+  i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
   if (align != ret->bytes) {
     throw ParseException(
       "Align of Atomic Cmpxchg must match size", s.line, s.col);
@@ -2010,17 +2056,21 @@ Expression* SExpressionWasmBuilder::makeAtomicCmpxchg(Element& s,
 Expression* SExpressionWasmBuilder::makeAtomicWait(Element& s, Type type) {
   auto ret = allocator.alloc<AtomicWait>();
   ret->type = Type::i32;
+  ret->offset = 0;
   ret->expectedType = type;
-  Address align;
-  Address expectedAlign;
-  if (type == Type::i32) {
-    expectedAlign = 4;
-  } else if (type == Type::i64) {
-    expectedAlign = 8;
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 4, i)) {
+    memory = getMemoryName(*s[i++]);
   } else {
-    WASM_UNREACHABLE("Invalid prefix for memory.atomic.wait");
+    memory = getMemoryNameAtIdx(0);
   }
-  size_t i = parseMemAttributes(s, ret->offset, align, expectedAlign);
+  ret->memory = memory;
+  Address expectedAlign = type == Type::i64 ? 8 : 4;
+  Address align = expectedAlign;
+  i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
   if (align != expectedAlign) {
     throw ParseException(
       "Align of memory.atomic.wait must match size", s.line, s.col);
@@ -2035,8 +2085,19 @@ Expression* SExpressionWasmBuilder::makeAtomicWait(Element& s, Type type) {
 Expression* SExpressionWasmBuilder::makeAtomicNotify(Element& s) {
   auto ret = allocator.alloc<AtomicNotify>();
   ret->type = Type::i32;
-  Address align;
-  size_t i = parseMemAttributes(s, ret->offset, align, 4);
+  ret->offset = 0;
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 3, i)) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  Address align = 4;
+  i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
   if (align != 4) {
     throw ParseException(
       "Align of memory.atomic.notify must be 4", s.line, s.col);
@@ -2052,7 +2113,7 @@ Expression* SExpressionWasmBuilder::makeAtomicFence(Element& s) {
 }
 
 static uint8_t parseLaneIndex(const Element* s, size_t lanes) {
-  const char* str = s->c_str();
+  const char* str = s->str().str.data();
   char* end;
   auto n = static_cast<unsigned long long>(strtoll(str, &end, 10));
   if (end == str || *end != '\0') {
@@ -2119,70 +2180,66 @@ Expression* SExpressionWasmBuilder::makeSIMDShift(Element& s, SIMDShiftOp op) {
   return ret;
 }
 
-Expression* SExpressionWasmBuilder::makeSIMDLoad(Element& s, SIMDLoadOp op) {
+Expression*
+SExpressionWasmBuilder::makeSIMDLoad(Element& s, SIMDLoadOp op, int bytes) {
   auto ret = allocator.alloc<SIMDLoad>();
   ret->op = op;
-  Address defaultAlign;
-  switch (op) {
-    case Load8SplatVec128:
-      defaultAlign = 1;
-      break;
-    case Load16SplatVec128:
-      defaultAlign = 2;
-      break;
-    case Load32SplatVec128:
-    case Load32ZeroVec128:
-      defaultAlign = 4;
-      break;
-    case Load64SplatVec128:
-    case Load8x8SVec128:
-    case Load8x8UVec128:
-    case Load16x4SVec128:
-    case Load16x4UVec128:
-    case Load32x2SVec128:
-    case Load32x2UVec128:
-    case Load64ZeroVec128:
-      defaultAlign = 8;
-      break;
+  ret->offset = 0;
+  ret->align = bytes;
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 2, i)) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
   }
-  size_t i = parseMemAttributes(s, ret->offset, ret->align, defaultAlign);
+  ret->memory = memory;
+  i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
   ret->ptr = parseExpression(s[i]);
   ret->finalize();
   return ret;
 }
 
-Expression*
-SExpressionWasmBuilder::makeSIMDLoadStoreLane(Element& s,
-                                              SIMDLoadStoreLaneOp op) {
+Expression* SExpressionWasmBuilder::makeSIMDLoadStoreLane(
+  Element& s, SIMDLoadStoreLaneOp op, int bytes) {
   auto* ret = allocator.alloc<SIMDLoadStoreLane>();
   ret->op = op;
-  Address defaultAlign;
+  ret->offset = 0;
+  ret->align = bytes;
   size_t lanes;
   switch (op) {
     case Load8LaneVec128:
     case Store8LaneVec128:
-      defaultAlign = 1;
       lanes = 16;
       break;
     case Load16LaneVec128:
     case Store16LaneVec128:
-      defaultAlign = 2;
       lanes = 8;
       break;
     case Load32LaneVec128:
     case Store32LaneVec128:
-      defaultAlign = 4;
       lanes = 4;
       break;
     case Load64LaneVec128:
     case Store64LaneVec128:
-      defaultAlign = 8;
       lanes = 2;
       break;
     default:
       WASM_UNREACHABLE("Unexpected SIMDLoadStoreLane op");
   }
-  size_t i = parseMemAttributes(s, ret->offset, ret->align, defaultAlign);
+  Index i = 1;
+  Name memory;
+  // Check to make sure there are more than the default args & this str isn't
+  // the mem attributes
+  if (hasMemoryIdx(s, 4, i)) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
   ret->index = parseLaneIndex(s[i++], lanes);
   ret->ptr = parseExpression(s[i++]);
   ret->vec = parseExpression(s[i]);
@@ -2192,35 +2249,63 @@ SExpressionWasmBuilder::makeSIMDLoadStoreLane(Element& s,
 
 Expression* SExpressionWasmBuilder::makeMemoryInit(Element& s) {
   auto ret = allocator.alloc<MemoryInit>();
-  ret->segment = atoi(s[1]->str().c_str());
-  ret->dest = parseExpression(s[2]);
-  ret->offset = parseExpression(s[3]);
-  ret->size = parseExpression(s[4]);
+  Index i = 1;
+  Name memory;
+  if (s.size() > 5) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  ret->segment = parseIndex(*s[i++]);
+  ret->dest = parseExpression(s[i++]);
+  ret->offset = parseExpression(s[i++]);
+  ret->size = parseExpression(s[i]);
   ret->finalize();
   return ret;
 }
 
 Expression* SExpressionWasmBuilder::makeDataDrop(Element& s) {
   auto ret = allocator.alloc<DataDrop>();
-  ret->segment = atoi(s[1]->str().c_str());
+  ret->segment = parseIndex(*s[1]);
   ret->finalize();
   return ret;
 }
 
 Expression* SExpressionWasmBuilder::makeMemoryCopy(Element& s) {
   auto ret = allocator.alloc<MemoryCopy>();
-  ret->dest = parseExpression(s[1]);
-  ret->source = parseExpression(s[2]);
-  ret->size = parseExpression(s[3]);
+  Index i = 1;
+  Name destMemory;
+  Name sourceMemory;
+  if (s.size() > 4) {
+    destMemory = getMemoryName(*s[i++]);
+    sourceMemory = getMemoryName(*s[i++]);
+  } else {
+    destMemory = getMemoryNameAtIdx(0);
+    sourceMemory = getMemoryNameAtIdx(0);
+  }
+  ret->destMemory = destMemory;
+  ret->sourceMemory = sourceMemory;
+  ret->dest = parseExpression(s[i++]);
+  ret->source = parseExpression(s[i++]);
+  ret->size = parseExpression(s[i]);
   ret->finalize();
   return ret;
 }
 
 Expression* SExpressionWasmBuilder::makeMemoryFill(Element& s) {
   auto ret = allocator.alloc<MemoryFill>();
-  ret->dest = parseExpression(s[1]);
-  ret->value = parseExpression(s[2]);
-  ret->size = parseExpression(s[3]);
+  Index i = 1;
+  Name memory;
+  if (s.size() > 4) {
+    memory = getMemoryName(*s[i++]);
+  } else {
+    memory = getMemoryNameAtIdx(0);
+  }
+  ret->memory = memory;
+  ret->dest = parseExpression(s[i++]);
+  ret->value = parseExpression(s[i++]);
+  ret->size = parseExpression(s[i]);
   ret->finalize();
   return ret;
 }
@@ -2364,7 +2449,7 @@ Name SExpressionWasmBuilder::getLabel(Element& s, LabelType labelType) {
     // offset, break to nth outside label
     uint64_t offset;
     try {
-      offset = std::stoll(s.c_str(), nullptr, 0);
+      offset = std::stoll(s.toString(), nullptr, 0);
     } catch (std::invalid_argument&) {
       throw ParseException("invalid break offset", s.line, s.col);
     } catch (std::out_of_range&) {
@@ -2444,9 +2529,9 @@ Expression* SExpressionWasmBuilder::makeRefNull(Element& s) {
   // (ref.null func), or it may be the name of a defined type, such as
   // (ref.null $struct.FOO)
   if (s[1]->dollared()) {
-    ret->finalize(parseHeapType(*s[1]));
+    ret->finalize(parseHeapType(*s[1]).getBottom());
   } else {
-    ret->finalize(stringToHeapType(s[1]->str()));
+    ret->finalize(stringToHeapType(s[1]->str()).getBottom());
   }
   return ret;
 }
@@ -2648,7 +2733,7 @@ Expression* SExpressionWasmBuilder::makeTupleMake(Element& s) {
 
 Expression* SExpressionWasmBuilder::makeTupleExtract(Element& s) {
   auto ret = allocator.alloc<TupleExtract>();
-  ret->index = atoi(s[1]->str().c_str());
+  ret->index = parseIndex(*s[1]);
   ret->tuple = parseExpression(s[2]);
   if (ret->tuple->type != Type::unreachable &&
       ret->index >= ret->tuple->type.size()) {
@@ -2659,11 +2744,20 @@ Expression* SExpressionWasmBuilder::makeTupleExtract(Element& s) {
 }
 
 Expression* SExpressionWasmBuilder::makeCallRef(Element& s, bool isReturn) {
+  HeapType sigType = parseHeapType(*s[1]);
   std::vector<Expression*> operands;
-  parseOperands(s, 1, s.size() - 1, operands);
+  parseOperands(s, 2, s.size() - 1, operands);
   auto* target = parseExpression(s[s.size() - 1]);
-  return ValidatingBuilder(wasm, s.line, s.col)
-    .validateAndMakeCallRef(target, operands, isReturn);
+
+  if (!sigType.isSignature()) {
+    throw ParseException(
+      std::string(isReturn ? "return_call_ref" : "call_ref") +
+        " type annotation should be a signature",
+      s.line,
+      s.col);
+  }
+  return Builder(wasm).makeCallRef(
+    target, operands, sigType.getSignature().results, isReturn);
 }
 
 Expression* SExpressionWasmBuilder::makeI31New(Element& s) {
@@ -2681,24 +2775,12 @@ Expression* SExpressionWasmBuilder::makeI31Get(Element& s, bool signed_) {
   return ret;
 }
 
-Expression* SExpressionWasmBuilder::makeRefTest(Element& s) {
-  auto* ref = parseExpression(*s[1]);
-  auto* rtt = parseExpression(*s[2]);
-  return Builder(wasm).makeRefTest(ref, rtt);
-}
-
 Expression* SExpressionWasmBuilder::makeRefTestStatic(Element& s) {
   auto heapType = parseHeapType(*s[1]);
   auto* ref = parseExpression(*s[2]);
   return Builder(wasm).makeRefTest(ref, heapType);
 }
 
-Expression* SExpressionWasmBuilder::makeRefCast(Element& s) {
-  auto* ref = parseExpression(*s[1]);
-  auto* rtt = parseExpression(*s[2]);
-  return Builder(wasm).makeRefCast(ref, rtt);
-}
-
 Expression* SExpressionWasmBuilder::makeRefCastStatic(Element& s) {
   auto heapType = parseHeapType(*s[1]);
   auto* ref = parseExpression(*s[2]);
@@ -2714,12 +2796,8 @@ Expression* SExpressionWasmBuilder::makeRefCastNopStatic(Element& s) {
 Expression* SExpressionWasmBuilder::makeBrOn(Element& s, BrOnOp op) {
   auto name = getLabel(*s[1]);
   auto* ref = parseExpression(*s[2]);
-  Expression* rtt = nullptr;
-  if (op == BrOnCast || op == BrOnCastFail) {
-    rtt = parseExpression(*s[3]);
-  }
   return ValidatingBuilder(wasm, s.line, s.col)
-    .validateAndMakeBrOn(op, name, ref, rtt);
+    .validateAndMakeBrOn(op, name, ref);
 }
 
 Expression* SExpressionWasmBuilder::makeBrOnStatic(Element& s, BrOnOp op) {
@@ -2729,39 +2807,6 @@ Expression* SExpressionWasmBuilder::makeBrOnStatic(Element& s, BrOnOp op) {
   return Builder(wasm).makeBrOn(op, name, ref, heapType);
 }
 
-Expression* SExpressionWasmBuilder::makeRttCanon(Element& s) {
-  return Builder(wasm).makeRttCanon(parseHeapType(*s[1]));
-}
-
-Expression* SExpressionWasmBuilder::makeRttSub(Element& s) {
-  auto heapType = parseHeapType(*s[1]);
-  auto parent = parseExpression(*s[2]);
-  return Builder(wasm).makeRttSub(heapType, parent);
-}
-
-Expression* SExpressionWasmBuilder::makeRttFreshSub(Element& s) {
-  auto heapType = parseHeapType(*s[1]);
-  auto parent = parseExpression(*s[2]);
-  return Builder(wasm).makeRttFreshSub(heapType, parent);
-}
-
-Expression* SExpressionWasmBuilder::makeStructNew(Element& s, bool default_) {
-  auto heapType = parseHeapType(*s[1]);
-  auto numOperands = s.size() - 3;
-  if (default_ && numOperands > 0) {
-    throw ParseException(
-      "arguments provided for struct.new_with_default", s.line, s.col);
-  }
-  std::vector<Expression*> operands;
-  operands.resize(numOperands);
-  for (Index i = 0; i < numOperands; i++) {
-    operands[i] = parseExpression(*s[i + 2]);
-  }
-  auto* rtt = parseExpression(*s[s.size() - 1]);
-  validateHeapTypeUsingChild(rtt, heapType, s);
-  return Builder(wasm).makeStructNew(rtt, operands);
-}
-
 Expression* SExpressionWasmBuilder::makeStructNewStatic(Element& s,
                                                         bool default_) {
   auto heapType = parseHeapType(*s[1]);
@@ -2780,7 +2825,7 @@ Expression* SExpressionWasmBuilder::makeStructNewStatic(Element& s,
 Index SExpressionWasmBuilder::getStructIndex(Element& type, Element& field) {
   if (field.dollared()) {
     auto name = field.str();
-    auto index = typeIndices[type.str().str];
+    auto index = typeIndices[type.toString()];
     auto struct_ = types[index].getStruct();
     auto& fields = struct_.fields;
     const auto& names = fieldNames[index];
@@ -2793,7 +2838,7 @@ Index SExpressionWasmBuilder::getStructIndex(Element& type, Element& field) {
     throw ParseException("bad struct field name", field.line, field.col);
   }
   // this is a numeric index
-  return atoi(field.c_str());
+  return parseIndex(field);
 }
 
 Expression* SExpressionWasmBuilder::makeStructGet(Element& s, bool signed_) {
@@ -2820,19 +2865,6 @@ Expression* SExpressionWasmBuilder::makeStructSet(Element& s) {
   return Builder(wasm).makeStructSet(index, ref, value);
 }
 
-Expression* SExpressionWasmBuilder::makeArrayNew(Element& s, bool default_) {
-  auto heapType = parseHeapType(*s[1]);
-  Expression* init = nullptr;
-  size_t i = 2;
-  if (!default_) {
-    init = parseExpression(*s[i++]);
-  }
-  auto* size = parseExpression(*s[i++]);
-  auto* rtt = parseExpression(*s[i++]);
-  validateHeapTypeUsingChild(rtt, heapType, s);
-  return Builder(wasm).makeArrayNew(rtt, size, init);
-}
-
 Expression* SExpressionWasmBuilder::makeArrayNewStatic(Element& s,
                                                        bool default_) {
   auto heapType = parseHeapType(*s[1]);
@@ -2845,16 +2877,13 @@ Expression* SExpressionWasmBuilder::makeArrayNewStatic(Element& s,
   return Builder(wasm).makeArrayNew(heapType, size, init);
 }
 
-Expression* SExpressionWasmBuilder::makeArrayInit(Element& s) {
+Expression* SExpressionWasmBuilder::makeArrayNewSeg(Element& s,
+                                                    ArrayNewSegOp op) {
   auto heapType = parseHeapType(*s[1]);
-  size_t i = 2;
-  std::vector<Expression*> values;
-  while (i < s.size() - 1) {
-    values.push_back(parseExpression(*s[i++]));
-  }
-  auto* rtt = parseExpression(*s[i++]);
-  validateHeapTypeUsingChild(rtt, heapType, s);
-  return Builder(wasm).makeArrayInit(rtt, values);
+  Index seg = parseIndex(*s[2]);
+  Expression* offset = parseExpression(*s[3]);
+  Expression* size = parseExpression(*s[4]);
+  return Builder(wasm).makeArrayNewSeg(op, heapType, seg, offset, size);
 }
 
 Expression* SExpressionWasmBuilder::makeArrayInitStatic(Element& s) {
@@ -2869,10 +2898,14 @@ Expression* SExpressionWasmBuilder::makeArrayInitStatic(Element& s) {
 
 Expression* SExpressionWasmBuilder::makeArrayGet(Element& s, bool signed_) {
   auto heapType = parseHeapType(*s[1]);
+  if (!heapType.isArray()) {
+    throw ParseException("bad array heap type", s.line, s.col);
+  }
   auto ref = parseExpression(*s[2]);
+  auto type = heapType.getArray().element.type;
   validateHeapTypeUsingChild(ref, heapType, s);
   auto index = parseExpression(*s[3]);
-  return Builder(wasm).makeArrayGet(ref, index, signed_);
+  return Builder(wasm).makeArrayGet(ref, index, type, signed_);
 }
 
 Expression* SExpressionWasmBuilder::makeArraySet(Element& s) {
@@ -2885,9 +2918,14 @@ Expression* SExpressionWasmBuilder::makeArraySet(Element& s) {
 }
 
 Expression* SExpressionWasmBuilder::makeArrayLen(Element& s) {
-  auto heapType = parseHeapType(*s[1]);
-  auto ref = parseExpression(*s[2]);
-  validateHeapTypeUsingChild(ref, heapType, s);
+  // There may or may not be a type annotation.
+  Index i = 1;
+  try {
+    parseHeapType(*s[i]);
+    ++i;
+  } catch (...) {
+  }
+  auto ref = parseExpression(*s[i]);
   return Builder(wasm).makeArrayLen(ref);
 }
 
@@ -2909,21 +2947,174 @@ Expression* SExpressionWasmBuilder::makeRefAs(Element& s, RefAsOp op) {
   return Builder(wasm).makeRefAs(op, parseExpression(s[1]));
 }
 
+Expression* SExpressionWasmBuilder::makeStringNew(Element& s, StringNewOp op) {
+  size_t i = 1;
+  Expression* length = nullptr;
+  if (op == StringNewWTF8) {
+    std::string_view str = s[i++]->str().str;
+    if (str == "utf8") {
+      op = StringNewUTF8;
+    } else if (str == "wtf8") {
+      op = StringNewWTF8;
+    } else if (str == "replace") {
+      op = StringNewReplace;
+    } else {
+      throw ParseException("bad string.new op", s.line, s.col);
+    }
+    length = parseExpression(s[i + 1]);
+    return Builder(wasm).makeStringNew(op, parseExpression(s[i]), length);
+  } else if (op == StringNewWTF16) {
+    length = parseExpression(s[i + 1]);
+    return Builder(wasm).makeStringNew(op, parseExpression(s[i]), length);
+  } else if (op == StringNewWTF8Array) {
+    std::string_view str = s[i++]->str().str;
+    if (str == "utf8") {
+      op = StringNewUTF8Array;
+    } else if (str == "wtf8") {
+      op = StringNewWTF8Array;
+    } else if (str == "replace") {
+      op = StringNewReplaceArray;
+    } else {
+      throw ParseException("bad string.new op", s.line, s.col);
+    }
+    auto* start = parseExpression(s[i + 1]);
+    auto* end = parseExpression(s[i + 2]);
+    return Builder(wasm).makeStringNew(op, parseExpression(s[i]), start, end);
+  } else if (op == StringNewWTF16Array) {
+    auto* start = parseExpression(s[i + 1]);
+    auto* end = parseExpression(s[i + 2]);
+    return Builder(wasm).makeStringNew(op, parseExpression(s[i]), start, end);
+  } else {
+    throw ParseException("bad string.new op", s.line, s.col);
+  }
+}
+
+Expression* SExpressionWasmBuilder::makeStringConst(Element& s) {
+  std::vector<char> data;
+  stringToBinary(*s[1], s[1]->str().str, data);
+  Name str = std::string_view(data.data(), data.size());
+  return Builder(wasm).makeStringConst(str);
+}
+
+Expression* SExpressionWasmBuilder::makeStringMeasure(Element& s,
+                                                      StringMeasureOp op) {
+  size_t i = 1;
+  if (op == StringMeasureWTF8) {
+    std::string_view str = s[i++]->str().str;
+    if (str == "utf8") {
+      op = StringMeasureUTF8;
+    } else if (str == "wtf8") {
+      op = StringMeasureWTF8;
+    } else {
+      throw ParseException("bad string.measure op", s.line, s.col);
+    }
+  }
+  return Builder(wasm).makeStringMeasure(op, parseExpression(s[i]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringEncode(Element& s,
+                                                     StringEncodeOp op) {
+  size_t i = 1;
+  Expression* start = nullptr;
+  if (op == StringEncodeWTF8) {
+    std::string_view str = s[i++]->str().str;
+    if (str == "utf8") {
+      op = StringEncodeUTF8;
+    } else if (str == "wtf8") {
+      op = StringEncodeWTF8;
+    } else {
+      throw ParseException("bad string.new op", s.line, s.col);
+    }
+  } else if (op == StringEncodeWTF8Array) {
+    std::string_view str = s[i++]->str().str;
+    if (str == "utf8") {
+      op = StringEncodeUTF8Array;
+    } else if (str == "wtf8") {
+      op = StringEncodeWTF8Array;
+    } else {
+      throw ParseException("bad string.new op", s.line, s.col);
+    }
+    start = parseExpression(s[i + 2]);
+  } else if (op == StringEncodeWTF16Array) {
+    start = parseExpression(s[i + 2]);
+  }
+  return Builder(wasm).makeStringEncode(
+    op, parseExpression(s[i]), parseExpression(s[i + 1]), start);
+}
+
+Expression* SExpressionWasmBuilder::makeStringConcat(Element& s) {
+  return Builder(wasm).makeStringConcat(parseExpression(s[1]),
+                                        parseExpression(s[2]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringEq(Element& s) {
+  return Builder(wasm).makeStringEq(parseExpression(s[1]),
+                                    parseExpression(s[2]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringAs(Element& s, StringAsOp op) {
+  return Builder(wasm).makeStringAs(op, parseExpression(s[1]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringWTF8Advance(Element& s) {
+  return Builder(wasm).makeStringWTF8Advance(
+    parseExpression(s[1]), parseExpression(s[2]), parseExpression(s[3]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringWTF16Get(Element& s) {
+  return Builder(wasm).makeStringWTF16Get(parseExpression(s[1]),
+                                          parseExpression(s[2]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringIterNext(Element& s) {
+  return Builder(wasm).makeStringIterNext(parseExpression(s[1]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringIterMove(Element& s,
+                                                       StringIterMoveOp op) {
+  return Builder(wasm).makeStringIterMove(
+    op, parseExpression(s[1]), parseExpression(s[2]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringSliceWTF(Element& s,
+                                                       StringSliceWTFOp op) {
+  return Builder(wasm).makeStringSliceWTF(
+    op, parseExpression(s[1]), parseExpression(s[2]), parseExpression(s[3]));
+}
+
+Expression* SExpressionWasmBuilder::makeStringSliceIter(Element& s) {
+  return Builder(wasm).makeStringSliceIter(parseExpression(s[1]),
+                                           parseExpression(s[2]));
+}
+
 // converts an s-expression string representing binary data into an output
 // sequence of raw bytes this appends to data, which may already contain
 // content.
-void SExpressionWasmBuilder::stringToBinary(const char* input,
-                                            size_t size,
+void SExpressionWasmBuilder::stringToBinary(Element& s,
+                                            std::string_view str,
                                             std::vector<char>& data) {
   auto originalSize = data.size();
-  data.resize(originalSize + size);
+  data.resize(originalSize + str.size());
   char* write = data.data() + originalSize;
-  while (1) {
-    if (input[0] == 0) {
-      break;
-    }
+  const char* end = str.data() + str.size();
+  for (const char* input = str.data(); input < end;) {
     if (input[0] == '\\') {
-      if (input[1] == '"') {
+      if (input + 1 >= end) {
+        throw ParseException("Unterminated escape sequence", s.line, s.col);
+      }
+      if (input[1] == 't') {
+        *write++ = '\t';
+        input += 2;
+        continue;
+      } else if (input[1] == 'n') {
+        *write++ = '\n';
+        input += 2;
+        continue;
+      } else if (input[1] == 'r') {
+        *write++ = '\r';
+        input += 2;
+        continue;
+      } else if (input[1] == '"') {
         *write++ = '"';
         input += 2;
         continue;
@@ -2935,15 +3126,10 @@ void SExpressionWasmBuilder::stringToBinary(const char* input,
         *write++ = '\\';
         input += 2;
         continue;
-      } else if (input[1] == 'n') {
-        *write++ = '\n';
-        input += 2;
-        continue;
-      } else if (input[1] == 't') {
-        *write++ = '\t';
-        input += 2;
-        continue;
       } else {
+        if (input + 2 >= end) {
+          throw ParseException("Unterminated escape sequence", s.line, s.col);
+        }
         *write++ = (char)(unhex(input[1]) * 16 + unhex(input[2]));
         input += 3;
         continue;
@@ -2958,35 +3144,37 @@ void SExpressionWasmBuilder::stringToBinary(const char* input,
   data.resize(actual);
 }
 
-Index SExpressionWasmBuilder::parseMemoryIndex(Element& s, Index i) {
+Index SExpressionWasmBuilder::parseMemoryIndex(
+  Element& s, Index i, std::unique_ptr<Memory>& memory) {
   if (i < s.size() && s[i]->isStr()) {
     if (s[i]->str() == "i64") {
       i++;
-      wasm.memory.indexType = Type::i64;
+      memory->indexType = Type::i64;
     } else if (s[i]->str() == "i32") {
       i++;
-      wasm.memory.indexType = Type::i32;
+      memory->indexType = Type::i32;
     }
   }
   return i;
 }
 
-Index SExpressionWasmBuilder::parseMemoryLimits(Element& s, Index i) {
-  i = parseMemoryIndex(s, i);
+Index SExpressionWasmBuilder::parseMemoryLimits(
+  Element& s, Index i, std::unique_ptr<Memory>& memory) {
+  i = parseMemoryIndex(s, i, memory);
   if (i == s.size()) {
     throw ParseException("missing memory limits", s.line, s.col);
   }
   auto initElem = s[i++];
-  wasm.memory.initial = getAddress(initElem);
-  if (!wasm.memory.is64()) {
-    checkAddress(wasm.memory.initial, "excessive memory init", initElem);
+  memory->initial = getAddress(initElem);
+  if (!memory->is64()) {
+    checkAddress(memory->initial, "excessive memory init", initElem);
   }
   if (i == s.size()) {
-    wasm.memory.max = Memory::kUnlimitedSize;
+    memory->max = Memory::kUnlimitedSize;
   } else {
     auto maxElem = s[i++];
-    wasm.memory.max = getAddress(maxElem);
-    if (!wasm.memory.is64() && wasm.memory.max > Memory::kMaxSize32) {
+    memory->max = getAddress(maxElem);
+    if (!memory->is64() && memory->max > Memory::kMaxSize32) {
       throw ParseException(
         "total memory must be <= 4GB", maxElem->line, maxElem->col);
     }
@@ -2995,23 +3183,24 @@ Index SExpressionWasmBuilder::parseMemoryLimits(Element& s, Index i) {
 }
 
 void SExpressionWasmBuilder::parseMemory(Element& s, bool preParseImport) {
-  if (wasm.memory.exists) {
-    throw ParseException("too many memories", s.line, s.col);
-  }
-  wasm.memory.exists = true;
-  wasm.memory.shared = false;
+  auto memory = make_unique<Memory>();
+  memory->shared = false;
   Index i = 1;
   if (s[i]->dollared()) {
-    wasm.memory.setExplicitName(s[i++]->str());
+    memory->setExplicitName(s[i++]->str());
+  } else {
+    memory->name = Name::fromInt(memoryCounter++);
   }
-  i = parseMemoryIndex(s, i);
+  memoryNames.push_back(memory->name);
+
+  i = parseMemoryIndex(s, i, memory);
   Name importModule, importBase;
   if (s[i]->isList()) {
     auto& inner = *s[i];
     if (elementStartsWith(inner, EXPORT)) {
       auto ex = make_unique<Export>();
       ex->name = inner[1]->str();
-      ex->value = wasm.memory.name;
+      ex->value = memory->name;
       ex->kind = ExternalKind::Memory;
       if (wasm.getExportOrNull(ex->name)) {
         throw ParseException("duplicate export", inner.line, inner.col);
@@ -3019,32 +3208,36 @@ void SExpressionWasmBuilder::parseMemory(Element& s, bool preParseImport) {
       wasm.addExport(ex.release());
       i++;
     } else if (elementStartsWith(inner, IMPORT)) {
-      wasm.memory.module = inner[1]->str();
-      wasm.memory.base = inner[2]->str();
+      memory->module = inner[1]->str();
+      memory->base = inner[2]->str();
       i++;
     } else if (elementStartsWith(inner, SHARED)) {
-      wasm.memory.shared = true;
-      parseMemoryLimits(inner, 1);
+      memory->shared = true;
+      parseMemoryLimits(inner, 1, memory);
       i++;
     } else {
       if (!(inner.size() > 0 ? inner[0]->str() != IMPORT : true)) {
         throw ParseException("bad import ending", inner.line, inner.col);
       }
       // (memory (data ..)) format
-      auto j = parseMemoryIndex(inner, 1);
+      auto j = parseMemoryIndex(inner, 1, memory);
       auto offset = allocator.alloc<Const>();
-      if (wasm.memory.is64()) {
+      if (memory->is64()) {
         offset->set(Literal(int64_t(0)));
       } else {
         offset->set(Literal(int32_t(0)));
       }
-      parseInnerData(inner, j, {}, offset, false);
-      wasm.memory.initial = wasm.memory.segments[0].data.size();
+      auto seg = Builder::makeDataSegment(
+        Name::fromInt(dataCounter++), memory->name, false, offset);
+      parseInnerData(inner, j, seg);
+      memory->initial = seg->data.size();
+      wasm.addDataSegment(std::move(seg));
+      wasm.addMemory(std::move(memory));
       return;
     }
   }
-  if (!wasm.memory.shared) {
-    i = parseMemoryLimits(s, i);
+  if (!memory->shared) {
+    i = parseMemoryLimits(s, i, memory);
   }
 
   // Parse memory initializers.
@@ -3057,51 +3250,62 @@ void SExpressionWasmBuilder::parseMemory(Element& s, bool preParseImport) {
     } else {
       auto offsetElem = curr[j++];
       offsetValue = getAddress(offsetElem);
-      if (!wasm.memory.is64()) {
+      if (!memory->is64()) {
         checkAddress(offsetValue, "excessive memory offset", offsetElem);
       }
     }
-    const char* input = curr[j]->c_str();
+    std::string_view input = curr[j]->str().str;
     auto* offset = allocator.alloc<Const>();
-    if (wasm.memory.is64()) {
+    if (memory->is64()) {
       offset->type = Type::i64;
       offset->value = Literal(offsetValue);
     } else {
       offset->type = Type::i32;
       offset->value = Literal(int32_t(offsetValue));
     }
-    if (auto size = strlen(input)) {
+    if (input.size()) {
       std::vector<char> data;
-      stringToBinary(input, size, data);
-      wasm.memory.segments.emplace_back(offset, data.data(), data.size());
+      stringToBinary(*curr[j], input, data);
+      auto segment = Builder::makeDataSegment(Name::fromInt(dataCounter++),
+                                              memory->name,
+                                              false,
+                                              offset,
+                                              data.data(),
+                                              data.size());
+      segment->hasExplicitName = false;
+      wasm.addDataSegment(std::move(segment));
     } else {
-      wasm.memory.segments.emplace_back(offset, "", 0);
+      auto segment = Builder::makeDataSegment(
+        Name::fromInt(dataCounter++), memory->name, false, offset);
+      segment->hasExplicitName = false;
+      wasm.addDataSegment(std::move(segment));
     }
     i++;
   }
+  wasm.addMemory(std::move(memory));
 }
 
 void SExpressionWasmBuilder::parseData(Element& s) {
-  if (!wasm.memory.exists) {
-    throw ParseException("data but no memory", s.line, s.col);
-  }
+  Index i = 1;
+  Name name = Name::fromInt(dataCounter++);
+  bool hasExplicitName = false;
+  Name memory;
   bool isPassive = true;
   Expression* offset = nullptr;
-  Index i = 1;
-  Name name;
 
   if (s[i]->isStr() && s[i]->dollared()) {
     name = s[i++]->str();
+    hasExplicitName = true;
   }
 
   if (s[i]->isList()) {
     // Optional (memory <memoryidx>)
     if (elementStartsWith(s[i], MEMORY)) {
-      // TODO: we're just skipping memory since we have only one. Assign the
-      //  memory name to the segment when we support multiple memories.
-      i += 1;
+      auto& inner = *s[i++];
+      memory = getMemoryName(*inner[1]);
+    } else {
+      memory = getMemoryNameAtIdx(0);
     }
-
     // Offset expression (offset (<expr>)) | (<expr>)
     auto& inner = *s[i++];
     if (elementStartsWith(inner, OFFSET)) {
@@ -3112,20 +3316,22 @@ void SExpressionWasmBuilder::parseData(Element& s) {
     isPassive = false;
   }
 
-  parseInnerData(s, i, name, offset, isPassive);
+  auto seg = Builder::makeDataSegment(name, memory, isPassive, offset);
+  seg->hasExplicitName = hasExplicitName;
+  parseInnerData(s, i, seg);
+  wasm.addDataSegment(std::move(seg));
 }
 
-void SExpressionWasmBuilder::parseInnerData(
-  Element& s, Index i, Name name, Expression* offset, bool isPassive) {
+void SExpressionWasmBuilder::parseInnerData(Element& s,
+                                            Index i,
+                                            std::unique_ptr<DataSegment>& seg) {
   std::vector<char> data;
   while (i < s.size()) {
-    const char* input = s[i++]->c_str();
-    if (auto size = strlen(input)) {
-      stringToBinary(input, size, data);
-    }
+    std::string_view input = s[i++]->str().str;
+    stringToBinary(s, input, data);
   }
-  wasm.memory.segments.emplace_back(
-    name, isPassive, offset, data.data(), data.size());
+  seg->data.resize(data.size());
+  std::copy_n(data.data(), data.size(), seg->data.begin());
 }
 
 void SExpressionWasmBuilder::parseExport(Element& s) {
@@ -3172,10 +3378,6 @@ void SExpressionWasmBuilder::parseImport(Element& s) {
       kind = ExternalKind::Function;
     } else if (elementStartsWith(*s[3], MEMORY)) {
       kind = ExternalKind::Memory;
-      if (wasm.memory.exists) {
-        throw ParseException("more than one memory", s[3]->line, s[3]->col);
-      }
-      wasm.memory.exists = true;
     } else if (elementStartsWith(*s[3], TABLE)) {
       kind = ExternalKind::Table;
     } else if (elementStartsWith(*s[3], GLOBAL)) {
@@ -3200,8 +3402,7 @@ void SExpressionWasmBuilder::parseImport(Element& s) {
       name = Name("fimport$" + std::to_string(functionCounter++));
       functionNames.push_back(name);
     } else if (kind == ExternalKind::Global) {
-      name = Name("gimport$" + std::to_string(globalCounter++));
-      globalNames.push_back(name);
+      // Handled in `parseGlobal`.
     } else if (kind == ExternalKind::Memory) {
       name = Name("mimport$" + std::to_string(memoryCounter++));
     } else if (kind == ExternalKind::Table) {
@@ -3239,25 +3440,11 @@ void SExpressionWasmBuilder::parseImport(Element& s) {
     functionTypes[name] = func->type;
     wasm.addFunction(func.release());
   } else if (kind == ExternalKind::Global) {
-    Type type;
-    bool mutable_ = false;
-    if (inner[j]->isStr()) {
-      type = stringToType(inner[j++]->str());
-    } else {
-      auto& inner2 = *inner[j++];
-      if (inner2[0]->str() != MUT) {
-        throw ParseException("expected mut", inner2.line, inner2.col);
-      }
-      type = stringToType(inner2[1]->str());
-      mutable_ = true;
-    }
-    auto global = make_unique<Global>();
-    global->setName(name, hasExplicitName);
+    parseGlobal(inner, true);
+    j++;
+    auto& global = wasm.globals.back();
     global->module = module;
     global->base = base;
-    global->type = type;
-    global->mutable_ = mutable_;
-    wasm.addGlobal(global.release());
   } else if (kind == ExternalKind::Table) {
     auto table = make_unique<Table>();
     table->setName(name, hasExplicitName);
@@ -3283,20 +3470,25 @@ void SExpressionWasmBuilder::parseImport(Element& s) {
     j++; // funcref
     // ends with the table element type
   } else if (kind == ExternalKind::Memory) {
-    wasm.memory.setName(name, hasExplicitName);
-    wasm.memory.module = module;
-    wasm.memory.base = base;
+    auto memory = make_unique<Memory>();
+    memory->setName(name, hasExplicitName);
+    memory->module = module;
+    memory->base = base;
+    memoryNames.push_back(name);
+
     if (inner[j]->isList()) {
       auto& limits = *inner[j];
       if (!elementStartsWith(limits, SHARED)) {
         throw ParseException(
           "bad memory limit declaration", inner[j]->line, inner[j]->col);
       }
-      wasm.memory.shared = true;
-      j = parseMemoryLimits(limits, 1);
+      memory->shared = true;
+      j = parseMemoryLimits(limits, 1, memory);
     } else {
-      j = parseMemoryLimits(inner, j);
+      j = parseMemoryLimits(inner, j, memory);
     }
+
+    wasm.addMemory(std::move(memory));
   } else if (kind == ExternalKind::Tag) {
     auto tag = make_unique<Tag>();
     HeapType tagType;
@@ -3318,6 +3510,8 @@ void SExpressionWasmBuilder::parseGlobal(Element& s, bool preParseImport) {
   size_t i = 1;
   if (s[i]->dollared() && !(s[i]->isStr() && isType(s[i]->str()))) {
     global->setExplicitName(s[i++]->str());
+  } else if (preParseImport) {
+    global->name = Name("gimport$" + std::to_string(globalCounter));
   } else {
     global->name = Name::fromInt(globalCounter);
   }
@@ -3377,13 +3571,10 @@ void SExpressionWasmBuilder::parseGlobal(Element& s, bool preParseImport) {
     wasm.addGlobal(im.release());
     return;
   }
-  if (preParseImport) {
-    throw ParseException("preParseImport in global", s.line, s.col);
-  }
   global->type = type;
   if (i < s.size()) {
     global->init = parseExpression(s[i++]);
-  } else {
+  } else if (!preParseImport) {
     throw ParseException("global without init", s.line, s.col);
   }
   global->mutable_ = mutable_;
@@ -3433,12 +3624,12 @@ void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) {
 
   bool hasExplicitLimit = false;
 
-  if (s[i]->isStr() && String::isNumber(s[i]->c_str())) {
-    table->initial = atoi(s[i++]->c_str());
+  if (s[i]->isStr() && String::isNumber(s[i]->toString())) {
+    table->initial = parseIndex(*s[i++]);
     hasExplicitLimit = true;
   }
-  if (s[i]->isStr() && String::isNumber(s[i]->c_str())) {
-    table->max = atoi(s[i++]->c_str());
+  if (s[i]->isStr() && String::isNumber(s[i]->toString())) {
+    table->max = parseIndex(*s[i++]);
   }
 
   table->type = elementToType(*s[i++]);
@@ -3602,7 +3793,7 @@ HeapType SExpressionWasmBuilder::parseHeapType(Element& s) {
   if (s.isStr()) {
     // It's a string.
     if (s.dollared()) {
-      auto it = typeIndices.find(s.str().str);
+      auto it = typeIndices.find(s.toString());
       if (it == typeIndices.end()) {
         throw ParseException("unknown dollared function type", s.line, s.col);
       }
@@ -3610,15 +3801,15 @@ HeapType SExpressionWasmBuilder::parseHeapType(Element& s) {
     } else {
       // It may be a numerical index, or it may be a built-in type name like
       // "i31".
-      auto* str = s.str().c_str();
+      auto str = s.toString();
       if (String::isNumber(str)) {
-        size_t offset = atoi(str);
+        size_t offset = parseIndex(s);
         if (offset >= types.size()) {
           throw ParseException("unknown indexed function type", s.line, s.col);
         }
         return types[offset];
       }
-      return stringToHeapType(str, /* prefix = */ false);
+      return stringToHeapType(s.str(), /* prefix = */ false);
     }
   }
   throw ParseException("invalid heap type", s.line, s.col);
@@ -3684,6 +3875,7 @@ void SExpressionWasmBuilder::parseTag(Element& s, bool preParseImport) {
     }
     ex->value = tag->name;
     ex->kind = ExternalKind::Tag;
+    wasm.addExport(ex.release());
   }
 
   // Parse typeuse
@@ -3705,7 +3897,7 @@ void SExpressionWasmBuilder::validateHeapTypeUsingChild(Expression* child,
   if (child->type == Type::unreachable) {
     return;
   }
-  if ((!child->type.isRef() && !child->type.isRtt()) ||
+  if (!child->type.isRef() ||
       !HeapType::isSubType(child->type.getHeapType(), heapType)) {
     throw ParseException("bad heap type: expected " + heapType.toString() +
                            " but found " + child->type.toString(),
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 8588a04..daeac60 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -187,11 +187,6 @@ void BinaryInstWriter::visitLoad(Load* curr) {
         // the pointer is unreachable, so we are never reached; just don't emit
         // a load
         return;
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref:
       case Type::none:
         WASM_UNREACHABLE("unexpected type");
     }
@@ -239,7 +234,7 @@ void BinaryInstWriter::visitLoad(Load* curr) {
         WASM_UNREACHABLE("unexpected type");
     }
   }
-  emitMemoryAccess(curr->align, curr->bytes, curr->offset);
+  emitMemoryAccess(curr->align, curr->bytes, curr->offset, curr->memory);
 }
 
 void BinaryInstWriter::visitStore(Store* curr) {
@@ -290,11 +285,6 @@ void BinaryInstWriter::visitStore(Store* curr) {
         o << int8_t(BinaryConsts::SIMDPrefix)
           << U32LEB(BinaryConsts::V128Store);
         break;
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref:
       case Type::none:
       case Type::unreachable:
         WASM_UNREACHABLE("unexpected type");
@@ -341,7 +331,7 @@ void BinaryInstWriter::visitStore(Store* curr) {
         WASM_UNREACHABLE("unexpected type");
     }
   }
-  emitMemoryAccess(curr->align, curr->bytes, curr->offset);
+  emitMemoryAccess(curr->align, curr->bytes, curr->offset, curr->memory);
 }
 
 void BinaryInstWriter::visitAtomicRMW(AtomicRMW* curr) {
@@ -400,7 +390,7 @@ void BinaryInstWriter::visitAtomicRMW(AtomicRMW* curr) {
   }
 #undef CASE_FOR_OP
 
-  emitMemoryAccess(curr->bytes, curr->bytes, curr->offset);
+  emitMemoryAccess(curr->bytes, curr->bytes, curr->offset, curr->memory);
 }
 
 void BinaryInstWriter::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
@@ -442,7 +432,7 @@ void BinaryInstWriter::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
     default:
       WASM_UNREACHABLE("unexpected type");
   }
-  emitMemoryAccess(curr->bytes, curr->bytes, curr->offset);
+  emitMemoryAccess(curr->bytes, curr->bytes, curr->offset, curr->memory);
 }
 
 void BinaryInstWriter::visitAtomicWait(AtomicWait* curr) {
@@ -450,12 +440,12 @@ void BinaryInstWriter::visitAtomicWait(AtomicWait* curr) {
   switch (curr->expectedType.getBasic()) {
     case Type::i32: {
       o << int8_t(BinaryConsts::I32AtomicWait);
-      emitMemoryAccess(4, 4, curr->offset);
+      emitMemoryAccess(4, 4, curr->offset, curr->memory);
       break;
     }
     case Type::i64: {
       o << int8_t(BinaryConsts::I64AtomicWait);
-      emitMemoryAccess(8, 8, curr->offset);
+      emitMemoryAccess(8, 8, curr->offset, curr->memory);
       break;
     }
     default:
@@ -465,7 +455,7 @@ void BinaryInstWriter::visitAtomicWait(AtomicWait* curr) {
 
 void BinaryInstWriter::visitAtomicNotify(AtomicNotify* curr) {
   o << int8_t(BinaryConsts::AtomicPrefix) << int8_t(BinaryConsts::AtomicNotify);
-  emitMemoryAccess(4, 4, curr->offset);
+  emitMemoryAccess(4, 4, curr->offset, curr->memory);
 }
 
 void BinaryInstWriter::visitAtomicFence(AtomicFence* curr) {
@@ -570,9 +560,6 @@ void BinaryInstWriter::visitSIMDTernary(SIMDTernary* curr) {
     case DotI8x16I7x16AddSToVecI32x4:
       o << U32LEB(BinaryConsts::I32x4DotI8x16I7x16AddS);
       break;
-    case DotI8x16I7x16AddUToVecI32x4:
-      o << U32LEB(BinaryConsts::I32x4DotI8x16I7x16AddU);
-      break;
   }
 }
 
@@ -659,7 +646,8 @@ void BinaryInstWriter::visitSIMDLoad(SIMDLoad* curr) {
       break;
   }
   assert(curr->align);
-  emitMemoryAccess(curr->align, /*(unused) bytes=*/0, curr->offset);
+  emitMemoryAccess(
+    curr->align, /*(unused) bytes=*/0, curr->offset, curr->memory);
 }
 
 void BinaryInstWriter::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
@@ -691,14 +679,15 @@ void BinaryInstWriter::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
       break;
   }
   assert(curr->align);
-  emitMemoryAccess(curr->align, /*(unused) bytes=*/0, curr->offset);
+  emitMemoryAccess(
+    curr->align, /*(unused) bytes=*/0, curr->offset, curr->memory);
   o << curr->index;
 }
 
 void BinaryInstWriter::visitMemoryInit(MemoryInit* curr) {
   o << int8_t(BinaryConsts::MiscPrefix);
   o << U32LEB(BinaryConsts::MemoryInit);
-  o << U32LEB(curr->segment) << int8_t(0);
+  o << U32LEB(curr->segment) << int8_t(parent.getMemoryIndex(curr->memory));
 }
 
 void BinaryInstWriter::visitDataDrop(DataDrop* curr) {
@@ -710,13 +699,14 @@ void BinaryInstWriter::visitDataDrop(DataDrop* curr) {
 void BinaryInstWriter::visitMemoryCopy(MemoryCopy* curr) {
   o << int8_t(BinaryConsts::MiscPrefix);
   o << U32LEB(BinaryConsts::MemoryCopy);
-  o << int8_t(0) << int8_t(0);
+  o << int8_t(parent.getMemoryIndex(curr->destMemory))
+    << int8_t(parent.getMemoryIndex(curr->sourceMemory));
 }
 
 void BinaryInstWriter::visitMemoryFill(MemoryFill* curr) {
   o << int8_t(BinaryConsts::MiscPrefix);
   o << U32LEB(BinaryConsts::MemoryFill);
-  o << int8_t(0);
+  o << int8_t(parent.getMemoryIndex(curr->memory));
 }
 
 void BinaryInstWriter::visitConst(Const* curr) {
@@ -745,11 +735,6 @@ void BinaryInstWriter::visitConst(Const* curr) {
       }
       break;
     }
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
     case Type::unreachable:
       WASM_UNREACHABLE("unexpected type");
@@ -1853,10 +1838,6 @@ void BinaryInstWriter::visitBinary(Binary* curr) {
       o << int8_t(BinaryConsts::SIMDPrefix)
         << U32LEB(BinaryConsts::I16x8DotI8x16I7x16S);
       break;
-    case DotI8x16I7x16UToVecI16x8:
-      o << int8_t(BinaryConsts::SIMDPrefix)
-        << U32LEB(BinaryConsts::I16x8DotI8x16I7x16U);
-      break;
 
     case InvalidBinary:
       WASM_UNREACHABLE("invalid binary op");
@@ -1881,12 +1862,12 @@ void BinaryInstWriter::visitReturn(Return* curr) {
 
 void BinaryInstWriter::visitMemorySize(MemorySize* curr) {
   o << int8_t(BinaryConsts::MemorySize);
-  o << U32LEB(0); // Reserved flags field
+  o << U32LEB(parent.getMemoryIndex(curr->memory));
 }
 
 void BinaryInstWriter::visitMemoryGrow(MemoryGrow* curr) {
   o << int8_t(BinaryConsts::MemoryGrow);
-  o << U32LEB(0); // Reserved flags field
+  o << U32LEB(parent.getMemoryIndex(curr->memory));
 }
 
 void BinaryInstWriter::visitRefNull(RefNull* curr) {
@@ -2032,32 +2013,30 @@ void BinaryInstWriter::visitI31Get(I31Get* curr) {
 }
 
 void BinaryInstWriter::visitCallRef(CallRef* curr) {
+  assert(curr->target->type != Type::unreachable);
+  if (curr->target->type.isNull()) {
+    emitUnreachable();
+    return;
+  }
   o << int8_t(curr->isReturn ? BinaryConsts::RetCallRef
                              : BinaryConsts::CallRef);
+  parent.writeIndexedHeapType(curr->target->type.getHeapType());
 }
 
 void BinaryInstWriter::visitRefTest(RefTest* curr) {
   o << int8_t(BinaryConsts::GCPrefix);
-  if (curr->rtt) {
-    o << U32LEB(BinaryConsts::RefTest);
-  } else {
-    o << U32LEB(BinaryConsts::RefTestStatic);
-    parent.writeIndexedHeapType(curr->intendedType);
-  }
+  o << U32LEB(BinaryConsts::RefTestStatic);
+  parent.writeIndexedHeapType(curr->intendedType);
 }
 
 void BinaryInstWriter::visitRefCast(RefCast* curr) {
   o << int8_t(BinaryConsts::GCPrefix);
-  if (curr->rtt) {
-    o << U32LEB(BinaryConsts::RefCast);
+  if (curr->safety == RefCast::Unsafe) {
+    o << U32LEB(BinaryConsts::RefCastNopStatic);
   } else {
-    if (curr->safety == RefCast::Unsafe) {
-      o << U32LEB(BinaryConsts::RefCastNopStatic);
-    } else {
-      o << U32LEB(BinaryConsts::RefCastStatic);
-    }
-    parent.writeIndexedHeapType(curr->intendedType);
+    o << U32LEB(BinaryConsts::RefCastStatic);
   }
+  parent.writeIndexedHeapType(curr->intendedType);
 }
 
 void BinaryInstWriter::visitBrOn(BrOn* curr) {
@@ -2070,19 +2049,11 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) {
       break;
     case BrOnCast:
       o << int8_t(BinaryConsts::GCPrefix);
-      if (curr->rtt) {
-        o << U32LEB(BinaryConsts::BrOnCast);
-      } else {
-        o << U32LEB(BinaryConsts::BrOnCastStatic);
-      }
+      o << U32LEB(BinaryConsts::BrOnCastStatic);
       break;
     case BrOnCastFail:
       o << int8_t(BinaryConsts::GCPrefix);
-      if (curr->rtt) {
-        o << U32LEB(BinaryConsts::BrOnCastFail);
-      } else {
-        o << U32LEB(BinaryConsts::BrOnCastStaticFail);
-      }
+      o << U32LEB(BinaryConsts::BrOnCastStaticFail);
       break;
     case BrOnFunc:
       o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnFunc);
@@ -2106,41 +2077,26 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) {
       WASM_UNREACHABLE("invalid br_on_*");
   }
   o << U32LEB(getBreakIndex(curr->name));
-  if ((curr->op == BrOnCast || curr->op == BrOnCastFail) && !curr->rtt) {
+  if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
     parent.writeIndexedHeapType(curr->intendedType);
   }
 }
 
-void BinaryInstWriter::visitRttCanon(RttCanon* curr) {
-  o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::RttCanon);
-  parent.writeIndexedHeapType(curr->type.getRtt().heapType);
-}
-
-void BinaryInstWriter::visitRttSub(RttSub* curr) {
-  o << int8_t(BinaryConsts::GCPrefix);
-  o << U32LEB(curr->fresh ? BinaryConsts::RttFreshSub : BinaryConsts::RttSub);
-  parent.writeIndexedHeapType(curr->type.getRtt().heapType);
-}
-
 void BinaryInstWriter::visitStructNew(StructNew* curr) {
   o << int8_t(BinaryConsts::GCPrefix);
-  if (curr->rtt) {
-    if (curr->isWithDefault()) {
-      o << U32LEB(BinaryConsts::StructNewDefaultWithRtt);
-    } else {
-      o << U32LEB(BinaryConsts::StructNewWithRtt);
-    }
+  if (curr->isWithDefault()) {
+    o << U32LEB(BinaryConsts::StructNewDefault);
   } else {
-    if (curr->isWithDefault()) {
-      o << U32LEB(BinaryConsts::StructNewDefault);
-    } else {
-      o << U32LEB(BinaryConsts::StructNew);
-    }
+    o << U32LEB(BinaryConsts::StructNew);
   }
   parent.writeIndexedHeapType(curr->type.getHeapType());
 }
 
 void BinaryInstWriter::visitStructGet(StructGet* curr) {
+  if (curr->ref->type.isNull()) {
+    emitUnreachable();
+    return;
+  }
   const auto& heapType = curr->ref->type.getHeapType();
   const auto& field = heapType.getStruct().fields[curr->index];
   int8_t op;
@@ -2157,6 +2113,10 @@ void BinaryInstWriter::visitStructGet(StructGet* curr) {
 }
 
 void BinaryInstWriter::visitStructSet(StructSet* curr) {
+  if (curr->ref->type.isNull()) {
+    emitUnreachable();
+    return;
+  }
   o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StructSet);
   parent.writeIndexedHeapType(curr->ref->type.getHeapType());
   o << U32LEB(curr->index);
@@ -2164,34 +2124,42 @@ void BinaryInstWriter::visitStructSet(StructSet* curr) {
 
 void BinaryInstWriter::visitArrayNew(ArrayNew* curr) {
   o << int8_t(BinaryConsts::GCPrefix);
-  if (curr->rtt) {
-    if (curr->isWithDefault()) {
-      o << U32LEB(BinaryConsts::ArrayNewDefaultWithRtt);
-    } else {
-      o << U32LEB(BinaryConsts::ArrayNewWithRtt);
-    }
+  if (curr->isWithDefault()) {
+    o << U32LEB(BinaryConsts::ArrayNewDefault);
   } else {
-    if (curr->isWithDefault()) {
-      o << U32LEB(BinaryConsts::ArrayNewDefault);
-    } else {
-      o << U32LEB(BinaryConsts::ArrayNew);
-    }
+    o << U32LEB(BinaryConsts::ArrayNew);
   }
   parent.writeIndexedHeapType(curr->type.getHeapType());
 }
 
-void BinaryInstWriter::visitArrayInit(ArrayInit* curr) {
+void BinaryInstWriter::visitArrayNewSeg(ArrayNewSeg* curr) {
   o << int8_t(BinaryConsts::GCPrefix);
-  if (curr->rtt) {
-    o << U32LEB(BinaryConsts::ArrayInit);
-  } else {
-    o << U32LEB(BinaryConsts::ArrayInitStatic);
+  switch (curr->op) {
+    case NewData:
+      o << U32LEB(BinaryConsts::ArrayNewData);
+      break;
+    case NewElem:
+      o << U32LEB(BinaryConsts::ArrayNewElem);
+      break;
+    default:
+      WASM_UNREACHABLE("unexpected op");
   }
   parent.writeIndexedHeapType(curr->type.getHeapType());
+  o << U32LEB(curr->segment);
+}
+
+void BinaryInstWriter::visitArrayInit(ArrayInit* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  o << U32LEB(BinaryConsts::ArrayInitStatic);
+  parent.writeIndexedHeapType(curr->type.getHeapType());
   o << U32LEB(curr->values.size());
 }
 
 void BinaryInstWriter::visitArrayGet(ArrayGet* curr) {
+  if (curr->ref->type.isNull()) {
+    emitUnreachable();
+    return;
+  }
   auto heapType = curr->ref->type.getHeapType();
   const auto& field = heapType.getArray().element;
   int8_t op;
@@ -2207,16 +2175,23 @@ void BinaryInstWriter::visitArrayGet(ArrayGet* curr) {
 }
 
 void BinaryInstWriter::visitArraySet(ArraySet* curr) {
+  if (curr->ref->type.isNull()) {
+    emitUnreachable();
+    return;
+  }
   o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArraySet);
   parent.writeIndexedHeapType(curr->ref->type.getHeapType());
 }
 
 void BinaryInstWriter::visitArrayLen(ArrayLen* curr) {
   o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayLen);
-  parent.writeIndexedHeapType(curr->ref->type.getHeapType());
 }
 
 void BinaryInstWriter::visitArrayCopy(ArrayCopy* curr) {
+  if (curr->srcRef->type.isNull() || curr->destRef->type.isNull()) {
+    emitUnreachable();
+    return;
+  }
   o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayCopy);
   parent.writeIndexedHeapType(curr->destRef->type.getHeapType());
   parent.writeIndexedHeapType(curr->srcRef->type.getHeapType());
@@ -2236,11 +2211,197 @@ void BinaryInstWriter::visitRefAs(RefAs* curr) {
     case RefAsI31:
       o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::RefAsI31);
       break;
+    case ExternInternalize:
+      o << int8_t(BinaryConsts::GCPrefix)
+        << U32LEB(BinaryConsts::ExternInternalize);
+      break;
+    case ExternExternalize:
+      o << int8_t(BinaryConsts::GCPrefix)
+        << U32LEB(BinaryConsts::ExternExternalize);
+      break;
     default:
       WASM_UNREACHABLE("invalid ref.as_*");
   }
 }
 
+void BinaryInstWriter::visitStringNew(StringNew* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  switch (curr->op) {
+    case StringNewUTF8:
+      o << U32LEB(BinaryConsts::StringNewWTF8);
+      o << int8_t(0); // Memory index.
+      o << U32LEB(BinaryConsts::StringPolicy::UTF8);
+      break;
+    case StringNewWTF8:
+      o << U32LEB(BinaryConsts::StringNewWTF8);
+      o << int8_t(0); // Memory index.
+      o << U32LEB(BinaryConsts::StringPolicy::WTF8);
+      break;
+    case StringNewReplace:
+      o << U32LEB(BinaryConsts::StringNewWTF8);
+      o << int8_t(0); // Memory index.
+      o << U32LEB(BinaryConsts::StringPolicy::Replace);
+      break;
+    case StringNewWTF16:
+      o << U32LEB(BinaryConsts::StringNewWTF16);
+      o << int8_t(0); // Memory index.
+      break;
+    case StringNewUTF8Array:
+      o << U32LEB(BinaryConsts::StringNewWTF8Array)
+        << U32LEB(BinaryConsts::StringPolicy::UTF8);
+      break;
+    case StringNewWTF8Array:
+      o << U32LEB(BinaryConsts::StringNewWTF8Array)
+        << U32LEB(BinaryConsts::StringPolicy::WTF8);
+      break;
+    case StringNewReplaceArray:
+      o << U32LEB(BinaryConsts::StringNewWTF8Array)
+        << U32LEB(BinaryConsts::StringPolicy::Replace);
+      break;
+    case StringNewWTF16Array:
+      o << U32LEB(BinaryConsts::StringNewWTF16Array);
+      break;
+    default:
+      WASM_UNREACHABLE("invalid string.new*");
+  }
+}
+
+void BinaryInstWriter::visitStringConst(StringConst* curr) {
+  o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringConst)
+    << U32LEB(parent.getStringIndex(curr->string));
+}
+
+void BinaryInstWriter::visitStringMeasure(StringMeasure* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  switch (curr->op) {
+    case StringMeasureUTF8:
+      o << U32LEB(BinaryConsts::StringMeasureWTF8)
+        << U32LEB(BinaryConsts::StringPolicy::UTF8);
+      break;
+    case StringMeasureWTF8:
+      o << U32LEB(BinaryConsts::StringMeasureWTF8)
+        << U32LEB(BinaryConsts::StringPolicy::WTF8);
+      break;
+    case StringMeasureWTF16:
+      o << U32LEB(BinaryConsts::StringMeasureWTF16);
+      break;
+    case StringMeasureIsUSV:
+      o << U32LEB(BinaryConsts::StringIsUSV);
+      break;
+    case StringMeasureWTF16View:
+      o << U32LEB(BinaryConsts::StringViewWTF16Length);
+      break;
+    default:
+      WASM_UNREACHABLE("invalid string.new*");
+  }
+}
+
+void BinaryInstWriter::visitStringEncode(StringEncode* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  switch (curr->op) {
+    case StringEncodeUTF8:
+      o << U32LEB(BinaryConsts::StringEncodeWTF8);
+      o << int8_t(0); // Memory index.
+      o << U32LEB(BinaryConsts::StringPolicy::UTF8);
+      break;
+    case StringEncodeWTF8:
+      o << U32LEB(BinaryConsts::StringEncodeWTF8);
+      o << int8_t(0); // Memory index.
+      o << U32LEB(BinaryConsts::StringPolicy::WTF8);
+      break;
+    case StringEncodeWTF16:
+      o << U32LEB(BinaryConsts::StringEncodeWTF16);
+      o << int8_t(0); // Memory index.
+      break;
+    case StringEncodeUTF8Array:
+      o << U32LEB(BinaryConsts::StringEncodeWTF8Array);
+      o << U32LEB(BinaryConsts::StringPolicy::UTF8);
+      break;
+    case StringEncodeWTF8Array:
+      o << U32LEB(BinaryConsts::StringEncodeWTF8Array)
+        << U32LEB(BinaryConsts::StringPolicy::WTF8);
+      break;
+    case StringEncodeWTF16Array:
+      o << U32LEB(BinaryConsts::StringEncodeWTF16Array);
+      break;
+    default:
+      WASM_UNREACHABLE("invalid string.new*");
+  }
+}
+
+void BinaryInstWriter::visitStringConcat(StringConcat* curr) {
+  o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringConcat);
+}
+
+void BinaryInstWriter::visitStringEq(StringEq* curr) {
+  o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringEq);
+}
+
+void BinaryInstWriter::visitStringAs(StringAs* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  switch (curr->op) {
+    case StringAsWTF8:
+      o << U32LEB(BinaryConsts::StringAsWTF8);
+      break;
+    case StringAsWTF16:
+      o << U32LEB(BinaryConsts::StringAsWTF16);
+      break;
+    case StringAsIter:
+      o << U32LEB(BinaryConsts::StringAsIter);
+      break;
+    default:
+      WASM_UNREACHABLE("invalid string.as*");
+  }
+}
+
+void BinaryInstWriter::visitStringWTF8Advance(StringWTF8Advance* curr) {
+  o << int8_t(BinaryConsts::GCPrefix)
+    << U32LEB(BinaryConsts::StringViewWTF8Advance);
+}
+
+void BinaryInstWriter::visitStringWTF16Get(StringWTF16Get* curr) {
+  o << int8_t(BinaryConsts::GCPrefix)
+    << U32LEB(BinaryConsts::StringViewWTF16GetCodePoint);
+}
+
+void BinaryInstWriter::visitStringIterNext(StringIterNext* curr) {
+  o << int8_t(BinaryConsts::GCPrefix)
+    << U32LEB(BinaryConsts::StringViewIterNext);
+}
+
+void BinaryInstWriter::visitStringIterMove(StringIterMove* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  switch (curr->op) {
+    case StringIterMoveAdvance:
+      o << U32LEB(BinaryConsts::StringViewIterAdvance);
+      break;
+    case StringIterMoveRewind:
+      o << U32LEB(BinaryConsts::StringViewIterRewind);
+      break;
+    default:
+      WASM_UNREACHABLE("invalid string.move*");
+  }
+}
+
+void BinaryInstWriter::visitStringSliceWTF(StringSliceWTF* curr) {
+  o << int8_t(BinaryConsts::GCPrefix);
+  switch (curr->op) {
+    case StringSliceWTF8:
+      o << U32LEB(BinaryConsts::StringViewWTF8Slice);
+      break;
+    case StringSliceWTF16:
+      o << U32LEB(BinaryConsts::StringViewWTF16Slice);
+      break;
+    default:
+      WASM_UNREACHABLE("invalid string.move*");
+  }
+}
+
+void BinaryInstWriter::visitStringSliceIter(StringSliceIter* curr) {
+  o << int8_t(BinaryConsts::GCPrefix)
+    << U32LEB(BinaryConsts::StringViewIterSlice);
+}
+
 void BinaryInstWriter::emitScopeEnd(Expression* curr) {
   assert(!breakStack.empty());
   breakStack.pop_back();
@@ -2287,6 +2448,29 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() {
     }
   }
   countScratchLocals();
+
+  if (parent.getModule()->features.hasReferenceTypes()) {
+    // Sort local types in a way that keeps all MVP types together and all
+    // reference types together. E.g. it is helpful to avoid a block of i32s in
+    // between blocks of different reference types, since clearing out reference
+    // types may require different work.
+    //
+    // See https://github.com/WebAssembly/binaryen/issues/4773
+    //
+    // In order to decide whether to put MVP types or reference types first,
+    // look at the type of the first local. In an optimized binary we will have
+    // sorted the locals by frequency of uses, so this way we'll keep the most
+    // commonly-used local at the top, which should work well in many cases.
+    bool refsFirst = !localTypes.empty() && localTypes[0].isRef();
+    std::stable_sort(localTypes.begin(), localTypes.end(), [&](Type a, Type b) {
+      if (refsFirst) {
+        return a.isRef() && !b.isRef();
+      } else {
+        return !a.isRef() && b.isRef();
+      }
+    });
+  }
+
   std::unordered_map<Type, size_t> currLocalsByType;
   for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
     Index j = 0;
@@ -2344,9 +2528,26 @@ void BinaryInstWriter::setScratchLocals() {
 
 void BinaryInstWriter::emitMemoryAccess(size_t alignment,
                                         size_t bytes,
-                                        uint32_t offset) {
-  o << U32LEB(Bits::log2(alignment ? alignment : bytes));
-  o << U32LEB(offset);
+                                        uint64_t offset,
+                                        Name memory) {
+  uint32_t alignmentBits = Bits::log2(alignment ? alignment : bytes);
+  uint32_t memoryIdx = parent.getMemoryIndex(memory);
+  if (memoryIdx > 0) {
+    // Set bit 6 in the alignment to indicate a memory index is present per:
+    // https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md
+    alignmentBits = alignmentBits | 1 << 6;
+  }
+  o << U32LEB(alignmentBits);
+  if (memoryIdx > 0) {
+    o << U32LEB(memoryIdx);
+  }
+
+  bool memory64 = parent.getModule()->getMemory(memory)->is64();
+  if (memory64) {
+    o << U64LEB(offset);
+  } else {
+    o << U32LEB(offset);
+  }
 }
 
 int32_t BinaryInstWriter::getBreakIndex(Name name) { // -1 if not found
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp
index a9ce30e..6b11ff9 100644
--- a/src/wasm/wasm-type.cpp
+++ b/src/wasm/wasm-type.cpp
@@ -60,7 +60,6 @@ struct TypeInfo {
   enum Kind {
     TupleKind,
     RefKind,
-    RttKind,
   } kind;
   struct Ref {
     HeapType heapType;
@@ -69,20 +68,17 @@ struct TypeInfo {
   union {
     Tuple tuple;
     Ref ref;
-    Rtt rtt;
   };
 
   TypeInfo(const Tuple& tuple) : kind(TupleKind), tuple(tuple) {}
   TypeInfo(Tuple&& tuple) : kind(TupleKind), tuple(std::move(tuple)) {}
   TypeInfo(HeapType heapType, Nullability nullable)
     : kind(RefKind), ref{heapType, nullable} {}
-  TypeInfo(Rtt rtt) : kind(RttKind), rtt(rtt) {}
   TypeInfo(const TypeInfo& other);
   ~TypeInfo();
 
   constexpr bool isTuple() const { return kind == TupleKind; }
   constexpr bool isRef() const { return kind == RefKind; }
-  constexpr bool isRtt() const { return kind == RttKind; }
 
   bool isNullable() const { return kind == RefKind && ref.nullable; }
 
@@ -165,7 +161,6 @@ struct SubTyper {
   bool isSubType(const Signature& a, const Signature& b);
   bool isSubType(const Struct& a, const Struct& b);
   bool isSubType(const Array& a, const Array& b);
-  bool isSubType(const Rtt& a, const Rtt& b);
 };
 
 // Helper for finding the equirecursive least upper bound of two types.
@@ -177,7 +172,7 @@ struct TypeBounder {
 
   bool hasLeastUpperBound(Type a, Type b);
   Type getLeastUpperBound(Type a, Type b);
-  HeapType getLeastUpperBound(HeapType a, HeapType b);
+  std::optional<HeapType> getLeastUpperBound(HeapType a, HeapType b);
 
 private:
   // Return the LUB iff a LUB was found. The HeapType and Struct overloads are
@@ -186,15 +181,12 @@ private:
   // Note that these methods can return temporary types, so they should never be
   // used directly.
   std::optional<Type> lub(Type a, Type b);
-  HeapType lub(HeapType a, HeapType b);
-  HeapType::BasicHeapType lub(HeapType::BasicHeapType a,
-                              HeapType::BasicHeapType b);
+  std::optional<HeapType> lub(HeapType a, HeapType b);
   std::optional<Tuple> lub(const Tuple& a, const Tuple& b);
   std::optional<Field> lub(const Field& a, const Field& b);
   std::optional<Signature> lub(const Signature& a, const Signature& b);
   Struct lub(const Struct& a, const Struct& b);
   std::optional<Array> lub(const Array& a, const Array& b);
-  std::optional<Rtt> lub(const Rtt& a, const Rtt& b);
 };
 
 // Helper for printing types.
@@ -233,7 +225,6 @@ struct TypePrinter {
                       std::optional<HeapType> super = std::nullopt);
   std::ostream& print(const Array& array,
                       std::optional<HeapType> super = std::nullopt);
-  std::ostream& print(const Rtt& rtt);
 };
 
 // Helper for hashing the shapes of TypeInfos and HeapTypeInfos. Keeps track of
@@ -257,7 +248,6 @@ struct FiniteShapeHasher {
   size_t hash(const Signature& sig);
   size_t hash(const Struct& struct_);
   size_t hash(const Array& array);
-  size_t hash(const Rtt& rtt);
 };
 
 // Helper for comparing the shapes of TypeInfos and HeapTypeInfos for equality.
@@ -283,7 +273,6 @@ struct FiniteShapeEquator {
   bool eq(const Signature& a, const Signature& b);
   bool eq(const Struct& a, const Struct& b);
   bool eq(const Array& a, const Array& b);
-  bool eq(const Rtt& a, const Rtt& b);
 };
 
 struct RecGroupHasher {
@@ -309,7 +298,6 @@ struct RecGroupHasher {
   size_t hash(const Signature& sig) const;
   size_t hash(const Struct& struct_) const;
   size_t hash(const Array& array) const;
-  size_t hash(const Rtt& rtt) const;
 };
 
 struct RecGroupEquator {
@@ -336,7 +324,6 @@ struct RecGroupEquator {
   bool eq(const Signature& a, const Signature& b) const;
   bool eq(const Struct& a, const Struct& b) const;
   bool eq(const Array& a, const Array& b) const;
-  bool eq(const Rtt& a, const Rtt& b) const;
 };
 
 // A wrapper around a RecGroup that provides equality and hashing based on the
@@ -569,17 +556,76 @@ Type asCanonical(Type type) {
   }
 }
 
-// Given a HeapType that may or may not be backed by the simplest possible
-// representation, return the equivalent type that is definitely backed by the
-// simplest possible representation.
-HeapType asCanonical(HeapType type) {
+HeapType::BasicHeapType getBasicHeapSupertype(HeapType type) {
   if (type.isBasic()) {
-    return type;
-  } else if (auto canon = getHeapTypeInfo(type)->getCanonical()) {
-    return *canon;
-  } else {
-    return type;
+    return type.getBasic();
   }
+  auto* info = getHeapTypeInfo(type);
+  switch (info->kind) {
+    case HeapTypeInfo::BasicKind:
+      break;
+    case HeapTypeInfo::SignatureKind:
+      return HeapType::func;
+    case HeapTypeInfo::StructKind:
+      return HeapType::data;
+    case HeapTypeInfo::ArrayKind:
+      return HeapType::array;
+  }
+  WASM_UNREACHABLE("unexpected kind");
+};
+
+std::optional<HeapType> getBasicHeapTypeLUB(HeapType::BasicHeapType a,
+                                            HeapType::BasicHeapType b) {
+  if (a == b) {
+    return a;
+  }
+  if (HeapType(a).getBottom() != HeapType(b).getBottom()) {
+    return {};
+  }
+  if (HeapType(a).isBottom()) {
+    return b;
+  }
+  if (HeapType(b).isBottom()) {
+    return a;
+  }
+  // Canonicalize to have `a` be the lesser type.
+  if (unsigned(a) > unsigned(b)) {
+    std::swap(a, b);
+  }
+  switch (a) {
+    case HeapType::ext:
+    case HeapType::func:
+      return std::nullopt;
+    case HeapType::any:
+      return {HeapType::any};
+    case HeapType::eq:
+      if (b == HeapType::i31 || b == HeapType::data || b == HeapType::array) {
+        return {HeapType::eq};
+      }
+      return {HeapType::any};
+    case HeapType::i31:
+      if (b == HeapType::data || b == HeapType::array) {
+        return {HeapType::eq};
+      }
+      return {HeapType::any};
+    case HeapType::data:
+      if (b == HeapType::array) {
+        return {HeapType::data};
+      }
+      return {HeapType::any};
+    case HeapType::array:
+    case HeapType::string:
+    case HeapType::stringview_wtf8:
+    case HeapType::stringview_wtf16:
+    case HeapType::stringview_iter:
+      return {HeapType::any};
+    case HeapType::none:
+    case HeapType::noext:
+    case HeapType::nofunc:
+      // Bottom types already handled.
+      break;
+  }
+  WASM_UNREACHABLE("unexpected basic type");
 }
 
 TypeInfo::TypeInfo(const TypeInfo& other) {
@@ -591,9 +637,6 @@ TypeInfo::TypeInfo(const TypeInfo& other) {
     case RefKind:
       new (&ref) auto(other.ref);
       return;
-    case RttKind:
-      new (&rtt) auto(other.rtt);
-      return;
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -606,9 +649,6 @@ TypeInfo::~TypeInfo() {
     case RefKind:
       ref.~Ref();
       return;
-    case RttKind:
-      rtt.~Rtt();
-      return;
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -622,31 +662,6 @@ std::optional<Type> TypeInfo::getCanonical() const {
       return tuple.types[0];
     }
   }
-  if (isRef()) {
-    HeapType basic = asCanonical(ref.heapType);
-    if (basic.isBasic()) {
-      if (ref.nullable) {
-        switch (basic.getBasic()) {
-          case HeapType::func:
-            return Type::funcref;
-          case HeapType::any:
-            return Type::anyref;
-          case HeapType::eq:
-            return Type::eqref;
-          case HeapType::i31:
-          case HeapType::data:
-            break;
-        }
-      } else {
-        if (basic == HeapType::i31) {
-          return Type::i31ref;
-        }
-        if (basic == HeapType::data) {
-          return Type::dataref;
-        }
-      }
-    }
-  }
   return {};
 }
 
@@ -660,8 +675,6 @@ bool TypeInfo::operator==(const TypeInfo& other) const {
     case RefKind:
       return ref.nullable == other.ref.nullable &&
              ref.heapType == other.ref.heapType;
-    case RttKind:
-      return rtt == other.rtt;
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -937,11 +950,6 @@ Type::Type(HeapType heapType, Nullability nullable) {
   new (this) Type(globalTypeStore.insert(TypeInfo(heapType, nullable)));
 }
 
-Type::Type(Rtt rtt) {
-  assert(!isTemp(rtt.heapType) && "Leaking temporary type!");
-  new (this) Type(globalTypeStore.insert(rtt));
-}
-
 bool Type::isTuple() const {
   if (isBasic()) {
     return false;
@@ -952,7 +960,7 @@ bool Type::isTuple() const {
 
 bool Type::isRef() const {
   if (isBasic()) {
-    return id >= funcref && id <= _last_basic_type;
+    return false;
   } else {
     return getTypeInfo(*this)->isRef();
   }
@@ -960,7 +968,7 @@ bool Type::isRef() const {
 
 bool Type::isFunction() const {
   if (isBasic()) {
-    return id == funcref;
+    return false;
   } else {
     auto* info = getTypeInfo(*this);
     return info->isRef() && info->ref.heapType.isFunction();
@@ -969,7 +977,7 @@ bool Type::isFunction() const {
 
 bool Type::isData() const {
   if (isBasic()) {
-    return id == dataref;
+    return false;
   } else {
     auto* info = getTypeInfo(*this);
     return info->isRef() && info->ref.heapType.isData();
@@ -978,7 +986,7 @@ bool Type::isData() const {
 
 bool Type::isNullable() const {
   if (isBasic()) {
-    return id >= funcref && id <= eqref; // except i31ref and dataref
+    return false;
   } else {
     return getTypeInfo(*this)->isNullable();
   }
@@ -992,14 +1000,6 @@ bool Type::isNonNullable() const {
   }
 }
 
-bool Type::isRtt() const {
-  if (isBasic()) {
-    return false;
-  } else {
-    return getTypeInfo(*this)->isRtt();
-  }
-}
-
 bool Type::isStruct() const { return isRef() && getHeapType().isStruct(); }
 
 bool Type::isArray() const { return isRef() && getHeapType().isArray(); }
@@ -1016,19 +1016,7 @@ bool Type::isDefaultable() const {
     }
     return true;
   }
-  return isConcrete() && !isNonNullable() && !isRtt();
-}
-
-bool Type::isDefaultableOrNonNullable() const {
-  if (isTuple()) {
-    for (auto t : *this) {
-      if (!t.isDefaultableOrNonNullable()) {
-        return false;
-      }
-    }
-    return true;
-  }
-  return isConcrete() && !isRtt();
+  return isConcrete() && !isNonNullable();
 }
 
 Nullability Type::getNullability() const {
@@ -1047,11 +1035,6 @@ unsigned Type::getByteSize() const {
         return 8;
       case Type::v128:
         return 16;
-      case Type::funcref:
-      case Type::anyref:
-      case Type::eqref:
-      case Type::i31ref:
-      case Type::dataref:
       case Type::none:
       case Type::unreachable:
         break;
@@ -1109,11 +1092,26 @@ FeatureSet Type::getFeatures() const {
       }
       if (heapType.isBasic()) {
         switch (heapType.getBasic()) {
-          case HeapType::BasicHeapType::eq:
-          case HeapType::BasicHeapType::i31:
-          case HeapType::BasicHeapType::data:
+          case HeapType::ext:
+          case HeapType::func:
+            return FeatureSet::ReferenceTypes;
+          case HeapType::any:
+          case HeapType::eq:
+          case HeapType::i31:
+          case HeapType::data:
+          case HeapType::array:
             return FeatureSet::ReferenceTypes | FeatureSet::GC;
-          default: {}
+          case HeapType::string:
+          case HeapType::stringview_wtf8:
+          case HeapType::stringview_wtf16:
+          case HeapType::stringview_iter:
+            return FeatureSet::ReferenceTypes | FeatureSet::Strings;
+          case HeapType::none:
+          case HeapType::noext:
+          case HeapType::nofunc:
+            // Technically introduced in GC, but used internally as part of
+            // ref.null with just reference types.
+            return FeatureSet::ReferenceTypes;
         }
       }
       // Note: Technically typed function references also require the typed
@@ -1122,8 +1120,6 @@ FeatureSet Type::getFeatures() const {
       // load of the wasm we don't know the features yet, so we apply the more
       // refined types), so we don't add that in any case here.
       return FeatureSet::ReferenceTypes;
-    } else if (t.isRtt()) {
-      return FeatureSet::ReferenceTypes | FeatureSet::GC;
     }
     TODO_SINGLE_COMPOUND(t);
     switch (t.getBasic()) {
@@ -1160,16 +1156,6 @@ HeapType Type::getHeapType() const {
       case Type::f64:
       case Type::v128:
         break;
-      case Type::funcref:
-        return HeapType::func;
-      case Type::anyref:
-        return HeapType::any;
-      case Type::eqref:
-        return HeapType::eq;
-      case Type::i31ref:
-        return HeapType::i31;
-      case Type::dataref:
-        return HeapType::data;
     }
     WASM_UNREACHABLE("Unexpected type");
   } else {
@@ -1179,18 +1165,11 @@ HeapType Type::getHeapType() const {
         break;
       case TypeInfo::RefKind:
         return info->ref.heapType;
-      case TypeInfo::RttKind:
-        return info->rtt.heapType;
     }
     WASM_UNREACHABLE("Unexpected type");
   }
 }
 
-Rtt Type::getRtt() const {
-  assert(isRtt());
-  return getTypeInfo(*this)->rtt;
-}
-
 Type Type::get(unsigned byteSize, bool float_) {
   if (byteSize < 4) {
     return Type::i32;
@@ -1222,11 +1201,51 @@ std::vector<HeapType> Type::getHeapTypeChildren() {
 }
 
 bool Type::hasLeastUpperBound(Type a, Type b) {
-  return TypeBounder().hasLeastUpperBound(a, b);
+  if (getTypeSystem() == TypeSystem::Equirecursive) {
+    return TypeBounder().hasLeastUpperBound(a, b);
+  }
+  return getLeastUpperBound(a, b) != Type::none;
 }
 
 Type Type::getLeastUpperBound(Type a, Type b) {
-  return TypeBounder().getLeastUpperBound(a, b);
+  if (a == b) {
+    return a;
+  }
+  if (getTypeSystem() == TypeSystem::Equirecursive) {
+    return TypeBounder().getLeastUpperBound(a, b);
+  }
+  if (a == Type::unreachable) {
+    return b;
+  }
+  if (b == Type::unreachable) {
+    return a;
+  }
+  if (a.isTuple() && b.isTuple()) {
+    auto size = a.size();
+    if (size != b.size()) {
+      return Type::none;
+    }
+    std::vector<Type> elems;
+    elems.reserve(size);
+    for (size_t i = 0; i < size; ++i) {
+      auto lub = Type::getLeastUpperBound(a[i], b[i]);
+      if (lub == Type::none) {
+        return Type::none;
+      }
+      elems.push_back(lub);
+    }
+    return Type(elems);
+  }
+  if (a.isRef() && b.isRef()) {
+    if (auto heapType =
+          HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) {
+      auto nullability =
+        (a.isNullable() || b.isNullable()) ? Nullable : NonNullable;
+      return Type(*heapType, nullability);
+    }
+  }
+  return Type::none;
+  WASM_UNREACHABLE("unexpected type");
 }
 
 size_t Type::size() const {
@@ -1368,6 +1387,30 @@ bool HeapType::isArray() const {
   }
 }
 
+bool HeapType::isBottom() const {
+  if (isBasic()) {
+    switch (getBasic()) {
+      case ext:
+      case func:
+      case any:
+      case eq:
+      case i31:
+      case data:
+      case array:
+      case string:
+      case stringview_wtf8:
+      case stringview_wtf16:
+      case stringview_iter:
+        return false;
+      case none:
+      case noext:
+      case nofunc:
+        return true;
+    }
+  }
+  return false;
+}
+
 Signature HeapType::getSignature() const {
   assert(isSignature());
   return getHeapTypeInfo(*this)->signature;
@@ -1400,9 +1443,87 @@ size_t HeapType::getDepth() const {
   for (auto curr = *this; (super = curr.getSuperType()); curr = *super) {
     ++depth;
   }
+  // In addition to the explicit supertypes we just traversed over, there is
+  // implicit supertyping wrt basic types. A signature type always has one more
+  // super, HeapType::func, etc.
+  if (!isBasic()) {
+    if (isFunction()) {
+      depth++;
+    } else if (isStruct()) {
+      // specific struct types <: data <: eq <: any
+      depth += 3;
+    } else if (isArray()) {
+      // specific array types <: array <: data <: eq <: any
+      depth += 4;
+    }
+  } else {
+    // Some basic types have supers.
+    switch (getBasic()) {
+      case HeapType::ext:
+      case HeapType::func:
+      case HeapType::any:
+        break;
+      case HeapType::eq:
+        depth++;
+        break;
+      case HeapType::i31:
+      case HeapType::data:
+      case HeapType::string:
+      case HeapType::stringview_wtf8:
+      case HeapType::stringview_wtf16:
+      case HeapType::stringview_iter:
+        depth += 2;
+        break;
+      case HeapType::array:
+        depth += 3;
+        break;
+      case HeapType::none:
+      case HeapType::nofunc:
+      case HeapType::noext:
+        // Bottom types are infinitely deep.
+        depth = size_t(-1l);
+    }
+  }
   return depth;
 }
 
+HeapType::BasicHeapType HeapType::getBottom() const {
+  if (isBasic()) {
+    switch (getBasic()) {
+      case ext:
+        return noext;
+      case func:
+        return nofunc;
+      case any:
+      case eq:
+      case i31:
+      case data:
+      case array:
+      case string:
+      case stringview_wtf8:
+      case stringview_wtf16:
+      case stringview_iter:
+      case none:
+        return none;
+      case noext:
+        return noext;
+      case nofunc:
+        return nofunc;
+    }
+  }
+  auto* info = getHeapTypeInfo(*this);
+  switch (info->kind) {
+    case HeapTypeInfo::BasicKind:
+      return HeapType(info->basic).getBottom();
+    case HeapTypeInfo::SignatureKind:
+      return nofunc;
+    case HeapTypeInfo::StructKind:
+    case HeapTypeInfo::ArrayKind:
+      return none;
+  }
+  WASM_UNREACHABLE("unexpected kind");
+}
+
 bool HeapType::isSubType(HeapType left, HeapType right) {
   // As an optimization, in the common case do not even construct a SubTyper.
   if (left == right) {
@@ -1425,8 +1546,63 @@ std::vector<HeapType> HeapType::getReferencedHeapTypes() const {
   return types;
 }
 
-HeapType HeapType::getLeastUpperBound(HeapType a, HeapType b) {
-  return TypeBounder().getLeastUpperBound(a, b);
+std::optional<HeapType> HeapType::getLeastUpperBound(HeapType a, HeapType b) {
+  if (a == b) {
+    return a;
+  }
+  if (a.getBottom() != b.getBottom()) {
+    return {};
+  }
+  if (a.isBottom()) {
+    return b;
+  }
+  if (b.isBottom()) {
+    return a;
+  }
+  if (getTypeSystem() == TypeSystem::Equirecursive) {
+    return TypeBounder().getLeastUpperBound(a, b);
+  }
+  if (a.isBasic() || b.isBasic()) {
+    return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
+                               getBasicHeapSupertype(b));
+  }
+
+  auto* infoA = getHeapTypeInfo(a);
+  auto* infoB = getHeapTypeInfo(b);
+
+  if (infoA->kind != infoB->kind) {
+    return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
+                               getBasicHeapSupertype(b));
+  }
+
+  // Walk up the subtype tree to find the LUB. Ascend the tree from both `a`
+  // and `b` in lockstep. The first type we see for a second time must be the
+  // LUB because there are no cycles and the only way to encounter a type
+  // twice is for it to be on the path above both `a` and `b`.
+  std::unordered_set<HeapTypeInfo*> seen;
+  seen.insert(infoA);
+  seen.insert(infoB);
+  while (true) {
+    auto* nextA = infoA->supertype;
+    auto* nextB = infoB->supertype;
+    if (nextA == nullptr && nextB == nullptr) {
+      // Did not find a LUB in the subtype tree.
+      return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
+                                 getBasicHeapSupertype(b));
+    }
+    if (nextA) {
+      if (!seen.insert(nextA).second) {
+        return HeapType(uintptr_t(nextA));
+      }
+      infoA = nextA;
+    }
+    if (nextB) {
+      if (!seen.insert(nextB).second) {
+        return HeapType(uintptr_t(nextB));
+      }
+      infoB = nextB;
+    }
+  }
 }
 
 // Recursion groups with single elements are encoded as that single element's
@@ -1499,7 +1675,6 @@ std::string Tuple::toString() const { return genericToString(*this); }
 std::string Signature::toString() const { return genericToString(*this); }
 std::string Struct::toString() const { return genericToString(*this); }
 std::string Array::toString() const { return genericToString(*this); }
-std::string Rtt::toString() const { return genericToString(*this); }
 
 std::ostream& operator<<(std::ostream& os, Type type) {
   return TypePrinter(os).print(type);
@@ -1528,9 +1703,6 @@ std::ostream& operator<<(std::ostream& os, Struct struct_) {
 std::ostream& operator<<(std::ostream& os, Array array) {
   return TypePrinter(os).print(array);
 }
-std::ostream& operator<<(std::ostream& os, Rtt rtt) {
-  return TypePrinter(os).print(rtt);
-}
 std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) {
   switch (reason) {
     case TypeBuilder::ErrorReason::SelfSupertype:
@@ -1576,9 +1748,6 @@ bool SubTyper::isSubType(Type a, Type b) {
   if (a.isTuple() && b.isTuple()) {
     return isSubType(a.getTuple(), b.getTuple());
   }
-  if (a.isRtt() && b.isRtt()) {
-    return isSubType(a.getRtt(), b.getRtt());
-  }
   return false;
 }
 
@@ -1591,21 +1760,36 @@ bool SubTyper::isSubType(HeapType a, HeapType b) {
   }
   if (b.isBasic()) {
     switch (b.getBasic()) {
+      case HeapType::ext:
+        return a.getBottom() == HeapType::noext;
       case HeapType::func:
-        return a.isSignature();
+        return a.getBottom() == HeapType::nofunc;
       case HeapType::any:
-        return true;
+        return a.getBottom() == HeapType::none;
       case HeapType::eq:
-        return a == HeapType::i31 || a.isData();
+        return a == HeapType::i31 || a == HeapType::data ||
+               a == HeapType::array || a == HeapType::none || a.isData();
       case HeapType::i31:
-        return false;
+        return a == HeapType::none;
       case HeapType::data:
-        return a.isData();
+        return a == HeapType::array || a == HeapType::none || a.isData();
+      case HeapType::array:
+        return a == HeapType::none || a.isArray();
+      case HeapType::string:
+      case HeapType::stringview_wtf8:
+      case HeapType::stringview_wtf16:
+      case HeapType::stringview_iter:
+        return a == HeapType::none;
+      case HeapType::none:
+      case HeapType::noext:
+      case HeapType::nofunc:
+        return false;
     }
   }
   if (a.isBasic()) {
-    // Basic HeapTypes are never subtypes of compound HeapTypes.
-    return false;
+    // Basic HeapTypes are only subtypes of compound HeapTypes if they are
+    // bottom types.
+    return a == b.getBottom();
   }
   if (typeSystem == TypeSystem::Nominal ||
       typeSystem == TypeSystem::Isorecursive) {
@@ -1684,13 +1868,6 @@ bool SubTyper::isSubType(const Array& a, const Array& b) {
   return isSubType(a.element, b.element);
 }
 
-bool SubTyper::isSubType(const Rtt& a, const Rtt& b) {
-  // (rtt n $x) is a subtype of (rtt $x), that is, if the only difference in
-  // information is that the left side specifies a depth while the right side
-  // allows any depth.
-  return a.heapType == b.heapType && a.hasDepth() && !b.hasDepth();
-}
-
 bool TypeBounder::hasLeastUpperBound(Type a, Type b) { return bool(lub(a, b)); }
 
 Type TypeBounder::getLeastUpperBound(Type a, Type b) {
@@ -1712,8 +1889,13 @@ Type TypeBounder::getLeastUpperBound(Type a, Type b) {
   return built.back().getArray().element.type;
 }
 
-HeapType TypeBounder::getLeastUpperBound(HeapType a, HeapType b) {
-  HeapType l = lub(a, b);
+std::optional<HeapType> TypeBounder::getLeastUpperBound(HeapType a,
+                                                        HeapType b) {
+  auto maybeLub = lub(a, b);
+  if (!maybeLub) {
+    return std::nullopt;
+  }
+  HeapType l = *maybeLub;
   if (!isTemp(l)) {
     // The LUB is already canonical, so we're done.
     return l;
@@ -1744,89 +1926,43 @@ std::optional<Type> TypeBounder::lub(Type a, Type b) {
     }
     return builder.getTempTupleType(*tuple);
   } else if (a.isRef() && b.isRef()) {
-    auto nullability =
-      (a.isNullable() || b.isNullable()) ? Nullable : NonNullable;
-    HeapType heapType = lub(a.getHeapType(), b.getHeapType());
-    return builder.getTempRefType(heapType, nullability);
-  } else if (a.isRtt() && b.isRtt()) {
-    auto rtt = lub(a.getRtt(), b.getRtt());
-    if (!rtt) {
-      return {};
+    if (auto heapType = lub(a.getHeapType(), b.getHeapType())) {
+      auto nullability =
+        (a.isNullable() || b.isNullable()) ? Nullable : NonNullable;
+      return builder.getTempRefType(*heapType, nullability);
     }
-    return builder.getTempRttType(*rtt);
   }
   return {};
 }
 
-HeapType TypeBounder::lub(HeapType a, HeapType b) {
+std::optional<HeapType> TypeBounder::lub(HeapType a, HeapType b) {
   if (a == b) {
     return a;
   }
-
-  auto getBasicApproximation = [](HeapType x) {
-    if (x.isBasic()) {
-      return x.getBasic();
-    }
-    auto* info = getHeapTypeInfo(x);
-    switch (info->kind) {
-      case HeapTypeInfo::BasicKind:
-        break;
-      case HeapTypeInfo::SignatureKind:
-        return HeapType::func;
-      case HeapTypeInfo::StructKind:
-      case HeapTypeInfo::ArrayKind:
-        return HeapType::data;
-    }
-    WASM_UNREACHABLE("unexpected kind");
-  };
-
-  auto getBasicLUB = [&]() {
-    return lub(getBasicApproximation(a), getBasicApproximation(b));
-  };
+  if (a.getBottom() != b.getBottom()) {
+    return {};
+  }
+  if (a.isBottom()) {
+    return b;
+  }
+  if (b.isBottom()) {
+    return a;
+  }
 
   if (a.isBasic() || b.isBasic()) {
-    return getBasicLUB();
+    return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
+                               getBasicHeapSupertype(b));
   }
 
   HeapTypeInfo* infoA = getHeapTypeInfo(a);
   HeapTypeInfo* infoB = getHeapTypeInfo(b);
 
   if (infoA->kind != infoB->kind) {
-    return getBasicLUB();
+    return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
+                               getBasicHeapSupertype(b));
   }
 
-  if (typeSystem == TypeSystem::Nominal ||
-      typeSystem == TypeSystem::Isorecursive) {
-    // Walk up the subtype tree to find the LUB. Ascend the tree from both `a`
-    // and `b` in lockstep. The first type we see for a second time must be the
-    // LUB because there are no cycles and the only way to encounter a type
-    // twice is for it to be on the path above both `a` and `b`.
-    std::unordered_set<HeapTypeInfo*> seen;
-    auto* currA = infoA;
-    auto* currB = infoB;
-    seen.insert(currA);
-    seen.insert(currB);
-    while (true) {
-      auto* nextA = currA->supertype;
-      auto* nextB = currB->supertype;
-      if (nextA == nullptr && nextB == nullptr) {
-        // Did not find a LUB in the subtype tree.
-        return getBasicLUB();
-      }
-      if (nextA) {
-        if (!seen.insert(nextA).second) {
-          return HeapType(uintptr_t(nextA));
-        }
-        currA = nextA;
-      }
-      if (nextB) {
-        if (!seen.insert(nextB).second) {
-          return HeapType(uintptr_t(nextB));
-        }
-        currB = nextB;
-      }
-    }
-  }
+  assert(getTypeSystem() == TypeSystem::Equirecursive);
 
   // Allocate a new slot to construct the LUB of this pair if we have not
   // already seen it before. Canonicalize the pair to have the element with the
@@ -1865,35 +2001,6 @@ HeapType TypeBounder::lub(HeapType a, HeapType b) {
   WASM_UNREACHABLE("unexpected kind");
 }
 
-HeapType::BasicHeapType TypeBounder::lub(HeapType::BasicHeapType a,
-                                         HeapType::BasicHeapType b) {
-  if (a == b) {
-    return a;
-  }
-  // Canonicalize to have `x` be the lesser type.
-  if (unsigned(a) > unsigned(b)) {
-    std::swap(a, b);
-  }
-  switch (a) {
-    case HeapType::func:
-    case HeapType::any:
-      return HeapType::any;
-    case HeapType::eq:
-      if (b == HeapType::i31 || b == HeapType::data) {
-        return HeapType::eq;
-      }
-      return HeapType::any;
-    case HeapType::i31:
-      if (b == HeapType::data) {
-        return HeapType::eq;
-      }
-      return HeapType::any;
-    case HeapType::data:
-      return HeapType::any;
-  }
-  WASM_UNREACHABLE("unexpected basic type");
-}
-
 std::optional<Tuple> TypeBounder::lub(const Tuple& a, const Tuple& b) {
   if (a.types.size() != b.types.size()) {
     return {};
@@ -1967,14 +2074,6 @@ std::optional<Array> TypeBounder::lub(const Array& a, const Array& b) {
   }
 }
 
-std::optional<Rtt> TypeBounder::lub(const Rtt& a, const Rtt& b) {
-  if (a.heapType != b.heapType) {
-    return {};
-  }
-  uint32_t depth = (a.depth == b.depth) ? a.depth : Rtt::NoDepth;
-  return Rtt(depth, a.heapType);
-}
-
 void TypePrinter::printHeapTypeName(HeapType type) {
   if (type.isBasic()) {
     print(type);
@@ -2009,16 +2108,6 @@ std::ostream& TypePrinter::print(Type type) {
         return os << "f64";
       case Type::v128:
         return os << "v128";
-      case Type::funcref:
-        return os << "funcref";
-      case Type::anyref:
-        return os << "anyref";
-      case Type::eqref:
-        return os << "eqref";
-      case Type::i31ref:
-        return os << "i31ref";
-      case Type::dataref:
-        return os << "dataref";
     }
   }
 
@@ -2031,14 +2120,48 @@ std::ostream& TypePrinter::print(Type type) {
   if (type.isTuple()) {
     print(type.getTuple());
   } else if (type.isRef()) {
+    auto heapType = type.getHeapType();
+    if (heapType.isBasic()) {
+      // Print shorthands for certain basic heap types.
+      if (type.isNullable()) {
+        switch (heapType.getBasic()) {
+          case HeapType::ext:
+            return os << "externref";
+          case HeapType::func:
+            return os << "funcref";
+          case HeapType::any:
+            return os << "anyref";
+          case HeapType::eq:
+            return os << "eqref";
+          case HeapType::i31:
+            return os << "i31ref";
+          case HeapType::data:
+            return os << "dataref";
+          case HeapType::array:
+            return os << "arrayref";
+          case HeapType::string:
+            return os << "stringref";
+          case HeapType::stringview_wtf8:
+            return os << "stringview_wtf8";
+          case HeapType::stringview_wtf16:
+            return os << "stringview_wtf16";
+          case HeapType::stringview_iter:
+            return os << "stringview_iter";
+          case HeapType::none:
+            return os << "nullref";
+          case HeapType::noext:
+            return os << "nullexternref";
+          case HeapType::nofunc:
+            return os << "nullfuncref";
+        }
+      }
+    }
     os << "(ref ";
     if (type.isNullable()) {
       os << "null ";
     }
-    printHeapTypeName(type.getHeapType());
+    printHeapTypeName(heapType);
     os << ')';
-  } else if (type.isRtt()) {
-    print(type.getRtt());
   } else {
     WASM_UNREACHABLE("unexpected type");
   }
@@ -2048,6 +2171,8 @@ std::ostream& TypePrinter::print(Type type) {
 std::ostream& TypePrinter::print(HeapType type) {
   if (type.isBasic()) {
     switch (type.getBasic()) {
+      case HeapType::ext:
+        return os << "extern";
       case HeapType::func:
         return os << "func";
       case HeapType::any:
@@ -2058,6 +2183,22 @@ std::ostream& TypePrinter::print(HeapType type) {
         return os << "i31";
       case HeapType::data:
         return os << "data";
+      case HeapType::array:
+        return os << "array";
+      case HeapType::string:
+        return os << "string";
+      case HeapType::stringview_wtf8:
+        return os << "stringview_wtf8";
+      case HeapType::stringview_wtf16:
+        return os << "stringview_wtf16";
+      case HeapType::stringview_iter:
+        return os << "stringview_iter";
+      case HeapType::none:
+        return os << "none";
+      case HeapType::noext:
+        return os << "noextern";
+      case HeapType::nofunc:
+        return os << "nofunc";
     }
   }
 
@@ -2183,15 +2324,6 @@ std::ostream& TypePrinter::print(const Array& array,
   return os << ')';
 }
 
-std::ostream& TypePrinter::print(const Rtt& rtt) {
-  os << "(rtt ";
-  if (rtt.hasDepth()) {
-    os << rtt.depth << ' ';
-  }
-  printHeapTypeName(rtt.heapType);
-  return os << ')';
-}
-
 size_t FiniteShapeHasher::hash(Type type) {
   size_t digest = wasm::hash(type.isBasic());
   if (type.isBasic()) {
@@ -2234,9 +2366,6 @@ size_t FiniteShapeHasher::hash(const TypeInfo& info) {
       rehash(digest, info.ref.nullable);
       hash_combine(digest, hash(info.ref.heapType));
       return digest;
-    case TypeInfo::RttKind:
-      hash_combine(digest, hash(info.rtt));
-      return digest;
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -2304,12 +2433,6 @@ size_t FiniteShapeHasher::hash(const Array& array) {
   return hash(array.element);
 }
 
-size_t FiniteShapeHasher::hash(const Rtt& rtt) {
-  size_t digest = wasm::hash(rtt.depth);
-  hash_combine(digest, hash(rtt.heapType));
-  return digest;
-}
-
 bool FiniteShapeEquator::eq(Type a, Type b) {
   if (a.isBasic() != b.isBasic()) {
     return false;
@@ -2353,8 +2476,6 @@ bool FiniteShapeEquator::eq(const TypeInfo& a, const TypeInfo& b) {
     case TypeInfo::RefKind:
       return a.ref.nullable == b.ref.nullable &&
              eq(a.ref.heapType, b.ref.heapType);
-    case TypeInfo::RttKind:
-      return eq(a.rtt, b.rtt);
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -2415,10 +2536,6 @@ bool FiniteShapeEquator::eq(const Array& a, const Array& b) {
   return eq(a.element, b.element);
 }
 
-bool FiniteShapeEquator::eq(const Rtt& a, const Rtt& b) {
-  return a.depth == b.depth && eq(a.heapType, b.heapType);
-}
-
 size_t RecGroupHasher::operator()() const {
   size_t digest = wasm::hash(group.size());
   for (auto type : group) {
@@ -2475,9 +2592,6 @@ size_t RecGroupHasher::hash(const TypeInfo& info) const {
       rehash(digest, info.ref.nullable);
       hash_combine(digest, hash(info.ref.heapType));
       return digest;
-    case TypeInfo::RttKind:
-      hash_combine(digest, hash(info.rtt));
-      return digest;
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -2538,12 +2652,6 @@ size_t RecGroupHasher::hash(const Array& array) const {
   return hash(array.element);
 }
 
-size_t RecGroupHasher::hash(const Rtt& rtt) const {
-  size_t digest = wasm::hash(rtt.depth);
-  hash_combine(digest, hash(rtt.heapType));
-  return digest;
-}
-
 bool RecGroupEquator::operator()() const {
   if (newGroup == otherGroup) {
     return true;
@@ -2568,11 +2676,8 @@ bool RecGroupEquator::topLevelEq(HeapType a, HeapType b) const {
 }
 
 bool RecGroupEquator::eq(Type a, Type b) const {
-  if (a == b) {
-    return true;
-  }
   if (a.isBasic() || b.isBasic()) {
-    return false;
+    return a == b;
   }
   return eq(*getTypeInfo(a), *getTypeInfo(b));
 }
@@ -2591,7 +2696,9 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const {
   }
   auto groupA = a.getRecGroup();
   auto groupB = b.getRecGroup();
-  return groupA == groupB || (groupA == newGroup && groupB == otherGroup);
+  bool selfRefA = groupA == newGroup;
+  bool selfRefB = groupB == otherGroup;
+  return (selfRefA && selfRefB) || (!selfRefA && !selfRefB && groupA == groupB);
 }
 
 bool RecGroupEquator::eq(const TypeInfo& a, const TypeInfo& b) const {
@@ -2604,8 +2711,6 @@ bool RecGroupEquator::eq(const TypeInfo& a, const TypeInfo& b) const {
     case TypeInfo::RefKind:
       return a.ref.nullable == b.ref.nullable &&
              eq(a.ref.heapType, b.ref.heapType);
-    case TypeInfo::RttKind:
-      return eq(a.rtt, b.rtt);
   }
   WASM_UNREACHABLE("unexpected kind");
 }
@@ -2666,10 +2771,6 @@ bool RecGroupEquator::eq(const Array& a, const Array& b) const {
   return eq(a.element, b.element);
 }
 
-bool RecGroupEquator::eq(const Rtt& a, const Rtt& b) const {
-  return a.depth == b.depth && eq(a.heapType, b.heapType);
-}
-
 template<typename Self> void TypeGraphWalkerBase<Self>::walkRoot(Type* type) {
   assert(taskList.empty());
   taskList.push_back(Task::scan(type));
@@ -2730,9 +2831,6 @@ template<typename Self> void TypeGraphWalkerBase<Self>::scanType(Type* type) {
       taskList.push_back(Task::scan(&info->ref.heapType));
       break;
     }
-    case TypeInfo::RttKind:
-      taskList.push_back(Task::scan(&info->rtt.heapType));
-      break;
   }
 }
 
@@ -2810,7 +2908,7 @@ TypeBuilder::TypeBuilder(TypeBuilder&& other) = default;
 TypeBuilder& TypeBuilder::operator=(TypeBuilder&& other) = default;
 
 void TypeBuilder::grow(size_t n) {
-  assert(size() + n > size());
+  assert(size() + n >= size());
   impl->entries.resize(size() + n);
 }
 
@@ -2870,15 +2968,10 @@ Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) {
   return markTemp(impl->typeStore.insert(TypeInfo(type, nullable)));
 }
 
-Type TypeBuilder::getTempRttType(Rtt rtt) {
-  return markTemp(impl->typeStore.insert(rtt));
-}
-
-void TypeBuilder::setSubType(size_t i, size_t j) {
-  assert(i < size() && j < size() && "index out of bounds");
+void TypeBuilder::setSubType(size_t i, HeapType super) {
+  assert(i < size() && "index out of bounds");
   HeapTypeInfo* sub = impl->entries[i].info.get();
-  HeapTypeInfo* super = impl->entries[j].info.get();
-  sub->supertype = super;
+  sub->supertype = getHeapTypeInfo(super);
 }
 
 void TypeBuilder::createRecGroup(size_t i, size_t length) {
@@ -3728,9 +3821,9 @@ std::optional<TypeBuilder::Error> canonicalizeIsorecursive(
     if (type.isBasic()) {
       continue;
     }
-    // Validate the supertype. Supertypes must precede their subtypes.
+    // Validate the supertype. Temporary supertypes must precede their subtypes.
     if (auto super = type.getSuperType()) {
-      if (!indexOfType.count(*super)) {
+      if (isTemp(*super) && !indexOfType.count(*super)) {
         return {{index, TypeBuilder::ErrorReason::ForwardSupertypeReference}};
       }
     }
@@ -3980,12 +4073,6 @@ size_t hash<wasm::RecGroup>::operator()(const wasm::RecGroup& group) const {
   return wasm::hash(group.getID());
 }
 
-size_t hash<wasm::Rtt>::operator()(const wasm::Rtt& rtt) const {
-  auto digest = wasm::hash(rtt.depth);
-  wasm::rehash(digest, rtt.heapType);
-  return digest;
-}
-
 size_t hash<wasm::TypeInfo>::operator()(const wasm::TypeInfo& info) const {
   auto digest = wasm::hash(info.kind);
   switch (info.kind) {
@@ -3996,9 +4083,6 @@ size_t hash<wasm::TypeInfo>::operator()(const wasm::TypeInfo& info) const {
       wasm::rehash(digest, info.ref.nullable);
       wasm::rehash(digest, info.ref.heapType);
       return digest;
-    case wasm::TypeInfo::RttKind:
-      wasm::rehash(digest, info.rtt);
-      return digest;
   }
   WASM_UNREACHABLE("unexpected kind");
 }
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 39eb996..c1316f2 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -23,6 +23,8 @@
 #include "ir/features.h"
 #include "ir/global-utils.h"
 #include "ir/intrinsics.h"
+#include "ir/local-graph.h"
+#include "ir/local-structural-dominance.h"
 #include "ir/module-utils.h"
 #include "ir/stack-utils.h"
 #include "ir/utils.h"
@@ -206,7 +208,9 @@ struct ValidationInfo {
 struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
   bool isFunctionParallel() override { return true; }
 
-  Pass* create() override { return new FunctionValidator(*getModule(), &info); }
+  std::unique_ptr<Pass> create() override {
+    return std::make_unique<FunctionValidator>(*getModule(), &info);
+  }
 
   bool modifiesBinaryenIR() override { return false; }
 
@@ -222,6 +226,9 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
   // Validate a specific expression.
   void validate(Expression* curr) { walk(curr); }
 
+  // Validate a function.
+  void validate(Function* func) { walkFunction(func); }
+
   std::unordered_map<Name, std::unordered_set<Type>> breakTypes;
   std::unordered_set<Name> delegateTargetNames;
   std::unordered_set<Name> rethrowTargetNames;
@@ -323,6 +330,63 @@ public:
         self->pushTask(visitPoppyExpression, currp);
       }
     }
+
+    // Also verify that only allowed expressions end up in the situation where
+    // the expression has type unreachable but there is no unreachable child.
+    // For example a Call with no unreachable child cannot be unreachable, but a
+    // Break can be.
+    if (curr->type == Type::unreachable) {
+      switch (curr->_id) {
+        case Expression::BreakId: {
+          // If there is a condition, that is already validated fully in
+          // visitBreak(). If there isn't a condition, then this is allowed to
+          // be unreachable even without an unreachable child. Either way, we
+          // can leave.
+          return;
+        }
+        case Expression::SwitchId:
+        case Expression::ReturnId:
+        case Expression::UnreachableId:
+        case Expression::ThrowId:
+        case Expression::RethrowId: {
+          // These can all be unreachable without an unreachable child.
+          return;
+        }
+        case Expression::CallId: {
+          if (curr->cast<Call>()->isReturn) {
+            return;
+          }
+          break;
+        }
+        case Expression::CallIndirectId: {
+          if (curr->cast<CallIndirect>()->isReturn) {
+            return;
+          }
+          break;
+        }
+        case Expression::CallRefId: {
+          if (curr->cast<CallRef>()->isReturn) {
+            return;
+          }
+          break;
+        }
+        default: {
+          break;
+        }
+      }
+
+      // If we reach here, then we must have an unreachable child.
+      bool hasUnreachableChild = false;
+      for (auto* child : ChildIterator(curr)) {
+        if (child->type == Type::unreachable) {
+          hasUnreachableChild = true;
+          break;
+        }
+      }
+      self->shouldBeTrue(hasUnreachableChild,
+                         curr,
+                         "unreachable instruction must have unreachable child");
+    }
   }
 
   void noteBreak(Name name, Expression* value, Expression* curr);
@@ -363,6 +427,7 @@ public:
   void visitMemoryGrow(MemoryGrow* curr);
   void visitRefNull(RefNull* curr);
   void visitRefIs(RefIs* curr);
+  void visitRefAs(RefAs* curr);
   void visitRefFunc(RefFunc* curr);
   void visitRefEq(RefEq* curr);
   void visitTableGet(TableGet* curr);
@@ -382,12 +447,11 @@ public:
   void visitRefTest(RefTest* curr);
   void visitRefCast(RefCast* curr);
   void visitBrOn(BrOn* curr);
-  void visitRttCanon(RttCanon* curr);
-  void visitRttSub(RttSub* curr);
   void visitStructNew(StructNew* curr);
   void visitStructGet(StructGet* curr);
   void visitStructSet(StructSet* curr);
   void visitArrayNew(ArrayNew* curr);
+  void visitArrayNewSeg(ArrayNewSeg* curr);
   void visitArrayInit(ArrayInit* curr);
   void visitArrayGet(ArrayGet* curr);
   void visitArraySet(ArraySet* curr);
@@ -441,18 +505,23 @@ private:
   template<typename T> void validateReturnCall(T* curr) {
     shouldBeTrue(!curr->isReturn || getModule()->features.hasTailCall(),
                  curr,
-                 "return_call* requires tail calls to be enabled");
+                 "return_call* requires tail calls [--enable-tail-call]");
   }
 
+  // |printable| is the expression to print in case of an error. That may differ
+  // from |curr| which we are validating.
   template<typename T>
-  void validateCallParamsAndResult(T* curr, HeapType sigType) {
-    if (!shouldBeTrue(
-          sigType.isSignature(), curr, "Heap type must be a signature type")) {
+  void validateCallParamsAndResult(T* curr,
+                                   HeapType sigType,
+                                   Expression* printable) {
+    if (!shouldBeTrue(sigType.isSignature(),
+                      printable,
+                      "Heap type must be a signature type")) {
       return;
     }
     auto sig = sigType.getSignature();
     if (!shouldBeTrue(curr->operands.size() == sig.params.size(),
-                      curr,
+                      printable,
                       "call* param number must match")) {
       return;
     }
@@ -460,7 +529,7 @@ private:
     for (const auto& param : sig.params) {
       if (!shouldBeSubType(curr->operands[i]->type,
                            param,
-                           curr,
+                           printable,
                            "call param types must match") &&
           !info.quiet) {
         getStream() << "(on argument " << i << ")\n";
@@ -470,23 +539,32 @@ private:
     if (curr->isReturn) {
       shouldBeEqual(curr->type,
                     Type(Type::unreachable),
-                    curr,
+                    printable,
                     "return_call* should have unreachable type");
       shouldBeSubType(
         sig.results,
         getFunction()->getResults(),
-        curr,
+        printable,
         "return_call* callee return type must match caller return type");
     } else {
       shouldBeEqualOrFirstIsUnreachable(
         curr->type,
         sig.results,
-        curr,
+        printable,
         "call* type must match callee return type");
     }
   }
 
-  Type indexType() { return getModule()->memory.indexType; }
+  // In the common case, we use |curr| as |printable|.
+  template<typename T>
+  void validateCallParamsAndResult(T* curr, HeapType sigType) {
+    validateCallParamsAndResult(curr, sigType, curr);
+  }
+
+  Type indexType(Name memoryName) {
+    auto memory = getModule()->getMemory(memoryName);
+    return memory->indexType;
+  }
 };
 
 void FunctionValidator::noteLabelName(Name name) {
@@ -535,9 +613,10 @@ void FunctionValidator::validatePoppyExpression(Expression* curr) {
 
 void FunctionValidator::visitBlock(Block* curr) {
   if (!getModule()->features.hasMultivalue()) {
-    shouldBeTrue(!curr->type.isTuple(),
-                 curr,
-                 "Multivalue block type (multivalue is not enabled)");
+    shouldBeTrue(
+      !curr->type.isTuple(),
+      curr,
+      "Multivalue block type require multivalue [--enable-multivalue]");
   }
   // if we are break'ed to, then the value must be right for us
   if (curr->name.is()) {
@@ -797,6 +876,42 @@ void FunctionValidator::visitCall(Call* curr) {
     return;
   }
   validateCallParamsAndResult(curr, target->type);
+
+  if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
+    // call.without.effects has the specific form of the last argument being a
+    // function reference, which will be called with all the previous arguments.
+    // The type must be consistent with that. This, for example, is not:
+    //
+    //  (call $call.without.effects
+    //    (i32.const 1)
+    //    (.. some function reference that expects an f64 param and not i32 ..)
+    //  )
+    if (shouldBeTrue(!curr->operands.empty(),
+                     curr,
+                     "call.without.effects must have a target operand")) {
+      auto* target = curr->operands.back();
+      // Validate only in the case that the target is a function. If it isn't,
+      // it might be unreachable (which is fine, and we can ignore this), or if
+      // the call.without.effects import doesn't have a function as the last
+      // parameter, then validateImports() will handle that later (and it's
+      // better to emit a single error there than one per callsite here).
+      if (target->type.isFunction()) {
+        // Copy the original call and remove the reference. It must then match
+        // the expected signature.
+        struct Copy {
+          std::vector<Expression*> operands;
+          bool isReturn;
+          Type type;
+        } copy;
+        for (Index i = 0; i < curr->operands.size() - 1; i++) {
+          copy.operands.push_back(curr->operands[i]);
+        }
+        copy.isReturn = curr->isReturn;
+        copy.type = curr->type;
+        validateCallParamsAndResult(&copy, target->type.getHeapType(), curr);
+      }
+    }
+  }
 }
 
 void FunctionValidator::visitCallIndirect(CallIndirect* curr) {
@@ -885,12 +1000,12 @@ void FunctionValidator::visitGlobalSet(GlobalSet* curr) {
 }
 
 void FunctionValidator::visitLoad(Load* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.load memory must exist");
   if (curr->isAtomic) {
     shouldBeTrue(getModule()->features.hasAtomics(),
                  curr,
-                 "Atomic operation (atomics are disabled)");
+                 "Atomic operations require threads [--enable-threads]");
     shouldBeTrue(curr->type == Type::i32 || curr->type == Type::i64 ||
                    curr->type == Type::unreachable,
                  curr,
@@ -899,13 +1014,13 @@ void FunctionValidator::visitLoad(Load* curr) {
   if (curr->type == Type::v128) {
     shouldBeTrue(getModule()->features.hasSIMD(),
                  curr,
-                 "SIMD operation (SIMD is disabled)");
+                 "SIMD operations require SIMD [--enable-simd]");
   }
   validateMemBytes(curr->bytes, curr->type, curr);
   validateAlignment(curr->align, curr->type, curr->bytes, curr->isAtomic, curr);
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "load pointer type must match memory index type");
   if (curr->isAtomic) {
@@ -916,12 +1031,12 @@ void FunctionValidator::visitLoad(Load* curr) {
 }
 
 void FunctionValidator::visitStore(Store* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.store memory must exist");
   if (curr->isAtomic) {
     shouldBeTrue(getModule()->features.hasAtomics(),
                  curr,
-                 "Atomic operation (atomics are disabled)");
+                 "Atomic operations require threads [--enable-threads]");
     shouldBeTrue(curr->valueType == Type::i32 || curr->valueType == Type::i64 ||
                    curr->valueType == Type::unreachable,
                  curr,
@@ -930,14 +1045,14 @@ void FunctionValidator::visitStore(Store* curr) {
   if (curr->valueType == Type::v128) {
     shouldBeTrue(getModule()->features.hasSIMD(),
                  curr,
-                 "SIMD operation (SIMD is disabled)");
+                 "SIMD operations require SIMD [--enable-simd]");
   }
   validateMemBytes(curr->bytes, curr->valueType, curr);
   validateAlignment(
     curr->align, curr->valueType, curr->bytes, curr->isAtomic, curr);
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "store pointer must match memory index type");
   shouldBeUnequal(curr->value->type,
@@ -953,15 +1068,15 @@ void FunctionValidator::visitStore(Store* curr) {
 }
 
 void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.atomicRMW memory must exist");
   shouldBeTrue(getModule()->features.hasAtomics(),
                curr,
-               "Atomic operation (atomics are disabled)");
+               "Atomic operations require threads [--enable-threads]");
   validateMemBytes(curr->bytes, curr->type, curr);
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "AtomicRMW pointer type must match memory index type");
   shouldBeEqualOrFirstIsUnreachable(curr->type,
@@ -973,15 +1088,15 @@ void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) {
 }
 
 void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.atomicCmpxchg memory must exist");
   shouldBeTrue(getModule()->features.hasAtomics(),
                curr,
-               "Atomic operation (atomics are disabled)");
+               "Atomic operations require threads [--enable-threads]");
   validateMemBytes(curr->bytes, curr->type, curr);
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "cmpxchg pointer must match memory index type");
   if (curr->expected->type != Type::unreachable &&
@@ -1006,16 +1121,16 @@ void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
 }
 
 void FunctionValidator::visitAtomicWait(AtomicWait* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.atomicWait memory must exist");
   shouldBeTrue(getModule()->features.hasAtomics(),
                curr,
-               "Atomic operation (atomics are disabled)");
+               "Atomic operations require threads [--enable-threads]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::i32), curr, "AtomicWait must have type i32");
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "AtomicWait pointer must match memory index type");
   shouldBeIntOrUnreachable(
@@ -1032,16 +1147,16 @@ void FunctionValidator::visitAtomicWait(AtomicWait* curr) {
 }
 
 void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.atomicNotify memory must exist");
   shouldBeTrue(getModule()->features.hasAtomics(),
                curr,
-               "Atomic operation (atomics are disabled)");
+               "Atomic operations require threads [--enable-threads]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::i32), curr, "AtomicNotify must have type i32");
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "AtomicNotify pointer must match memory index type");
   shouldBeEqualOrFirstIsUnreachable(
@@ -1052,11 +1167,11 @@ void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) {
 }
 
 void FunctionValidator::visitAtomicFence(AtomicFence* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  shouldBeFalse(
+    getModule()->memories.empty(), curr, "Memory operations require a memory");
   shouldBeTrue(getModule()->features.hasAtomics(),
                curr,
-               "Atomic operation (atomics are disabled)");
+               "Atomic operations require threads [--enable-threads]");
   shouldBeTrue(curr->order == 0,
                curr,
                "Currently only sequentially consistent atomics are supported, "
@@ -1064,8 +1179,9 @@ void FunctionValidator::visitAtomicFence(AtomicFence* curr) {
 }
 
 void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) {
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   shouldBeEqualOrFirstIsUnreachable(curr->vec->type,
                                     Type(Type::v128),
                                     curr,
@@ -1109,8 +1225,9 @@ void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) {
 }
 
 void FunctionValidator::visitSIMDReplace(SIMDReplace* curr) {
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::v128), curr, "replace_lane must have type v128");
   shouldBeEqualOrFirstIsUnreachable(curr->vec->type,
@@ -1151,8 +1268,9 @@ void FunctionValidator::visitSIMDReplace(SIMDReplace* curr) {
 }
 
 void FunctionValidator::visitSIMDShuffle(SIMDShuffle* curr) {
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::v128), curr, "i8x16.shuffle must have type v128");
   shouldBeEqualOrFirstIsUnreachable(
@@ -1165,8 +1283,9 @@ void FunctionValidator::visitSIMDShuffle(SIMDShuffle* curr) {
 }
 
 void FunctionValidator::visitSIMDTernary(SIMDTernary* curr) {
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::v128), curr, "SIMD ternary must have type v128");
   shouldBeEqualOrFirstIsUnreachable(
@@ -1178,8 +1297,9 @@ void FunctionValidator::visitSIMDTernary(SIMDTernary* curr) {
 }
 
 void FunctionValidator::visitSIMDShift(SIMDShift* curr) {
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::v128), curr, "vector shift must have type v128");
   shouldBeEqualOrFirstIsUnreachable(
@@ -1191,15 +1311,16 @@ void FunctionValidator::visitSIMDShift(SIMDShift* curr) {
 }
 
 void FunctionValidator::visitSIMDLoad(SIMDLoad* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.SIMDLoad memory must exist");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::v128), curr, "load_splat must have type v128");
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "load_splat address must match memory index type");
   Type memAlignType = Type::none;
@@ -1226,10 +1347,11 @@ void FunctionValidator::visitSIMDLoad(SIMDLoad* curr) {
 }
 
 void FunctionValidator::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
-  shouldBeTrue(
-    getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.SIMDLoadStoreLane memory must exist");
+  shouldBeTrue(getModule()->features.hasSIMD(),
+               curr,
+               "SIMD operations require SIMD [--enable-simd]");
   if (curr->isLoad()) {
     shouldBeEqualOrFirstIsUnreachable(
       curr->type, Type(Type::v128), curr, "loadX_lane must have type v128");
@@ -1239,7 +1361,7 @@ void FunctionValidator::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
   }
   shouldBeEqualOrFirstIsUnreachable(
     curr->ptr->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "loadX_lane or storeX_lane address must match memory index type");
   shouldBeEqualOrFirstIsUnreachable(
@@ -1279,14 +1401,15 @@ void FunctionValidator::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
 }
 
 void FunctionValidator::visitMemoryInit(MemoryInit* curr) {
-  shouldBeTrue(getModule()->features.hasBulkMemory(),
-               curr,
-               "Bulk memory operation (bulk memory is disabled)");
+  shouldBeTrue(
+    getModule()->features.hasBulkMemory(),
+    curr,
+    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::none), curr, "memory.init must have type none");
   shouldBeEqualOrFirstIsUnreachable(
     curr->dest->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "memory.init dest must match memory index type");
   shouldBeEqualOrFirstIsUnreachable(curr->offset->type,
@@ -1295,66 +1418,75 @@ void FunctionValidator::visitMemoryInit(MemoryInit* curr) {
                                     "memory.init offset must be an i32");
   shouldBeEqualOrFirstIsUnreachable(
     curr->size->type, Type(Type::i32), curr, "memory.init size must be an i32");
-  if (!shouldBeTrue(getModule()->memory.exists,
-                    curr,
-                    "Memory operations require a memory")) {
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  if (!shouldBeTrue(!!memory, curr, "memory.init memory must exist")) {
     return;
   }
-  shouldBeTrue(curr->segment < getModule()->memory.segments.size(),
+  shouldBeTrue(curr->segment < getModule()->dataSegments.size(),
                curr,
                "memory.init segment index out of bounds");
 }
 
 void FunctionValidator::visitDataDrop(DataDrop* curr) {
-  shouldBeTrue(getModule()->features.hasBulkMemory(),
-               curr,
-               "Bulk memory operation (bulk memory is disabled)");
+  shouldBeTrue(
+    getModule()->features.hasBulkMemory(),
+    curr,
+    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::none), curr, "data.drop must have type none");
-  if (!shouldBeTrue(getModule()->memory.exists,
-                    curr,
-                    "Memory operations require a memory")) {
+  if (!shouldBeFalse(getModule()->memories.empty(),
+                     curr,
+                     "Memory operations require a memory")) {
     return;
   }
-  shouldBeTrue(curr->segment < getModule()->memory.segments.size(),
+  shouldBeTrue(curr->segment < getModule()->dataSegments.size(),
                curr,
                "data.drop segment index out of bounds");
 }
 
 void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) {
-  shouldBeTrue(getModule()->features.hasBulkMemory(),
-               curr,
-               "Bulk memory operation (bulk memory is disabled)");
+  shouldBeTrue(
+    getModule()->features.hasBulkMemory(),
+    curr,
+    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::none), curr, "memory.copy must have type none");
+  auto* destMemory = getModule()->getMemoryOrNull(curr->destMemory);
+  shouldBeTrue(!!destMemory, curr, "memory.copy destMemory must exist");
+  auto* sourceMemory = getModule()->getMemoryOrNull(curr->sourceMemory);
+  shouldBeTrue(!!sourceMemory, curr, "memory.copy sourceMemory must exist");
   shouldBeEqualOrFirstIsUnreachable(
     curr->dest->type,
-    indexType(),
+    indexType(curr->destMemory),
     curr,
-    "memory.copy dest must match memory index type");
+    "memory.copy dest must match destMemory index type");
   shouldBeEqualOrFirstIsUnreachable(
     curr->source->type,
-    indexType(),
+    indexType(curr->sourceMemory),
     curr,
-    "memory.copy source must match memory index type");
+    "memory.copy source must match sourceMemory index type");
   shouldBeEqualOrFirstIsUnreachable(
     curr->size->type,
-    indexType(),
+    indexType(curr->destMemory),
     curr,
-    "memory.copy size must match memory index type");
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+    "memory.copy size must match destMemory index type");
+  shouldBeEqualOrFirstIsUnreachable(
+    curr->size->type,
+    indexType(curr->sourceMemory),
+    curr,
+    "memory.copy size must match destMemory index type");
 }
 
 void FunctionValidator::visitMemoryFill(MemoryFill* curr) {
-  shouldBeTrue(getModule()->features.hasBulkMemory(),
-               curr,
-               "Bulk memory operation (bulk memory is disabled)");
+  shouldBeTrue(
+    getModule()->features.hasBulkMemory(),
+    curr,
+    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::none), curr, "memory.fill must have type none");
   shouldBeEqualOrFirstIsUnreachable(
     curr->dest->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "memory.fill dest must match memory index type");
   shouldBeEqualOrFirstIsUnreachable(curr->value->type,
@@ -1363,11 +1495,11 @@ void FunctionValidator::visitMemoryFill(MemoryFill* curr) {
                                     "memory.fill value must be an i32");
   shouldBeEqualOrFirstIsUnreachable(
     curr->size->type,
-    indexType(),
+    indexType(curr->memory),
     curr,
     "memory.fill size must match memory index type");
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.fill memory must exist");
 }
 
 void FunctionValidator::validateMemBytes(uint8_t bytes,
@@ -1398,11 +1530,6 @@ void FunctionValidator::validateMemBytes(uint8_t bytes,
       break;
     case Type::unreachable:
       break;
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
       WASM_UNREACHABLE("unexpected type");
   }
@@ -1635,8 +1762,7 @@ void FunctionValidator::visitBinary(Binary* curr) {
     case SwizzleVecI8x16:
     case RelaxedSwizzleVecI8x16:
     case RelaxedQ15MulrSVecI16x8:
-    case DotI8x16I7x16SToVecI16x8:
-    case DotI8x16I7x16UToVecI16x8: {
+    case DotI8x16I7x16SToVecI16x8: {
       shouldBeEqualOrFirstIsUnreachable(
         curr->left->type, Type(Type::v128), curr, "v128 op");
       shouldBeEqualOrFirstIsUnreachable(
@@ -1977,15 +2103,15 @@ void FunctionValidator::visitReturn(Return* curr) {
 }
 
 void FunctionValidator::visitMemorySize(MemorySize* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.size memory must exist");
 }
 
 void FunctionValidator::visitMemoryGrow(MemoryGrow* curr) {
-  shouldBeTrue(
-    getModule()->memory.exists, curr, "Memory operations require a memory");
+  auto* memory = getModule()->getMemoryOrNull(curr->memory);
+  shouldBeTrue(!!memory, curr, "memory.grow memory must exist");
   shouldBeEqualOrFirstIsUnreachable(curr->delta->type,
-                                    indexType(),
+                                    indexType(curr->memory),
                                     curr,
                                     "memory.grow must match memory index type");
 }
@@ -1996,28 +2122,66 @@ void FunctionValidator::visitRefNull(RefNull* curr) {
   // features are enabled.
   shouldBeTrue(!getFunction() || getModule()->features.hasReferenceTypes(),
                curr,
-               "ref.null requires reference-types to be enabled");
+               "ref.null requires reference-types [--enable-reference-types]");
+  if (!shouldBeTrue(
+        curr->type.isNullable(), curr, "ref.null types must be nullable")) {
+    return;
+  }
   shouldBeTrue(
-    curr->type.isNullable(), curr, "ref.null types must be nullable");
+    curr->type.isNull(), curr, "ref.null must have a bottom heap type");
 }
 
 void FunctionValidator::visitRefIs(RefIs* curr) {
   shouldBeTrue(getModule()->features.hasReferenceTypes(),
                curr,
-               "ref.is_* requires reference-types to be enabled");
+               "ref.is_* requires reference-types [--enable-reference-types]");
   shouldBeTrue(curr->value->type == Type::unreachable ||
                  curr->value->type.isRef(),
                curr->value,
                "ref.is_*'s argument should be a reference type");
 }
 
+void FunctionValidator::visitRefAs(RefAs* curr) {
+  switch (curr->op) {
+    default:
+      // TODO: validate all the other ref.as_*
+      break;
+    case ExternInternalize: {
+      shouldBeTrue(getModule()->features.hasGC(),
+                   curr,
+                   "extern.internalize requries GC [--enable-gc]");
+      if (curr->type == Type::unreachable) {
+        return;
+      }
+      shouldBeSubType(curr->value->type,
+                      Type(HeapType::ext, Nullable),
+                      curr->value,
+                      "extern.internalize value should be an externref");
+      break;
+    }
+    case ExternExternalize: {
+      shouldBeTrue(getModule()->features.hasGC(),
+                   curr,
+                   "extern.externalize requries GC [--enable-gc]");
+      if (curr->type == Type::unreachable) {
+        return;
+      }
+      shouldBeSubType(curr->value->type,
+                      Type(HeapType::any, Nullable),
+                      curr->value,
+                      "extern.externalize value should be an anyref");
+      break;
+    }
+  }
+}
+
 void FunctionValidator::visitRefFunc(RefFunc* curr) {
   // If we are not in a function, this is a global location like a table. We
   // allow RefFunc there as we represent tables that way regardless of what
   // features are enabled.
   shouldBeTrue(!getFunction() || getModule()->features.hasReferenceTypes(),
                curr,
-               "ref.func requires reference-types to be enabled");
+               "ref.func requires reference-types [--enable-reference-types]");
   if (!info.validateGlobally) {
     return;
   }
@@ -2039,14 +2203,15 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) {
 }
 
 void FunctionValidator::visitRefEq(RefEq* curr) {
+  Type eqref = Type(HeapType::eq, Nullable);
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "ref.eq requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "ref.eq requires gc [--enable-gc]");
   shouldBeSubType(curr->left->type,
-                  Type::eqref,
+                  eqref,
                   curr->left,
                   "ref.eq's left argument should be a subtype of eqref");
   shouldBeSubType(curr->right->type,
-                  Type::eqref,
+                  eqref,
                   curr->right,
                   "ref.eq's right argument should be a subtype of eqref");
 }
@@ -2054,7 +2219,7 @@ void FunctionValidator::visitRefEq(RefEq* curr) {
 void FunctionValidator::visitTableGet(TableGet* curr) {
   shouldBeTrue(getModule()->features.hasReferenceTypes(),
                curr,
-               "table.get requires reference types to be enabled");
+               "table.get requires reference types [--enable-reference-types]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->index->type, Type(Type::i32), curr, "table.get index must be an i32");
   auto* table = getModule()->getTableOrNull(curr->table);
@@ -2068,7 +2233,7 @@ void FunctionValidator::visitTableGet(TableGet* curr) {
 void FunctionValidator::visitTableSet(TableSet* curr) {
   shouldBeTrue(getModule()->features.hasReferenceTypes(),
                curr,
-               "table.set requires reference types to be enabled");
+               "table.set requires reference types [--enable-reference-types]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->index->type, Type(Type::i32), curr, "table.set index must be an i32");
   auto* table = getModule()->getTableOrNull(curr->table);
@@ -2082,17 +2247,19 @@ void FunctionValidator::visitTableSet(TableSet* curr) {
 }
 
 void FunctionValidator::visitTableSize(TableSize* curr) {
-  shouldBeTrue(getModule()->features.hasReferenceTypes(),
-               curr,
-               "table.size requires reference types to be enabled");
+  shouldBeTrue(
+    getModule()->features.hasReferenceTypes(),
+    curr,
+    "table.size requires reference types [--enable-reference-types]");
   auto* table = getModule()->getTableOrNull(curr->table);
   shouldBeTrue(!!table, curr, "table.size table must exist");
 }
 
 void FunctionValidator::visitTableGrow(TableGrow* curr) {
-  shouldBeTrue(getModule()->features.hasReferenceTypes(),
-               curr,
-               "table.grow requires reference types to be enabled");
+  shouldBeTrue(
+    getModule()->features.hasReferenceTypes(),
+    curr,
+    "table.grow requires reference types [--enable-reference-types]");
   auto* table = getModule()->getTableOrNull(curr->table);
   if (shouldBeTrue(!!table, curr, "table.grow table must exist") &&
       curr->type != Type::unreachable) {
@@ -2124,7 +2291,7 @@ void FunctionValidator::noteRethrow(Name name, Expression* curr) {
 void FunctionValidator::visitTry(Try* curr) {
   shouldBeTrue(getModule()->features.hasExceptionHandling(),
                curr,
-               "try requires exception-handling to be enabled");
+               "try requires exception-handling [--enable-exception-handling]");
   if (curr->name.is()) {
     noteLabelName(curr->name);
   }
@@ -2159,29 +2326,6 @@ void FunctionValidator::visitTry(Try* curr) {
                 curr,
                 "try cannot have both catch and delegate at the same time");
 
-  // Given a catch body, find pops corresponding to the catch
-  auto findPops = [](Expression* expr) {
-    SmallVector<Pop*, 1> pops;
-    SmallVector<Expression*, 8> work;
-    work.push_back(expr);
-    while (!work.empty()) {
-      auto* curr = work.back();
-      work.pop_back();
-      if (auto* pop = curr->dynCast<Pop>()) {
-        pops.push_back(pop);
-      } else if (auto* try_ = curr->dynCast<Try>()) {
-        // We don't go into inner catch bodies; pops in inner catch bodies
-        // belong to the inner catches
-        work.push_back(try_->body);
-      } else {
-        for (auto* child : ChildIterator(curr)) {
-          work.push_back(child);
-        }
-      }
-    }
-    return pops;
-  };
-
   for (Index i = 0; i < curr->catchTags.size(); i++) {
     Name tagName = curr->catchTags[i];
     auto* tag = getModule()->getTagOrNull(tagName);
@@ -2190,7 +2334,7 @@ void FunctionValidator::visitTry(Try* curr) {
     }
 
     auto* catchBody = curr->catchBodies[i];
-    SmallVector<Pop*, 1> pops = findPops(catchBody);
+    auto pops = EHUtils::findPops(catchBody);
     if (tag->sig.params == Type::none) {
       if (!shouldBeTrue(pops.empty(), curr, "")) {
         getStream() << "catch's tag (" << tagName
@@ -2199,7 +2343,7 @@ void FunctionValidator::visitTry(Try* curr) {
     } else {
       if (shouldBeTrue(pops.size() == 1, curr, "")) {
         auto* pop = *pops.begin();
-        if (!shouldBeSubType(pop->type, tag->sig.params, curr, "")) {
+        if (!shouldBeSubType(tag->sig.params, pop->type, curr, "")) {
           getStream()
             << "catch's tag (" << tagName
             << ")'s pop doesn't have the same type as the tag's params";
@@ -2219,7 +2363,7 @@ void FunctionValidator::visitTry(Try* curr) {
 
   if (curr->hasCatchAll()) {
     auto* catchAllBody = curr->catchBodies.back();
-    shouldBeTrue(findPops(catchAllBody).empty(),
+    shouldBeTrue(EHUtils::findPops(catchAllBody).empty(),
                  curr,
                  "catch_all's body should not have pops");
   }
@@ -2232,9 +2376,10 @@ void FunctionValidator::visitTry(Try* curr) {
 }
 
 void FunctionValidator::visitThrow(Throw* curr) {
-  shouldBeTrue(getModule()->features.hasExceptionHandling(),
-               curr,
-               "throw requires exception-handling to be enabled");
+  shouldBeTrue(
+    getModule()->features.hasExceptionHandling(),
+    curr,
+    "throw requires exception-handling [--enable-exception-handling]");
   shouldBeEqual(curr->type,
                 Type(Type::unreachable),
                 curr,
@@ -2265,9 +2410,10 @@ void FunctionValidator::visitThrow(Throw* curr) {
 }
 
 void FunctionValidator::visitRethrow(Rethrow* curr) {
-  shouldBeTrue(getModule()->features.hasExceptionHandling(),
-               curr,
-               "rethrow requires exception-handling to be enabled");
+  shouldBeTrue(
+    getModule()->features.hasExceptionHandling(),
+    curr,
+    "rethrow requires exception-handling [--enable-exception-handling]");
   shouldBeEqual(curr->type,
                 Type(Type::unreachable),
                 curr,
@@ -2322,21 +2468,23 @@ void FunctionValidator::visitTupleExtract(TupleExtract* curr) {
 
 void FunctionValidator::visitCallRef(CallRef* curr) {
   validateReturnCall(curr);
-  shouldBeTrue(getModule()->features.hasTypedFunctionReferences(),
-               curr,
-               "call_ref requires typed-function-references to be enabled");
-  if (curr->target->type != Type::unreachable) {
-    if (shouldBeTrue(curr->target->type.isFunction(),
-                     curr,
-                     "call_ref target must be a function reference")) {
-      validateCallParamsAndResult(curr, curr->target->type.getHeapType());
-    }
+  shouldBeTrue(
+    getModule()->features.hasGC(), curr, "call_ref requires gc [--enable-gc]");
+  if (curr->target->type == Type::unreachable ||
+      (curr->target->type.isRef() &&
+       curr->target->type.getHeapType() == HeapType::nofunc)) {
+    return;
+  }
+  if (shouldBeTrue(curr->target->type.isFunction(),
+                   curr,
+                   "call_ref target must be a function reference")) {
+    validateCallParamsAndResult(curr, curr->target->type.getHeapType());
   }
 }
 
 void FunctionValidator::visitI31New(I31New* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "i31.new requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "i31.new requires gc [--enable-gc]");
   shouldBeSubType(curr->value->type,
                   Type::i32,
                   curr->value,
@@ -2346,96 +2494,60 @@ void FunctionValidator::visitI31New(I31New* curr) {
 void FunctionValidator::visitI31Get(I31Get* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "i31.get_s/u requires gc to be enabled");
+               "i31.get_s/u requires gc [--enable-gc]");
   shouldBeSubType(curr->i31->type,
-                  Type::i31ref,
+                  Type(HeapType::i31, Nullable),
                   curr->i31,
                   "i31.get_s/u's argument should be i31ref");
 }
 
 void FunctionValidator::visitRefTest(RefTest* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "ref.test requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "ref.test requires gc [--enable-gc]");
   if (curr->ref->type != Type::unreachable) {
     shouldBeTrue(
       curr->ref->type.isRef(), curr, "ref.test ref must have ref type");
   }
-  if (curr->rtt) {
-    if (curr->rtt->type != Type::unreachable) {
-      shouldBeTrue(
-        curr->rtt->type.isRtt(), curr, "ref.test rtt must have rtt type");
-    }
-    shouldBeEqual(curr->intendedType,
+  shouldBeUnequal(curr->intendedType,
                   HeapType(),
                   curr,
-                  "dynamic ref.test must not use intendedType field");
-  } else {
-    shouldBeUnequal(curr->intendedType,
-                    HeapType(),
-                    curr,
-                    "static ref.test must set intendedType field");
-    shouldBeTrue(
-      !curr->intendedType.isBasic(), curr, "ref.test must test a non-basic");
-  }
+                  "static ref.test must set intendedType field");
+  shouldBeTrue(
+    !curr->intendedType.isBasic(), curr, "ref.test must test a non-basic");
 }
 
 void FunctionValidator::visitRefCast(RefCast* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "ref.cast requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "ref.cast requires gc [--enable-gc]");
   if (curr->ref->type != Type::unreachable) {
     shouldBeTrue(
       curr->ref->type.isRef(), curr, "ref.cast ref must have ref type");
   }
-  if (curr->rtt) {
-    if (curr->rtt->type != Type::unreachable) {
-      shouldBeTrue(
-        curr->rtt->type.isRtt(), curr, "ref.cast rtt must have rtt type");
-    }
-    shouldBeEqual(curr->intendedType,
+  shouldBeUnequal(curr->intendedType,
                   HeapType(),
                   curr,
-                  "dynamic ref.cast must not use intendedType field");
-  } else {
-    shouldBeUnequal(curr->intendedType,
-                    HeapType(),
-                    curr,
-                    "static ref.cast must set intendedType field");
-    shouldBeTrue(
-      !curr->intendedType.isBasic(), curr, "ref.cast must cast to a non-basic");
-  }
+                  "static ref.cast must set intendedType field");
+  shouldBeTrue(
+    !curr->intendedType.isBasic(), curr, "ref.cast must cast to a non-basic");
 }
 
 void FunctionValidator::visitBrOn(BrOn* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "br_on_cast requires gc to be enabled");
+               "br_on_cast requires gc [--enable-gc]");
   if (curr->ref->type != Type::unreachable) {
     shouldBeTrue(
       curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type");
   }
   if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
-    if (curr->rtt) {
-      // Note that an unreachable rtt is not supported: the text and binary
-      // formats do not provide the type, so if it's unreachable we should not
-      // even create a br_on_cast in such a case, as we'd have no idea what it
-      // casts to.
-      shouldBeTrue(
-        curr->rtt->type.isRtt(), curr, "br_on_cast rtt must have rtt type");
-      shouldBeEqual(curr->intendedType,
+    shouldBeUnequal(curr->intendedType,
                     HeapType(),
                     curr,
-                    "dynamic br_on_cast* must not use intendedType field");
-    } else {
-      shouldBeUnequal(curr->intendedType,
-                      HeapType(),
-                      curr,
-                      "static br_on_cast* must set intendedType field");
-      shouldBeTrue(!curr->intendedType.isBasic(),
-                   curr,
-                   "br_on_cast* must cast to a non-basic");
-    }
+                    "static br_on_cast* must set intendedType field");
+    shouldBeTrue(!curr->intendedType.isBasic(),
+                 curr,
+                 "br_on_cast* must cast to a non-basic");
   } else {
-    shouldBeTrue(curr->rtt == nullptr, curr, "non-cast BrOn must not have rtt");
     shouldBeEqual(curr->intendedType,
                   HeapType(),
                   curr,
@@ -2444,58 +2556,14 @@ void FunctionValidator::visitBrOn(BrOn* curr) {
   noteBreak(curr->name, curr->getSentType(), curr);
 }
 
-void FunctionValidator::visitRttCanon(RttCanon* curr) {
-  shouldBeTrue(
-    getModule()->features.hasGC(), curr, "rtt.canon requires gc to be enabled");
-  shouldBeTrue(curr->type.isRtt(), curr, "rtt.canon must have RTT type");
-  auto rtt = curr->type.getRtt();
-  shouldBeEqual(rtt.depth,
-                Index(curr->type.getHeapType().getDepth()),
-                curr,
-                "rtt.canon must have the depth of its heap type");
-}
-
-void FunctionValidator::visitRttSub(RttSub* curr) {
-  shouldBeTrue(
-    getModule()->features.hasGC(), curr, "rtt.sub requires gc to be enabled");
-  shouldBeTrue(curr->type.isRtt(), curr, "rtt.sub must have RTT type");
-  if (curr->parent->type != Type::unreachable) {
-    shouldBeTrue(
-      curr->parent->type.isRtt(), curr, "rtt.sub parent must have RTT type");
-    auto parentRtt = curr->parent->type.getRtt();
-    auto rtt = curr->type.getRtt();
-    if (rtt.hasDepth() && parentRtt.hasDepth()) {
-      shouldBeEqual(rtt.depth,
-                    parentRtt.depth + 1,
-                    curr,
-                    "rtt.canon has a depth of 1 over the parent");
-    }
-    shouldBeTrue(HeapType::isSubType(rtt.heapType, parentRtt.heapType),
-                 curr,
-                 "rtt.sub parent must be a supertype");
-  }
-}
-
 void FunctionValidator::visitStructNew(StructNew* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "struct.new requires gc to be enabled");
+               "struct.new requires gc [--enable-gc]");
   if (curr->type == Type::unreachable) {
     return;
   }
-  if (curr->rtt) {
-    if (!shouldBeTrue(
-          curr->rtt->type.isRtt(), curr, "struct.new rtt must be rtt")) {
-      return;
-    }
-  }
   auto heapType = curr->type.getHeapType();
-  if (curr->rtt) {
-    shouldBeEqual(curr->rtt->type.getHeapType(),
-                  heapType,
-                  curr,
-                  "struct.new heap type must match rtt");
-  }
   if (!shouldBeTrue(
         heapType.isStruct(), curr, "struct.new heap type must be struct")) {
     return;
@@ -2530,8 +2598,8 @@ void FunctionValidator::visitStructNew(StructNew* curr) {
 void FunctionValidator::visitStructGet(StructGet* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "struct.get requires gc to be enabled");
-  if (curr->ref->type == Type::unreachable) {
+               "struct.get requires gc [--enable-gc]");
+  if (curr->type == Type::unreachable || curr->ref->type.isNull()) {
     return;
   }
   if (!shouldBeTrue(curr->ref->type.isStruct(),
@@ -2557,49 +2625,43 @@ void FunctionValidator::visitStructGet(StructGet* curr) {
 void FunctionValidator::visitStructSet(StructSet* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "struct.set requires gc to be enabled");
+               "struct.set requires gc [--enable-gc]");
   if (curr->ref->type == Type::unreachable) {
     return;
   }
-  if (!shouldBeTrue(curr->ref->type.isStruct(),
+  if (!shouldBeTrue(curr->ref->type.isRef(),
                     curr->ref,
-                    "struct.set ref must be a struct")) {
+                    "struct.set ref must be a reference type")) {
     return;
   }
-  if (curr->ref->type != Type::unreachable) {
-    const auto& fields = curr->ref->type.getHeapType().getStruct().fields;
-    shouldBeTrue(curr->index < fields.size(), curr, "bad struct.get field");
-    auto& field = fields[curr->index];
-    shouldBeSubType(curr->value->type,
-                    field.type,
-                    curr,
-                    "struct.set must have the proper type");
-    shouldBeEqual(
-      field.mutable_, Mutable, curr, "struct.set field must be mutable");
+  auto type = curr->ref->type.getHeapType();
+  if (type == HeapType::none) {
+    return;
   }
+  if (!shouldBeTrue(
+        type.isStruct(), curr->ref, "struct.set ref must be a struct")) {
+    return;
+  }
+  const auto& fields = type.getStruct().fields;
+  shouldBeTrue(curr->index < fields.size(), curr, "bad struct.get field");
+  auto& field = fields[curr->index];
+  shouldBeSubType(curr->value->type,
+                  field.type,
+                  curr,
+                  "struct.set must have the proper type");
+  shouldBeEqual(
+    field.mutable_, Mutable, curr, "struct.set field must be mutable");
 }
 
 void FunctionValidator::visitArrayNew(ArrayNew* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "array.new requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "array.new requires gc [--enable-gc]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->size->type, Type(Type::i32), curr, "array.new size must be an i32");
   if (curr->type == Type::unreachable) {
     return;
   }
-  if (curr->rtt) {
-    if (!shouldBeTrue(
-          curr->rtt->type.isRtt(), curr, "array.new rtt must be rtt")) {
-      return;
-    }
-  }
   auto heapType = curr->type.getHeapType();
-  if (curr->rtt) {
-    shouldBeEqual(curr->rtt->type.getHeapType(),
-                  heapType,
-                  curr,
-                  "array.new heap type must match rtt");
-  }
   if (!shouldBeTrue(
         heapType.isArray(), curr, "array.new heap type must be array")) {
     return;
@@ -2622,26 +2684,81 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) {
   }
 }
 
-void FunctionValidator::visitArrayInit(ArrayInit* curr) {
+void FunctionValidator::visitArrayNewSeg(ArrayNewSeg* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "array.init requires gc to be enabled");
+               "array.new_{data, elem} requires gc [--enable-gc]");
+  shouldBeEqualOrFirstIsUnreachable(
+    curr->offset->type,
+    Type(Type::i32),
+    curr,
+    "array.new_{data, elem} offset must be an i32");
+  shouldBeEqualOrFirstIsUnreachable(
+    curr->size->type,
+    Type(Type::i32),
+    curr,
+    "array.new_{data, elem} size must be an i32");
+  switch (curr->op) {
+    case NewData:
+      if (!shouldBeTrue(curr->segment < getModule()->dataSegments.size(),
+                        curr,
+                        "array.new_data segment index out of bounds")) {
+        return;
+      }
+      break;
+    case NewElem:
+      if (!shouldBeTrue(curr->segment < getModule()->elementSegments.size(),
+                        curr,
+                        "array.new_elem segment index out of bounds")) {
+        return;
+      }
+      break;
+    default:
+      WASM_UNREACHABLE("unexpected op");
+  }
   if (curr->type == Type::unreachable) {
     return;
   }
-  if (curr->rtt) {
-    if (!shouldBeTrue(
-          curr->rtt->type.isRtt(), curr, "array.init rtt must be rtt")) {
-      return;
-    }
+  if (!shouldBeTrue(
+        curr->type.isRef(),
+        curr,
+        "array.new_{data, elem} type should be an array reference")) {
+    return;
   }
   auto heapType = curr->type.getHeapType();
-  if (curr->rtt) {
-    shouldBeEqual(curr->rtt->type.getHeapType(),
-                  heapType,
-                  curr,
-                  "array.init heap type must match rtt");
+  if (!shouldBeTrue(
+        heapType.isArray(),
+        curr,
+        "array.new_{data, elem} type shoudl be an array reference")) {
+    return;
+  }
+  auto elemType = heapType.getArray().element.type;
+  switch (curr->op) {
+    case NewData:
+      shouldBeTrue(elemType.isNumber(),
+                   curr,
+                   "array.new_data result element type should be numeric");
+      break;
+    case NewElem:
+      shouldBeSubType(getModule()->elementSegments[curr->segment]->type,
+                      elemType,
+                      curr,
+                      "array.new_elem segment type should be a subtype of the "
+                      "result element type");
+      break;
+    default:
+      WASM_UNREACHABLE("unexpected op");
+  }
+}
+
+void FunctionValidator::visitArrayInit(ArrayInit* curr) {
+  shouldBeTrue(getModule()->features.hasGC(),
+               curr,
+               "array.init requires gc [--enable-gc]");
+  if (curr->type == Type::unreachable) {
+    return;
   }
+  auto heapType = curr->type.getHeapType();
   if (!shouldBeTrue(
         heapType.isArray(), curr, "array.init heap type must be array")) {
     return;
@@ -2657,13 +2774,28 @@ void FunctionValidator::visitArrayInit(ArrayInit* curr) {
 
 void FunctionValidator::visitArrayGet(ArrayGet* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "array.get requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "array.get requires gc [--enable-gc]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->index->type, Type(Type::i32), curr, "array.get index must be an i32");
   if (curr->type == Type::unreachable) {
     return;
   }
-  const auto& element = curr->ref->type.getHeapType().getArray().element;
+  if (!shouldBeSubType(curr->ref->type,
+                       Type(HeapType::array, Nullable),
+                       curr,
+                       "array.get target should be an array reference")) {
+    return;
+  }
+  auto heapType = curr->ref->type.getHeapType();
+  if (heapType == HeapType::none) {
+    return;
+  }
+  if (!shouldBeTrue(heapType != HeapType::array,
+                    curr,
+                    "array.get target should be a specific array reference")) {
+    return;
+  }
+  const auto& element = heapType.getArray().element;
   // If the type is not packed, it must be marked internally as unsigned, by
   // convention.
   if (element.type != Type::i32 || element.packedType == Field::not_packed) {
@@ -2675,12 +2807,27 @@ void FunctionValidator::visitArrayGet(ArrayGet* curr) {
 
 void FunctionValidator::visitArraySet(ArraySet* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "array.set requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "array.set requires gc [--enable-gc]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->index->type, Type(Type::i32), curr, "array.set index must be an i32");
   if (curr->type == Type::unreachable) {
     return;
   }
+  if (!shouldBeSubType(curr->ref->type,
+                       Type(HeapType::array, Nullable),
+                       curr,
+                       "array.set target should be an array reference")) {
+    return;
+  }
+  auto heapType = curr->ref->type.getHeapType();
+  if (heapType == HeapType::none) {
+    return;
+  }
+  if (!shouldBeTrue(heapType != HeapType::array,
+                    curr,
+                    "array.set target should be a specific array reference")) {
+    return;
+  }
   const auto& element = curr->ref->type.getHeapType().getArray().element;
   shouldBeSubType(curr->value->type,
                   element.type,
@@ -2691,15 +2838,19 @@ void FunctionValidator::visitArraySet(ArraySet* curr) {
 
 void FunctionValidator::visitArrayLen(ArrayLen* curr) {
   shouldBeTrue(
-    getModule()->features.hasGC(), curr, "array.len requires gc to be enabled");
+    getModule()->features.hasGC(), curr, "array.len requires gc [--enable-gc]");
   shouldBeEqualOrFirstIsUnreachable(
     curr->type, Type(Type::i32), curr, "array.len result must be an i32");
+  shouldBeSubType(curr->ref->type,
+                  Type(HeapType::array, Nullable),
+                  curr,
+                  "array.len argument must be an array reference");
 }
 
 void FunctionValidator::visitArrayCopy(ArrayCopy* curr) {
   shouldBeTrue(getModule()->features.hasGC(),
                curr,
-               "array.copy requires gc to be enabled");
+               "array.copy requires gc [--enable-gc]");
   shouldBeEqualOrFirstIsUnreachable(curr->srcIndex->type,
                                     Type(Type::i32),
                                     curr,
@@ -2711,9 +2862,33 @@ void FunctionValidator::visitArrayCopy(ArrayCopy* curr) {
   if (curr->type == Type::unreachable) {
     return;
   }
-  const auto& srcElement = curr->srcRef->type.getHeapType().getArray().element;
-  const auto& destElement =
-    curr->destRef->type.getHeapType().getArray().element;
+  if (!shouldBeSubType(curr->srcRef->type,
+                       Type(HeapType::array, Nullable),
+                       curr,
+                       "array.copy source should be an array reference") ||
+      !shouldBeSubType(curr->destRef->type,
+                       Type(HeapType::array, Nullable),
+                       curr,
+                       "array.copy destination should be an array reference")) {
+    return;
+  }
+  auto srcHeapType = curr->srcRef->type.getHeapType();
+  auto destHeapType = curr->destRef->type.getHeapType();
+  if (srcHeapType == HeapType::none || destHeapType == HeapType::none) {
+    return;
+  }
+  if (!shouldBeTrue(
+        srcHeapType != HeapType::array,
+        curr,
+        "array.copy source needs to be a specific array reference") ||
+      !shouldBeTrue(
+        srcHeapType != HeapType::array,
+        curr,
+        "array.copy destination needs to be a specific array reference")) {
+    return;
+  }
+  const auto& srcElement = srcHeapType.getArray().element;
+  const auto& destElement = destHeapType.getArray().element;
   shouldBeSubType(srcElement.type,
                   destElement.type,
                   curr,
@@ -2738,10 +2913,6 @@ void FunctionValidator::visitFunction(Function* curr) {
   }
   for (const auto& var : curr->vars) {
     features |= var.getFeatures();
-    bool valid = getModule()->features.hasGCNNLocals()
-                   ? var.isDefaultableOrNonNullable()
-                   : var.isDefaultable();
-    shouldBeTrue(valid, var, "vars must be defaultable");
   }
   shouldBeTrue(features <= getModule()->features,
                curr->name,
@@ -2774,33 +2945,55 @@ void FunctionValidator::visitFunction(Function* curr) {
     Name name = pair.second;
     shouldBeTrue(seen.insert(name).second, name, "local names must be unique");
   }
+
+  if (getModule()->features.hasGC()) {
+    // If we have non-nullable locals, verify that local.get are valid.
+    if (!getModule()->features.hasGCNNLocals()) {
+      // Without the special GCNNLocals feature, we implement the spec rules,
+      // that is, a set allows gets until the end of the block.
+      LocalStructuralDominance info(curr, *getModule());
+      for (auto index : info.nonDominatingIndices) {
+        shouldBeTrue(!curr->getLocalType(index).isNonNullable(),
+                     index,
+                     "non-nullable local's sets must dominate gets");
+      }
+    } else {
+      // With the special GCNNLocals feature, we allow gets anywhere, so long as
+      // we can prove they cannot read the null value. (TODO: remove this once
+      // the spec is stable).
+      //
+      // This is slow, so only do it if we find such locals exist at all.
+      bool hasNNLocals = false;
+      for (const auto& var : curr->vars) {
+        if (!var.isDefaultable()) {
+          hasNNLocals = true;
+          break;
+        }
+      }
+      if (hasNNLocals) {
+        LocalGraph graph(curr);
+        for (auto& [get, sets] : graph.getSetses) {
+          auto index = get->index;
+          // It is always ok to read nullable locals, and it is always ok to
+          // read params even if they are non-nullable.
+          if (curr->getLocalType(index).isDefaultable() ||
+              curr->isParam(index)) {
+            continue;
+          }
+          for (auto* set : sets) {
+            shouldBeTrue(!!set, index, "non-nullable local must not read null");
+          }
+        }
+      }
+    }
+  }
 }
 
 static bool checkSegmentOffset(Expression* curr,
                                Address add,
                                Address max,
                                FeatureSet features) {
-  if (!Properties::isValidInConstantExpression(curr, features)) {
-    return false;
-  }
-  auto* c = curr->dynCast<Const>();
-  if (!c) {
-    // Unless the instruction is actually a const instruction, we don't
-    // currently try to evaluate it.
-    // TODO: Attempt to evaluate other expressions that might also be const
-    // such as `global.get` or more complex instruction sequences involving
-    // add/sub/mul/etc.
-    return true;
-  }
-  uint64_t raw = c->value.getInteger();
-  if (raw > std::numeric_limits<Address::address32_t>::max()) {
-    return false;
-  }
-  if (raw + uint64_t(add) > std::numeric_limits<Address::address32_t>::max()) {
-    return false;
-  }
-  Address offset = raw;
-  return offset + add <= max;
+  return Properties::isValidInConstantExpression(curr, features);
 }
 
 void FunctionValidator::validateAlignment(
@@ -2840,11 +3033,6 @@ void FunctionValidator::validateAlignment(
     case Type::v128:
     case Type::unreachable:
       break;
-    case Type::funcref:
-    case Type::anyref:
-    case Type::eqref:
-    case Type::i31ref:
-    case Type::dataref:
     case Type::none:
       WASM_UNREACHABLE("invalid type");
   }
@@ -2868,15 +3056,19 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) {
       ReFinalizeNode().visit(curr);
       auto newType = curr->type;
       if (newType != oldType) {
-        // We accept concrete => undefined,
+        // We accept concrete => undefined on control flow structures:
         // e.g.
         //
         //  (drop (block (result i32) (unreachable)))
         //
-        // The block has an added type, not derived from the ast itself, so it
-        // is ok for it to be either i32 or unreachable.
+        // The block has a type annotated on it, which can make its unreachable
+        // contents have a concrete type. Refinalize will make it unreachable,
+        // so both are valid here.
+        bool validControlFlowStructureChange =
+          Properties::isControlFlowStructure(curr) && oldType.isConcrete() &&
+          newType == Type::unreachable;
         if (!Type::isSubType(newType, oldType) &&
-            !(oldType.isConcrete() && newType == Type::unreachable)) {
+            !validControlFlowStructureChange) {
           std::ostringstream ss;
           ss << "stale type found in " << scope << " on " << curr
              << "\n(marked as " << oldType << ", should be " << newType
@@ -2906,8 +3098,8 @@ static void validateImports(Module& module, ValidationInfo& info) {
     if (curr->getResults().isTuple()) {
       info.shouldBeTrue(module.features.hasMultivalue(),
                         curr->name,
-                        "Imported multivalue function "
-                        "(multivalue is not enabled)");
+                        "Imported multivalue function requires multivalue "
+                        "[--enable-multivalue]");
     }
     if (info.validateWeb) {
       for (const auto& param : curr->getParams()) {
@@ -2936,8 +3128,10 @@ static void validateImports(Module& module, ValidationInfo& info) {
   });
   ModuleUtils::iterImportedGlobals(module, [&](Global* curr) {
     if (!module.features.hasMutableGlobals()) {
-      info.shouldBeFalse(
-        curr->mutable_, curr->name, "Imported global cannot be mutable");
+      info.shouldBeFalse(curr->mutable_,
+                         curr->name,
+                         "Imported mutable global requires mutable-globals "
+                         "[--enable-mutable-globals]");
     }
     info.shouldBeFalse(
       curr->type.isTuple(), curr->name, "Imported global cannot be tuple");
@@ -2966,8 +3160,10 @@ static void validateExports(Module& module, ValidationInfo& info) {
     } else if (curr->kind == ExternalKind::Global) {
       if (Global* g = module.getGlobalOrNull(curr->value)) {
         if (!module.features.hasMutableGlobals()) {
-          info.shouldBeFalse(
-            g->mutable_, g->name, "Exported global cannot be mutable");
+          info.shouldBeFalse(g->mutable_,
+                             g->name,
+                             "Exported mutable global requires mutable-globals "
+                             "[--enable-mutable-globals]");
         }
         info.shouldBeFalse(
           g->type.isTuple(), g->name, "Exported global cannot be tuple");
@@ -2990,7 +3186,7 @@ static void validateExports(Module& module, ValidationInfo& info) {
                         name,
                         "module table exports must be found");
     } else if (exp->kind == ExternalKind::Memory) {
-      info.shouldBeTrue(name == Name("0") || name == module.memory.name,
+      info.shouldBeTrue(module.getMemoryOrNull(name),
                         name,
                         "module memory exports must be found");
     } else if (exp->kind == ExternalKind::Tag) {
@@ -3031,78 +3227,90 @@ static void validateGlobals(Module& module, ValidationInfo& info) {
   });
 }
 
-static void validateMemory(Module& module, ValidationInfo& info) {
-  auto& curr = module.memory;
-  info.shouldBeFalse(
-    curr.initial > curr.max, "memory", "memory max >= initial");
-  if (curr.is64()) {
-    info.shouldBeTrue(module.features.hasMemory64(),
-                      "memory",
-                      "memory is 64-bit, but memory64 is disabled");
-  } else {
-    info.shouldBeTrue(curr.initial <= Memory::kMaxSize32,
-                      "memory",
-                      "initial memory must be <= 4GB");
-    info.shouldBeTrue(!curr.hasMax() || curr.max <= Memory::kMaxSize32,
-                      "memory",
-                      "max memory must be <= 4GB, or unlimited");
+static void validateMemories(Module& module, ValidationInfo& info) {
+  if (module.memories.size() > 1) {
+    info.shouldBeTrue(
+      module.features.hasMultiMemories(),
+      "memory",
+      "multiple memories require multi-memories [--enable-multi-memories]");
   }
-  info.shouldBeTrue(!curr.shared || curr.hasMax(),
-                    "memory",
-                    "shared memory must have max size");
-  if (curr.shared) {
-    info.shouldBeTrue(module.features.hasAtomics(),
+  for (auto& memory : module.memories) {
+    if (memory->hasMax()) {
+      info.shouldBeFalse(
+        memory->initial > memory->max, "memory", "memory max >= initial");
+    }
+    if (memory->is64()) {
+      info.shouldBeTrue(module.features.hasMemory64(),
+                        "memory",
+                        "64-bit memories require memory64 [--enable-memory64]");
+    } else {
+      info.shouldBeTrue(memory->initial <= Memory::kMaxSize32,
+                        "memory",
+                        "initial memory must be <= 4GB");
+      info.shouldBeTrue(!memory->hasMax() || memory->max <= Memory::kMaxSize32,
+                        "memory",
+                        "max memory must be <= 4GB, or unlimited");
+    }
+    info.shouldBeTrue(!memory->shared || memory->hasMax(),
                       "memory",
-                      "memory is shared, but atomics are disabled");
-  }
-  for (auto& segment : curr.segments) {
-    auto size = segment.data.size();
-    if (segment.isPassive) {
-      info.shouldBeTrue(module.features.hasBulkMemory(),
-                        segment.offset,
-                        "nonzero segment flags (bulk memory is disabled)");
-      info.shouldBeEqual(segment.offset,
+                      "shared memory must have max size");
+    if (memory->shared) {
+      info.shouldBeTrue(module.features.hasAtomics(),
+                        "memory",
+                        "shared memory requires threads [--enable-threads]");
+    }
+  }
+}
+
+static void validateDataSegments(Module& module, ValidationInfo& info) {
+  for (auto& segment : module.dataSegments) {
+    auto size = segment->data.size();
+    if (segment->isPassive) {
+      info.shouldBeTrue(
+        module.features.hasBulkMemory(),
+        segment->offset,
+        "nonzero segment flags require bulk memory [--enable-bulk-memory]");
+      info.shouldBeEqual(segment->offset,
                          (Expression*)nullptr,
-                         segment.offset,
+                         segment->offset,
                          "passive segment should not have an offset");
     } else {
-      if (curr.is64()) {
-        if (!info.shouldBeEqual(segment.offset->type,
+      auto memory = module.getMemoryOrNull(segment->memory);
+      if (!info.shouldBeTrue(memory != nullptr,
+                             "segment",
+                             "active segment must have a valid memory name")) {
+        continue;
+      }
+      if (memory->is64()) {
+        if (!info.shouldBeEqual(segment->offset->type,
                                 Type(Type::i64),
-                                segment.offset,
+                                segment->offset,
                                 "segment offset should be i64")) {
           continue;
         }
       } else {
-        if (!info.shouldBeEqual(segment.offset->type,
+        if (!info.shouldBeEqual(segment->offset->type,
                                 Type(Type::i32),
-                                segment.offset,
+                                segment->offset,
                                 "segment offset should be i32")) {
           continue;
         }
       }
-      info.shouldBeTrue(checkSegmentOffset(segment.offset,
-                                           segment.data.size(),
-                                           curr.initial * Memory::kPageSize,
+      info.shouldBeTrue(checkSegmentOffset(segment->offset,
+                                           segment->data.size(),
+                                           memory->initial * Memory::kPageSize,
                                            module.features),
-                        segment.offset,
+                        segment->offset,
                         "memory segment offset should be reasonable");
-      if (segment.offset->is<Const>()) {
-        auto start = segment.offset->cast<Const>()->value.getUnsigned();
-        auto end = start + size;
-        info.shouldBeTrue(end <= curr.initial * Memory::kPageSize,
-                          segment.data.size(),
-                          "segment size should fit in memory (end)");
+      FunctionValidator(module, &info).validate(segment->offset);
+      // If the memory is imported we don't actually know its initial size.
+      // Specifically wasm dll's import a zero sized memory which is perfectly
+      // valid.
+      if (!memory->imported()) {
+        info.shouldBeTrue(size <= memory->initial * Memory::kPageSize,
+                          segment->data.size(),
+                          "segment size should fit in memory (initial)");
       }
-      FunctionValidator(module, &info).validate(segment.offset);
-    }
-    // If the memory is imported we don't actually know its initial size.
-    // Specifically wasm dll's import a zero sized memory which is perfectly
-    // valid.
-    if (!curr.imported()) {
-      info.shouldBeTrue(size <= curr.initial * Memory::kPageSize,
-                        segment.data.size(),
-                        "segment size should fit in memory (initial)");
     }
   }
 }
@@ -3117,7 +3325,7 @@ static void validateTables(Module& module, ValidationInfo& info) {
                       "--enable-reference-types)");
     if (!module.tables.empty()) {
       auto& table = module.tables.front();
-      info.shouldBeTrue(table->type == Type::funcref,
+      info.shouldBeTrue(table->type == Type(HeapType::func, Nullable),
                         "table",
                         "Only funcref is valid for table type (when reference "
                         "types are disabled)");
@@ -3137,6 +3345,8 @@ static void validateTables(Module& module, ValidationInfo& info) {
     }
   }
 
+  Type externref = Type(HeapType::ext, Nullable);
+  Type funcref = Type(HeapType::func, Nullable);
   for (auto& table : module.tables) {
     info.shouldBeTrue(table->initial <= table->max,
                       "table",
@@ -3146,26 +3356,24 @@ static void validateTables(Module& module, ValidationInfo& info) {
       "table",
       "Non-nullable reference types are not yet supported for tables");
     if (!module.features.hasGC()) {
-      info.shouldBeTrue(table->type.isFunction() || table->type == Type::anyref,
+      info.shouldBeTrue(table->type.isFunction() || table->type == externref,
                         "table",
-                        "Only function reference types or anyref are valid "
+                        "Only function reference types or externref are valid "
                         "for table type (when GC is disabled)");
     }
-    if (!module.features.hasTypedFunctionReferences()) {
-      info.shouldBeTrue(table->type == Type::funcref ||
-                          table->type == Type::anyref,
+    if (!module.features.hasGC()) {
+      info.shouldBeTrue(table->type == funcref || table->type == externref,
                         "table",
-                        "Only funcref and anyref are valid for table type "
-                        "(when typed-function references are disabled)");
+                        "Only funcref and externref are valid for table type "
+                        "(when gc is disabled)");
     }
   }
 
   for (auto& segment : module.elementSegments) {
     // Since element segment items need to be constant expressions, that leaves
-    // us with ref.null, ref.func and global.get. The GC proposal adds rtt.canon
-    // and rtt.sub to the list, but Binaryen doesn't consider RTTs as reference-
-    // types yet. As a result, the only possible type for element segments will
-    // be function references.
+    // us with ref.null, ref.func and global.get. As a result, the only possible
+    // type for element segments will be function references.
+    // TODO: This is not true! Allow GC data here (#4846).
     info.shouldBeTrue(segment->type.isFunction(),
                       "elem",
                       "element segment type must be of function type.");
@@ -3192,18 +3400,10 @@ static void validateTables(Module& module, ValidationInfo& info) {
                                            module.features),
                         segment->offset,
                         "table segment offset should be reasonable");
-      if (module.features.hasTypedFunctionReferences()) {
-        info.shouldBeTrue(
-          Type::isSubType(segment->type, table->type),
-          "elem",
-          "element segment type must be a subtype of the table type");
-      } else {
-        info.shouldBeEqual(
-          segment->type,
-          table->type,
-          "elem",
-          "element segment type must be the same as the table type");
-      }
+      info.shouldBeTrue(
+        Type::isSubType(segment->type, table->type),
+        "elem",
+        "element segment type must be a subtype of the table type");
       validator.validate(segment->offset);
     } else {
       info.shouldBeTrue(!segment->offset,
@@ -3237,9 +3437,10 @@ static void validateTables(Module& module, ValidationInfo& info) {
 
 static void validateTags(Module& module, ValidationInfo& info) {
   if (!module.tags.empty()) {
-    info.shouldBeTrue(module.features.hasExceptionHandling(),
-                      module.tags[0]->name,
-                      "Module has tags (exception-handling is disabled)");
+    info.shouldBeTrue(
+      module.features.hasExceptionHandling(),
+      module.tags[0]->name,
+      "Tags require exception-handling [--enable-exception-handling]");
   }
   for (auto& curr : module.tags) {
     info.shouldBeEqual(curr->sig.results,
@@ -3247,9 +3448,10 @@ static void validateTags(Module& module, ValidationInfo& info) {
                        curr->name,
                        "Tag type's result type should be none");
     if (curr->sig.params.isTuple()) {
-      info.shouldBeTrue(module.features.hasMultivalue(),
-                        curr->name,
-                        "Multivalue tag type (multivalue is not enabled)");
+      info.shouldBeTrue(
+        module.features.hasMultivalue(),
+        curr->name,
+        "Multivalue tag type requires multivalue [--enable-multivalue]");
     }
     for (const auto& param : curr->sig.params) {
       info.shouldBeTrue(param.isConcrete(),
@@ -3299,7 +3501,8 @@ bool WasmValidator::validate(Module& module, Flags flags) {
     validateImports(module, info);
     validateExports(module, info);
     validateGlobals(module, info);
-    validateMemory(module, info);
+    validateMemories(module, info);
+    validateDataSegments(module, info);
     validateTables(module, info);
     validateTags(module, info);
     validateModule(module, info);
@@ -3319,4 +3522,18 @@ bool WasmValidator::validate(Module& module, Flags flags) {
   return info.valid.load();
 }
 
+bool WasmValidator::validate(Function* func, Module& module, Flags flags) {
+  ValidationInfo info(module);
+  info.validateWeb = (flags & Web) != 0;
+  info.validateGlobally = (flags & Globally) != 0;
+  info.quiet = (flags & Quiet) != 0;
+  FunctionValidator(module, &info).validate(func);
+  // print all the data
+  if (!info.valid.load() && !info.quiet) {
+    std::cerr << info.getStream(func).str();
+    std::cerr << info.getStream(nullptr).str();
+  }
+  return info.valid.load();
+}
+
 } // namespace wasm
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index ca96913..a92b623 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -47,18 +47,14 @@ const char* ReferenceTypesFeature = "reference-types";
 const char* MultivalueFeature = "multivalue";
 const char* GCFeature = "gc";
 const char* Memory64Feature = "memory64";
-const char* TypedFunctionReferencesFeature = "typed-function-references";
 const char* RelaxedSIMDFeature = "relaxed-simd";
 const char* ExtendedConstFeature = "extended-const";
+const char* StringsFeature = "strings";
+const char* MultiMemoriesFeature = "multi-memories";
 } // namespace UserSections
 } // namespace BinaryConsts
 
-Name MEMORY_BASE("__memory_base");
-Name TABLE_BASE("__table_base");
 Name STACK_POINTER("__stack_pointer");
-Name GET_TEMP_RET0("getTempRet0");
-Name SET_TEMP_RET0("setTempRet0");
-Name NEW_SIZE("newSize");
 Name MODULE("module");
 Name START("start");
 Name GLOBAL("global");
@@ -182,7 +178,7 @@ void Block::finalize() {
     return;
   }
   // The default type is what is at the end. Next we need to see if breaks and/
-  // or unreachabitily change that.
+  // or unreachability change that.
   type = list.back()->type;
   if (!name.is()) {
     // Nothing branches here, so this is easy.
@@ -260,6 +256,12 @@ void Break::finalize() {
     if (condition->type == Type::unreachable) {
       type = Type::unreachable;
     } else if (value) {
+      // N.B. This is not correct wrt the spec, which mandates that it be the
+      // type of the block we target. In practice this does not matter because
+      // the br_if return value is not really used in the wild. To fix this,
+      // we'd need to do something like what we do for local.tee's type, which
+      // is to fix it up in a way that is aware of function-level context and
+      // not just the instruction itself (which would be a pain).
       type = value->type;
     } else {
       type = Type::none;
@@ -794,7 +796,10 @@ void MemoryGrow::finalize() {
   }
 }
 
-void RefNull::finalize(HeapType heapType) { type = Type(heapType, Nullable); }
+void RefNull::finalize(HeapType heapType) {
+  assert(heapType.isBottom());
+  type = Type(heapType, Nullable);
+}
 
 void RefNull::finalize(Type type_) { type = type_; }
 
@@ -902,7 +907,7 @@ void I31New::finalize() {
   if (value->type == Type::unreachable) {
     type = Type::unreachable;
   } else {
-    type = Type::i31ref;
+    type = Type(HeapType::i31, NonNullable);
   }
 }
 
@@ -930,36 +935,25 @@ void CallRef::finalize(Type type_) {
 }
 
 void RefTest::finalize() {
-  if (ref->type == Type::unreachable ||
-      (rtt && rtt->type == Type::unreachable)) {
+  if (ref->type == Type::unreachable) {
     type = Type::unreachable;
   } else {
     type = Type::i32;
   }
 }
 
-HeapType RefTest::getIntendedType() {
-  return rtt ? rtt->type.getHeapType() : intendedType;
-}
-
 void RefCast::finalize() {
-  if (ref->type == Type::unreachable ||
-      (rtt && rtt->type == Type::unreachable)) {
+  if (ref->type == Type::unreachable) {
     type = Type::unreachable;
   } else {
     // The output of ref.cast may be null if the input is null (in that case the
     // null is passed through).
-    type = Type(getIntendedType(), ref->type.getNullability());
+    type = Type(intendedType, ref->type.getNullability());
   }
 }
 
-HeapType RefCast::getIntendedType() {
-  return rtt ? rtt->type.getHeapType() : intendedType;
-}
-
 void BrOn::finalize() {
-  if (ref->type == Type::unreachable ||
-      (rtt && rtt->type == Type::unreachable)) {
+  if (ref->type == Type::unreachable) {
     type = Type::unreachable;
     return;
   }
@@ -983,7 +977,7 @@ void BrOn::finalize() {
     case BrOnCastFail:
       // If we do not branch, the cast worked, and we have something of the cast
       // type.
-      type = Type(getIntendedType(), NonNullable);
+      type = Type(intendedType, NonNullable);
       break;
     case BrOnNonFunc:
       type = Type(HeapType::func, NonNullable);
@@ -999,11 +993,6 @@ void BrOn::finalize() {
   }
 }
 
-HeapType BrOn::getIntendedType() {
-  assert(op == BrOnCast || op == BrOnCastFail);
-  return rtt ? rtt->type.getHeapType() : intendedType;
-}
-
 Type BrOn::getSentType() {
   switch (op) {
     case BrOnNull:
@@ -1021,13 +1010,13 @@ Type BrOn::getSentType() {
       if (ref->type == Type::unreachable) {
         return Type::unreachable;
       }
-      return Type(getIntendedType(), NonNullable);
+      return Type(intendedType, NonNullable);
     case BrOnFunc:
-      return Type::funcref;
+      return Type(HeapType::func, NonNullable);
     case BrOnData:
-      return Type::dataref;
+      return Type(HeapType::data, NonNullable);
     case BrOnI31:
-      return Type::i31ref;
+      return Type(HeapType::i31, NonNullable);
     case BrOnCastFail:
     case BrOnNonFunc:
     case BrOnNonData:
@@ -1038,37 +1027,16 @@ Type BrOn::getSentType() {
   }
 }
 
-void RttCanon::finalize() {
-  // Nothing to do - the type must have been set already during construction.
-}
-
-void RttSub::finalize() {
-  if (parent->type == Type::unreachable) {
-    type = Type::unreachable;
-  }
-  // Else nothing to do - the type must have been set already during
-  // construction.
-}
-
 void StructNew::finalize() {
-  if (rtt && rtt->type == Type::unreachable) {
-    type = Type::unreachable;
-    return;
-  }
   if (handleUnreachableOperands(this)) {
     return;
   }
-  // A dynamic StructNew infers the type from the rtt. A static one has the type
-  // already in the type field.
-  if (rtt) {
-    type = Type(rtt->type.getHeapType(), NonNullable);
-  }
 }
 
 void StructGet::finalize() {
   if (ref->type == Type::unreachable) {
     type = Type::unreachable;
-  } else {
+  } else if (!ref->type.isNull()) {
     type = ref->type.getHeapType().getStruct().fields[index].type;
   }
 }
@@ -1082,41 +1050,31 @@ void StructSet::finalize() {
 }
 
 void ArrayNew::finalize() {
-  if ((rtt && rtt->type == Type::unreachable) ||
-      size->type == Type::unreachable ||
+  if (size->type == Type::unreachable ||
       (init && init->type == Type::unreachable)) {
     type = Type::unreachable;
-    return;
-  }
-  // A dynamic ArrayNew infers the type from the rtt. A static one has the type
-  // already in the type field.
-  if (rtt) {
-    type = Type(rtt->type.getHeapType(), NonNullable);
   }
 }
 
-void ArrayInit::finalize() {
-  if (rtt && rtt->type == Type::unreachable) {
+void ArrayNewSeg::finalize() {
+  if (offset->type == Type::unreachable || size->type == Type::unreachable) {
     type = Type::unreachable;
-    return;
   }
+}
+
+void ArrayInit::finalize() {
   for (auto* value : values) {
     if (value->type == Type::unreachable) {
       type = Type::unreachable;
       return;
     }
   }
-  // A dynamic ArrayInit infers the type from the rtt. A static one has the type
-  // already in the type field.
-  if (rtt) {
-    type = Type(rtt->type.getHeapType(), NonNullable);
-  }
 }
 
 void ArrayGet::finalize() {
   if (ref->type == Type::unreachable || index->type == Type::unreachable) {
     type = Type::unreachable;
-  } else {
+  } else if (!ref->type.isNull()) {
     type = ref->type.getHeapType().getArray().element.type;
   }
 }
@@ -1163,16 +1121,136 @@ void RefAs::finalize() {
       type = Type(HeapType::func, NonNullable);
       break;
     case RefAsData:
-      type = Type::dataref;
+      type = Type(HeapType::data, NonNullable);
       break;
     case RefAsI31:
-      type = Type::i31ref;
+      type = Type(HeapType::i31, NonNullable);
+      break;
+    case ExternInternalize:
+      type = Type(HeapType::any, value->type.getNullability());
+      break;
+    case ExternExternalize:
+      type = Type(HeapType::ext, value->type.getNullability());
       break;
     default:
       WASM_UNREACHABLE("invalid ref.as_*");
   }
 }
 
+void StringNew::finalize() {
+  if (ptr->type == Type::unreachable ||
+      (length && length->type == Type::unreachable)) {
+    type = Type::unreachable;
+  } else {
+    type = Type(HeapType::string, NonNullable);
+  }
+}
+
+void StringConst::finalize() { type = Type(HeapType::string, NonNullable); }
+
+void StringMeasure::finalize() {
+  if (ref->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringEncode::finalize() {
+  if (ref->type == Type::unreachable || ptr->type == Type::unreachable ||
+      (start && start->type == Type::unreachable)) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringConcat::finalize() {
+  if (left->type == Type::unreachable || right->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type(HeapType::string, NonNullable);
+  }
+}
+
+void StringEq::finalize() {
+  if (left->type == Type::unreachable || right->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringAs::finalize() {
+  if (ref->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    switch (op) {
+      case StringAsWTF8:
+        type = Type(HeapType::stringview_wtf8, NonNullable);
+        break;
+      case StringAsWTF16:
+        type = Type(HeapType::stringview_wtf16, NonNullable);
+        break;
+      case StringAsIter:
+        type = Type(HeapType::stringview_iter, NonNullable);
+        break;
+      default:
+        WASM_UNREACHABLE("bad string.as");
+    }
+  }
+}
+
+void StringWTF8Advance::finalize() {
+  if (ref->type == Type::unreachable || pos->type == Type::unreachable ||
+      bytes->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringWTF16Get::finalize() {
+  if (ref->type == Type::unreachable || pos->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringIterNext::finalize() {
+  if (ref->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringIterMove::finalize() {
+  if (ref->type == Type::unreachable || num->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
+
+void StringSliceWTF::finalize() {
+  if (ref->type == Type::unreachable || start->type == Type::unreachable ||
+      end->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type(HeapType::string, NonNullable);
+  }
+}
+
+void StringSliceIter::finalize() {
+  if (ref->type == Type::unreachable || num->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type(HeapType::string, NonNullable);
+  }
+}
+
 size_t Function::getNumParams() { return getParams().size(); }
 
 size_t Function::getNumVars() { return vars.size(); }
@@ -1200,6 +1278,7 @@ Name Function::getLocalName(Index index) { return localNames.at(index); }
 void Function::setLocalName(Index index, Name name) {
   assert(index < getNumLocals());
   localNames[index] = name;
+  localIndices[name] = index;
 }
 
 Name Function::getLocalNameOrDefault(Index index) {
@@ -1219,6 +1298,10 @@ Name Function::getLocalNameOrGeneric(Index index) {
   return Name::fromInt(index);
 }
 
+bool Function::hasLocalIndex(Name name) const {
+  return localIndices.find(name) != localIndices.end();
+}
+
 Index Function::getLocalIndex(Name name) {
   auto iter = localIndices.find(name);
   if (iter == localIndices.end()) {
@@ -1275,6 +1358,14 @@ ElementSegment* Module::getElementSegment(Name name) {
   return getModuleElement(elementSegmentsMap, name, "getElementSegment");
 }
 
+Memory* Module::getMemory(Name name) {
+  return getModuleElement(memoriesMap, name, "getMemory");
+}
+
+DataSegment* Module::getDataSegment(Name name) {
+  return getModuleElement(dataSegmentsMap, name, "getDataSegment");
+}
+
 Global* Module::getGlobal(Name name) {
   return getModuleElement(globalsMap, name, "getGlobal");
 }
@@ -1308,6 +1399,14 @@ ElementSegment* Module::getElementSegmentOrNull(Name name) {
   return getModuleElementOrNull(elementSegmentsMap, name);
 }
 
+Memory* Module::getMemoryOrNull(Name name) {
+  return getModuleElementOrNull(memoriesMap, name);
+}
+
+DataSegment* Module::getDataSegmentOrNull(Name name) {
+  return getModuleElementOrNull(dataSegmentsMap, name);
+}
+
 Global* Module::getGlobalOrNull(Name name) {
   return getModuleElementOrNull(globalsMap, name);
 }
@@ -1383,6 +1482,15 @@ Module::addElementSegment(std::unique_ptr<ElementSegment>&& curr) {
     elementSegments, elementSegmentsMap, std::move(curr), "addElementSegment");
 }
 
+Memory* Module::addMemory(std::unique_ptr<Memory>&& curr) {
+  return addModuleElement(memories, memoriesMap, std::move(curr), "addMemory");
+}
+
+DataSegment* Module::addDataSegment(std::unique_ptr<DataSegment>&& curr) {
+  return addModuleElement(
+    dataSegments, dataSegmentsMap, std::move(curr), "addDataSegment");
+}
+
 Global* Module::addGlobal(std::unique_ptr<Global>&& curr) {
   return addModuleElement(globals, globalsMap, std::move(curr), "addGlobal");
 }
@@ -1416,6 +1524,12 @@ void Module::removeTable(Name name) {
 void Module::removeElementSegment(Name name) {
   removeModuleElement(elementSegments, elementSegmentsMap, name);
 }
+void Module::removeMemory(Name name) {
+  removeModuleElement(memories, memoriesMap, name);
+}
+void Module::removeDataSegment(Name name) {
+  removeModuleElement(dataSegments, dataSegmentsMap, name);
+}
 void Module::removeGlobal(Name name) {
   removeModuleElement(globals, globalsMap, name);
 }
@@ -1449,6 +1563,12 @@ void Module::removeTables(std::function<bool(Table*)> pred) {
 void Module::removeElementSegments(std::function<bool(ElementSegment*)> pred) {
   removeModuleElements(elementSegments, elementSegmentsMap, pred);
 }
+void Module::removeMemories(std::function<bool(Memory*)> pred) {
+  removeModuleElements(memories, memoriesMap, pred);
+}
+void Module::removeDataSegments(std::function<bool(DataSegment*)> pred) {
+  removeModuleElements(dataSegments, dataSegmentsMap, pred);
+}
 void Module::removeGlobals(std::function<bool(Global*)> pred) {
   removeModuleElements(globals, globalsMap, pred);
 }
@@ -1456,6 +1576,13 @@ void Module::removeTags(std::function<bool(Tag*)> pred) {
   removeModuleElements(tags, tagsMap, pred);
 }
 
+void Module::updateDataSegmentsMap() {
+  dataSegmentsMap.clear();
+  for (auto& curr : dataSegments) {
+    dataSegmentsMap[curr->name] = curr.get();
+  }
+}
+
 void Module::updateMaps() {
   functionsMap.clear();
   for (auto& curr : functions) {
@@ -1473,6 +1600,11 @@ void Module::updateMaps() {
   for (auto& curr : elementSegments) {
     elementSegmentsMap[curr->name] = curr.get();
   }
+  memoriesMap.clear();
+  for (auto& curr : memories) {
+    memoriesMap[curr->name] = curr.get();
+  }
+  updateDataSegmentsMap();
   globalsMap.clear();
   for (auto& curr : globals) {
     globalsMap[curr->name] = curr.get();
diff --git a/src/wasm/wat-lexer.cpp b/src/wasm/wat-lexer.cpp
new file mode 100644
index 0000000..80dda44
--- /dev/null
+++ b/src/wasm/wat-lexer.cpp
@@ -0,0 +1,1034 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <iostream>
+#include <optional>
+#include <sstream>
+#include <variant>
+
+#include "wat-lexer.h"
+
+using namespace std::string_view_literals;
+
+namespace wasm::WATParser {
+
+namespace {
+
+// ================
+// Lexical Analysis
+// ================
+
+// The result of lexing a token fragment.
+struct LexResult {
+  std::string_view span;
+};
+
+// Lexing context that accumulates lexed input to produce a token fragment.
+struct LexCtx {
+private:
+  // The input we are lexing.
+  std::string_view input;
+
+  // How much of the input we have already lexed.
+  size_t lexedSize = 0;
+
+public:
+  explicit LexCtx(std::string_view in) : input(in) {}
+
+  // Return the fragment that has been lexed so far.
+  std::optional<LexResult> lexed() const {
+    if (lexedSize > 0) {
+      return {LexResult{input.substr(0, lexedSize)}};
+    }
+    return {};
+  }
+
+  // The next input that has not already been lexed.
+  std::string_view next() const { return input.substr(lexedSize); }
+
+  // Get the next character without consuming it.
+  uint8_t peek() const { return next()[0]; }
+
+  // The size of the unlexed input.
+  size_t size() const { return input.size() - lexedSize; }
+
+  // Whether there is no more input.
+  bool empty() const { return size() == 0; }
+
+  // Tokens must be separated by spaces or parentheses.
+  bool canFinish() const;
+
+  // Whether the unlexed input starts with prefix `sv`.
+  size_t startsWith(std::string_view sv) const {
+    return next().substr(0, sv.size()) == sv;
+  }
+
+  // Consume the next `n` characters.
+  void take(size_t n) { lexedSize += n; }
+
+  // Consume an additional lexed fragment.
+  void take(const LexResult& res) { lexedSize += res.span.size(); }
+
+  // Consume the prefix and return true if possible.
+  bool takePrefix(std::string_view sv) {
+    if (startsWith(sv)) {
+      take(sv.size());
+      return true;
+    }
+    return false;
+  }
+
+  // Consume the rest of the input.
+  void takeAll() { lexedSize = input.size(); }
+};
+
+enum OverflowBehavior { DisallowOverflow, IgnoreOverflow };
+
+std::optional<int> getDigit(char c) {
+  if ('0' <= c && c <= '9') {
+    return c - '0';
+  }
+  return {};
+}
+
+std::optional<int> getHexDigit(char c) {
+  if ('0' <= c && c <= '9') {
+    return c - '0';
+  }
+  if ('A' <= c && c <= 'F') {
+    return 10 + c - 'A';
+  }
+  if ('a' <= c && c <= 'f') {
+    return 10 + c - 'a';
+  }
+  return {};
+}
+
+// The result of lexing an integer token fragment.
+struct LexIntResult : LexResult {
+  uint64_t n;
+  Sign sign;
+};
+
+// Lexing context that accumulates lexed input to produce an integer token
+// fragment.
+struct LexIntCtx : LexCtx {
+  using LexCtx::take;
+
+private:
+  uint64_t n = 0;
+  Sign sign = NoSign;
+  bool overflow = false;
+
+public:
+  explicit LexIntCtx(std::string_view in) : LexCtx(in) {}
+
+  // Lex only the underlying span, ignoring the overflow and value.
+  std::optional<LexIntResult> lexedRaw() {
+    if (auto basic = LexCtx::lexed()) {
+      return LexIntResult{*basic, 0, NoSign};
+    }
+    return {};
+  }
+
+  std::optional<LexIntResult> lexed() {
+    if (overflow) {
+      return {};
+    }
+    if (auto basic = LexCtx::lexed()) {
+      return LexIntResult{*basic, sign == Neg ? -n : n, sign};
+    }
+    return {};
+  }
+
+  void takeSign() {
+    if (takePrefix("+"sv)) {
+      sign = Pos;
+    } else if (takePrefix("-"sv)) {
+      sign = Neg;
+    } else {
+      sign = NoSign;
+    }
+  }
+
+  bool takeDigit() {
+    if (!empty()) {
+      if (auto d = getDigit(peek())) {
+        take(1);
+        uint64_t newN = n * 10 + *d;
+        if (newN < n) {
+          overflow = true;
+        }
+        n = newN;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool takeHexdigit() {
+    if (!empty()) {
+      if (auto h = getHexDigit(peek())) {
+        take(1);
+        uint64_t newN = n * 16 + *h;
+        if (newN < n) {
+          overflow = true;
+        }
+        n = newN;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void take(const LexIntResult& res) {
+    LexCtx::take(res);
+    n = res.n;
+  }
+};
+
+struct LexFloatResult : LexResult {
+  // The payload if we lexed a nan with payload. We cannot store the payload
+  // directly in `d` because we do not know at this point whether we are parsing
+  // an f32 or f64 and therefore we do not know what the allowable payloads are.
+  // No payload with NaN means to use the default payload for the expected float
+  // width.
+  std::optional<uint64_t> nanPayload;
+  double d;
+};
+
+struct LexFloatCtx : LexCtx {
+  std::optional<uint64_t> nanPayload;
+
+  LexFloatCtx(std::string_view in) : LexCtx(in) {}
+
+  std::optional<LexFloatResult> lexed() {
+    assert(!std::signbit(NAN) && "Expected NAN to be positive");
+    auto basic = LexCtx::lexed();
+    if (!basic) {
+      return {};
+    }
+    if (nanPayload) {
+      double nan = basic->span[0] == '-' ? -NAN : NAN;
+      return LexFloatResult{*basic, nanPayload, nan};
+    }
+    // strtod does not return -NAN for "-nan" on all platforms.
+    if (basic->span == "-nan"sv) {
+      return LexFloatResult{*basic, nanPayload, -NAN};
+    }
+    // Do not try to implement fully general and precise float parsing
+    // ourselves. Instead, call out to std::strtod to do our parsing. This means
+    // we need to strip any underscores since `std::strtod` does not understand
+    // them.
+    std::stringstream ss;
+    for (const char *curr = basic->span.data(),
+                    *end = curr + basic->span.size();
+         curr != end;
+         ++curr) {
+      if (*curr != '_') {
+        ss << *curr;
+      }
+    }
+    std::string str = ss.str();
+    char* last;
+    double d = std::strtod(str.data(), &last);
+    assert(last == str.data() + str.size() && "could not parse float");
+    return LexFloatResult{*basic, {}, d};
+  }
+};
+
+struct LexStrResult : LexResult {
+  // Allocate a string only if there are escape sequences, otherwise just use
+  // the original string_view.
+  std::optional<std::string> str;
+};
+
+struct LexStrCtx : LexCtx {
+private:
+  // Used to build a string with resolved escape sequences. Only used when the
+  // parsed string contains escape sequences, otherwise we can just use the
+  // parsed string directly.
+  std::optional<std::stringstream> escapeBuilder;
+
+public:
+  LexStrCtx(std::string_view in) : LexCtx(in) {}
+
+  std::optional<LexStrResult> lexed() {
+    if (auto basic = LexCtx::lexed()) {
+      if (escapeBuilder) {
+        return LexStrResult{*basic, {escapeBuilder->str()}};
+      } else {
+        return LexStrResult{*basic, {}};
+      }
+    }
+    return {};
+  }
+
+  void takeChar() {
+    if (escapeBuilder) {
+      *escapeBuilder << peek();
+    }
+    LexCtx::take(1);
+  }
+
+  void ensureBuildingEscaped() {
+    if (escapeBuilder) {
+      return;
+    }
+    // Drop the opening '"'.
+    escapeBuilder = std::stringstream{};
+    *escapeBuilder << LexCtx::lexed()->span.substr(1);
+  }
+
+  void appendEscaped(char c) { *escapeBuilder << c; }
+
+  bool appendUnicode(uint64_t u) {
+    if ((0xd800 <= u && u < 0xe000) || 0x110000 <= u) {
+      return false;
+    }
+    if (u < 0x80) {
+      // 0xxxxxxx
+      *escapeBuilder << uint8_t(u);
+    } else if (u < 0x800) {
+      // 110xxxxx 10xxxxxx
+      *escapeBuilder << uint8_t(0b11000000 | ((u >> 6) & 0b00011111));
+      *escapeBuilder << uint8_t(0b10000000 | ((u >> 0) & 0b00111111));
+    } else if (u < 0x10000) {
+      // 1110xxxx 10xxxxxx 10xxxxxx
+      *escapeBuilder << uint8_t(0b11100000 | ((u >> 12) & 0b00001111));
+      *escapeBuilder << uint8_t(0b10000000 | ((u >> 6) & 0b00111111));
+      *escapeBuilder << uint8_t(0b10000000 | ((u >> 0) & 0b00111111));
+    } else {
+      // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+      *escapeBuilder << uint8_t(0b11110000 | ((u >> 18) & 0b00000111));
+      *escapeBuilder << uint8_t(0b10000000 | ((u >> 12) & 0b00111111));
+      *escapeBuilder << uint8_t(0b10000000 | ((u >> 6) & 0b00111111));
+      *escapeBuilder << uint8_t(0b10000000 | ((u >> 0) & 0b00111111));
+    }
+    return true;
+  }
+};
+
+std::optional<LexResult> lparen(std::string_view in) {
+  LexCtx ctx(in);
+  ctx.takePrefix("("sv);
+  return ctx.lexed();
+}
+
+std::optional<LexResult> rparen(std::string_view in) {
+  LexCtx ctx(in);
+  ctx.takePrefix(")"sv);
+  return ctx.lexed();
+}
+
+// comment      ::= linecomment | blockcomment
+// linecomment  ::= ';;' linechar* ('\n' | eof)
+// linechar     ::= c:char                      (if c != '\n')
+// blockcomment ::= '(;' blockchar* ';)'
+// blockchar    ::= c:char                      (if c != ';' and c != '(')
+//                | ';'                         (if the next char is not ')')
+//                | '('                         (if the next char is not ';')
+//                | blockcomment
+std::optional<LexResult> comment(std::string_view in) {
+  LexCtx ctx(in);
+  if (ctx.size() < 2) {
+    return {};
+  }
+
+  // Line comment
+  if (ctx.takePrefix(";;"sv)) {
+    if (auto size = ctx.next().find('\n'); size != ""sv.npos) {
+      ctx.take(size);
+    } else {
+      ctx.takeAll();
+    }
+    return ctx.lexed();
+  }
+
+  // Block comment (possibly nested!)
+  if (ctx.takePrefix("(;"sv)) {
+    size_t depth = 1;
+    while (depth > 0 && ctx.size() >= 2) {
+      if (ctx.takePrefix("(;"sv)) {
+        ++depth;
+      } else if (ctx.takePrefix(";)"sv)) {
+        --depth;
+      } else {
+        ctx.take(1);
+      }
+    }
+    if (depth > 0) {
+      // TODO: Add error production for non-terminated block comment.
+      return {};
+    }
+    return ctx.lexed();
+  }
+
+  return {};
+}
+
+std::optional<LexResult> spacechar(std::string_view in) {
+  LexCtx ctx(in);
+  ctx.takePrefix(" "sv) || ctx.takePrefix("\n"sv) || ctx.takePrefix("\r"sv) ||
+    ctx.takePrefix("\t"sv);
+  return ctx.lexed();
+}
+
+// space  ::= (' ' | format | comment)*
+// format ::= '\t' | '\n' | '\r'
+std::optional<LexResult> space(std::string_view in) {
+  LexCtx ctx(in);
+  while (ctx.size()) {
+    if (auto lexed = spacechar(ctx.next())) {
+      ctx.take(*lexed);
+    } else if (auto lexed = comment(ctx.next())) {
+      ctx.take(*lexed);
+    } else {
+      break;
+    }
+  }
+  return ctx.lexed();
+}
+
+bool LexCtx::canFinish() const {
+  // Logically we want to check for eof, parens, and space. But we don't
+  // actually want to parse more than a couple characters of space, so check for
+  // individual space chars or comment starts instead.
+  return empty() || lparen(next()) || rparen(next()) || spacechar(next()) ||
+         startsWith(";;"sv);
+}
+
+// num   ::= d:digit => d
+//         |  n:num '_'? d:digit => 10*n + d
+// digit ::= '0' => 0 | ... | '9' => 9
+std::optional<LexIntResult> num(std::string_view in,
+                                OverflowBehavior overflow = DisallowOverflow) {
+  LexIntCtx ctx(in);
+  if (ctx.empty()) {
+    return {};
+  }
+  if (!ctx.takeDigit()) {
+    return {};
+  }
+  while (true) {
+    bool under = ctx.takePrefix("_"sv);
+    if (!ctx.takeDigit()) {
+      if (!under) {
+        return overflow == DisallowOverflow ? ctx.lexed() : ctx.lexedRaw();
+      }
+      // TODO: Add error production for trailing underscore.
+      return {};
+    }
+  }
+}
+
+// hexnum   ::= h:hexdigit => h
+//            | n:hexnum '_'? h:hexdigit => 16*n + h
+// hexdigit ::= d:digit => d
+//            | 'A' => 10 | ... | 'F' => 15
+//            | 'a' => 10 | ... | 'f' => 15
+std::optional<LexIntResult>
+hexnum(std::string_view in, OverflowBehavior overflow = DisallowOverflow) {
+  LexIntCtx ctx(in);
+  if (!ctx.takeHexdigit()) {
+    return {};
+  }
+  while (true) {
+    bool under = ctx.takePrefix("_"sv);
+    if (!ctx.takeHexdigit()) {
+      if (!under) {
+        return overflow == DisallowOverflow ? ctx.lexed() : ctx.lexedRaw();
+      }
+      // TODO: Add error production for trailing underscore.
+      return {};
+    }
+  }
+}
+
+// uN ::= n:num         => n (if n < 2^N)
+//      | '0x' n:hexnum => n (if n < 2^N)
+// sN ::= s:sign n:num         => [s]n (if -2^(N-1) <= [s]n < 2^(N-1))
+//      | s:sign '0x' n:hexnum => [s]n (if -2^(N-1) <= [s]n < 2^(N-1))
+// sign ::= {} => + | '+' => + | '-' => -
+//
+// Note: Defer bounds and sign checking until we know what kind of integer we
+// expect.
+std::optional<LexIntResult> integer(std::string_view in) {
+  LexIntCtx ctx(in);
+  ctx.takeSign();
+  if (ctx.takePrefix("0x"sv)) {
+    if (auto lexed = hexnum(ctx.next())) {
+      ctx.take(*lexed);
+      if (ctx.canFinish()) {
+        return ctx.lexed();
+      }
+    }
+    // TODO: Add error production for unrecognized hexnum.
+    return {};
+  }
+  if (auto lexed = num(ctx.next())) {
+    ctx.take(*lexed);
+    if (ctx.canFinish()) {
+      return ctx.lexed();
+    }
+  }
+  return {};
+}
+
+// float   ::= p:num '.'?                              => p
+//           | p:num '.' q:frac                        => p + q
+//           | p:num '.'? ('E'|'e') s:sign e:num       => p * 10^([s]e)
+//           | p:num '.' q:frac ('E'|'e') s:sign e:num => (p + q) * 10^([s]e)
+// frac    ::= d:digit                                 => d/10
+//           | d:digit '_'? p:frac                     => (d + p/10) / 10
+std::optional<LexResult> decfloat(std::string_view in) {
+  LexCtx ctx(in);
+  if (auto lexed = num(ctx.next(), IgnoreOverflow)) {
+    ctx.take(*lexed);
+  } else {
+    return {};
+  }
+  // Optional '.' followed by optional frac
+  if (ctx.takePrefix("."sv)) {
+    if (auto lexed = num(ctx.next(), IgnoreOverflow)) {
+      ctx.take(*lexed);
+    }
+  }
+  if (ctx.takePrefix("E"sv) || ctx.takePrefix("e"sv)) {
+    // Optional sign
+    ctx.takePrefix("+"sv) || ctx.takePrefix("-"sv);
+    if (auto lexed = num(ctx.next(), IgnoreOverflow)) {
+      ctx.take(*lexed);
+    } else {
+      // TODO: Add error production for missing exponent.
+      return {};
+    }
+  }
+  return ctx.lexed();
+}
+
+// hexfloat ::= '0x' p:hexnum '.'?                        => p
+//            | '0x' p:hexnum '.' q:hexfrac               => p + q
+//            | '0x' p:hexnum '.'? ('P'|'p') s:sign e:num => p * 2^([s]e)
+//            | '0x' p:hexnum '.' q:hexfrac ('P'|'p') s:sign e:num
+//                   => (p + q) * 2^([s]e)
+// hexfrac ::= h:hexdigit                              => h/16
+//           | h:hexdigit '_'? p:hexfrac               => (h + p/16) / 16
+std::optional<LexResult> hexfloat(std::string_view in) {
+  LexCtx ctx(in);
+  if (!ctx.takePrefix("0x"sv)) {
+    return {};
+  }
+  if (auto lexed = hexnum(ctx.next(), IgnoreOverflow)) {
+    ctx.take(*lexed);
+  } else {
+    return {};
+  }
+  // Optional '.' followed by optional hexfrac
+  if (ctx.takePrefix("."sv)) {
+    if (auto lexed = hexnum(ctx.next(), IgnoreOverflow)) {
+      ctx.take(*lexed);
+    }
+  }
+  if (ctx.takePrefix("P"sv) || ctx.takePrefix("p"sv)) {
+    // Optional sign
+    ctx.takePrefix("+"sv) || ctx.takePrefix("-"sv);
+    if (auto lexed = num(ctx.next(), IgnoreOverflow)) {
+      ctx.take(*lexed);
+    } else {
+      // TODO: Add error production for missing exponent.
+      return {};
+    }
+  }
+  return ctx.lexed();
+}
+
+// fN    ::= s:sign z:fNmag => [s]z
+// fNmag ::= z:float        => float_N(z) (if float_N(z) != +/-infinity)
+//         | z:hexfloat     => float_N(z) (if float_N(z) != +/-infinity)
+//         | 'inf'          => infinity
+//         | 'nan'          => nan(2^(signif(N)-1))
+//         | 'nan:0x' n:hexnum => nan(n) (if 1 <= n < 2^signif(N))
+std::optional<LexFloatResult> float_(std::string_view in) {
+  LexFloatCtx ctx(in);
+  // Optional sign
+  ctx.takePrefix("+"sv) || ctx.takePrefix("-"sv);
+  if (auto lexed = hexfloat(ctx.next())) {
+    ctx.take(*lexed);
+  } else if (auto lexed = decfloat(ctx.next())) {
+    ctx.take(*lexed);
+  } else if (ctx.takePrefix("inf"sv)) {
+    // nop
+  } else if (ctx.takePrefix("nan"sv)) {
+    if (ctx.takePrefix(":0x"sv)) {
+      if (auto lexed = hexnum(ctx.next())) {
+        ctx.take(*lexed);
+        ctx.nanPayload = lexed->n;
+      } else {
+        // TODO: Add error production for malformed NaN payload.
+        return {};
+      }
+    } else {
+      // No explicit payload necessary; we will inject the default payload
+      // later.
+    }
+  } else {
+    return {};
+  }
+  if (ctx.canFinish()) {
+    return ctx.lexed();
+  }
+  return {};
+}
+
+// idchar ::= '0' | ... | '9'
+//          | 'A' | ... | 'Z'
+//          | 'a' | ... | 'z'
+//          | '!' | '#' | '$' | '%' | '&' | ''' | '*' | '+'
+//          | '-' | '.' | '/' | ':' | '<' | '=' | '>' | '?'
+//          | '@' | '\' | '^' | '_' | '`' | '|' | '~'
+std::optional<LexResult> idchar(std::string_view in) {
+  LexCtx ctx(in);
+  if (ctx.empty()) {
+    return {};
+  }
+  uint8_t c = ctx.peek();
+  if (('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') ||
+      ('a' <= c && c <= 'z')) {
+    ctx.take(1);
+  } else {
+    switch (c) {
+      case '!':
+      case '#':
+      case '$':
+      case '%':
+      case '&':
+      case '\'':
+      case '*':
+      case '+':
+      case '-':
+      case '.':
+      case '/':
+      case ':':
+      case '<':
+      case '=':
+      case '>':
+      case '?':
+      case '@':
+      case '\\':
+      case '^':
+      case '_':
+      case '`':
+      case '|':
+      case '~':
+        ctx.take(1);
+    }
+  }
+  return ctx.lexed();
+}
+
+// id ::= '$' idchar+
+std::optional<LexResult> ident(std::string_view in) {
+  LexCtx ctx(in);
+  if (!ctx.takePrefix("$"sv)) {
+    return {};
+  }
+  if (auto lexed = idchar(ctx.next())) {
+    ctx.take(*lexed);
+  } else {
+    return {};
+  }
+  while (auto lexed = idchar(ctx.next())) {
+    ctx.take(*lexed);
+  }
+  if (ctx.canFinish()) {
+    return ctx.lexed();
+  }
+  return {};
+}
+
+// string     ::= '"' (b*:stringelem)* '"'  => concat((b*)*)
+//                    (if |concat((b*)*)| < 2^32)
+// stringelem ::= c:stringchar              => utf8(c)
+//              | '\' n:hexdigit m:hexdigit => 16*n + m
+// stringchar ::= c:char                    => c
+//                    (if c >= U+20 && c != U+7f && c != '"' && c != '\')
+//              | '\t' => \t | '\n' => \n | '\r' => \r
+//              | '\\' => \ | '\"' => " | '\'' => '
+//              | '\u{' n:hexnum '}'        => U+(n)
+//                    (if n < 0xD800 and 0xE000 <= n <= 0x110000)
+std::optional<LexStrResult> str(std::string_view in) {
+  LexStrCtx ctx(in);
+  if (!ctx.takePrefix("\""sv)) {
+    return {};
+  }
+  while (!ctx.takePrefix("\""sv)) {
+    if (ctx.empty()) {
+      // TODO: Add error production for unterminated string.
+      return {};
+    }
+    if (ctx.startsWith("\\"sv)) {
+      // Escape sequences
+      ctx.ensureBuildingEscaped();
+      ctx.take(1);
+      if (ctx.takePrefix("t"sv)) {
+        ctx.appendEscaped('\t');
+      } else if (ctx.takePrefix("n"sv)) {
+        ctx.appendEscaped('\n');
+      } else if (ctx.takePrefix("r"sv)) {
+        ctx.appendEscaped('\r');
+      } else if (ctx.takePrefix("\\"sv)) {
+        ctx.appendEscaped('\\');
+      } else if (ctx.takePrefix("\""sv)) {
+        ctx.appendEscaped('"');
+      } else if (ctx.takePrefix("'"sv)) {
+        ctx.appendEscaped('\'');
+      } else if (ctx.takePrefix("u{"sv)) {
+        auto lexed = hexnum(ctx.next());
+        if (!lexed) {
+          // TODO: Add error production for malformed unicode escapes.
+          return {};
+        }
+        ctx.take(*lexed);
+        if (!ctx.takePrefix("}"sv)) {
+          // TODO: Add error production for malformed unicode escapes.
+          return {};
+        }
+        if (!ctx.appendUnicode(lexed->n)) {
+          // TODO: Add error production for invalid unicode values.
+          return {};
+        }
+      } else {
+        LexIntCtx ictx(ctx.next());
+        if (!ictx.takeHexdigit() || !ictx.takeHexdigit()) {
+          // TODO: Add error production for unrecognized escape sequence.
+          return {};
+        }
+        auto lexed = *ictx.lexed();
+        ctx.take(lexed);
+        ctx.appendEscaped(char(lexed.n));
+      }
+    } else {
+      // Normal characters
+      if (uint8_t c = ctx.peek(); c >= 0x20 && c != 0x7F) {
+        ctx.takeChar();
+      } else {
+        // TODO: Add error production for unescaped control characters.
+        return {};
+      }
+    }
+  }
+  return ctx.lexed();
+}
+
+// keyword ::= ( 'a' | ... | 'z' ) idchar* (if literal terminal in grammar)
+// reserved ::= idchar+
+//
+// The "keyword" token we lex here covers both keywords as well as any reserved
+// tokens that match the keyword format. This saves us from having to enumerate
+// all the valid keywords here. These invalid keywords will still produce
+// errors, just at a higher level of the parser.
+std::optional<LexResult> keyword(std::string_view in) {
+  LexCtx ctx(in);
+  if (ctx.empty()) {
+    return {};
+  }
+  uint8_t start = ctx.peek();
+  if ('a' <= start && start <= 'z') {
+    ctx.take(1);
+  } else {
+    return {};
+  }
+  while (auto lexed = idchar(ctx.next())) {
+    ctx.take(*lexed);
+  }
+  return ctx.lexed();
+}
+
+} // anonymous namespace
+
+std::optional<uint64_t> Token::getU64() const {
+  if (auto* tok = std::get_if<IntTok>(&data)) {
+    if (tok->sign == NoSign) {
+      return tok->n;
+    }
+  }
+  return {};
+}
+
+std::optional<int64_t> Token::getS64() const {
+  if (auto* tok = std::get_if<IntTok>(&data)) {
+    if (tok->sign == Neg) {
+      if (uint64_t(INT64_MIN) <= tok->n || tok->n == 0) {
+        return int64_t(tok->n);
+      }
+      // TODO: Add error production for signed underflow.
+    } else {
+      if (tok->n <= uint64_t(INT64_MAX)) {
+        return int64_t(tok->n);
+      }
+      // TODO: Add error production for signed overflow.
+    }
+  }
+  return {};
+}
+
+std::optional<uint64_t> Token::getI64() const {
+  if (auto n = getU64()) {
+    return *n;
+  }
+  if (auto n = getS64()) {
+    return *n;
+  }
+  return {};
+}
+
+std::optional<uint32_t> Token::getU32() const {
+  if (auto* tok = std::get_if<IntTok>(&data)) {
+    if (tok->sign == NoSign && tok->n <= UINT32_MAX) {
+      return int32_t(tok->n);
+    }
+    // TODO: Add error production for unsigned overflow.
+  }
+  return {};
+}
+
+std::optional<int32_t> Token::getS32() const {
+  if (auto* tok = std::get_if<IntTok>(&data)) {
+    if (tok->sign == Neg) {
+      if (uint64_t(INT32_MIN) <= tok->n || tok->n == 0) {
+        return int32_t(tok->n);
+      }
+    } else {
+      if (tok->n <= uint64_t(INT32_MAX)) {
+        return int32_t(tok->n);
+      }
+    }
+  }
+  return {};
+}
+
+std::optional<uint32_t> Token::getI32() const {
+  if (auto n = getU32()) {
+    return *n;
+  }
+  if (auto n = getS32()) {
+    return uint32_t(*n);
+  }
+  return {};
+}
+
+std::optional<double> Token::getF64() const {
+  constexpr int signif = 52;
+  constexpr uint64_t payloadMask = (1ull << signif) - 1;
+  constexpr uint64_t nanDefault = 1ull << (signif - 1);
+  if (auto* tok = std::get_if<FloatTok>(&data)) {
+    double d = tok->d;
+    if (std::isnan(d)) {
+      // Inject payload.
+      uint64_t payload = tok->nanPayload ? *tok->nanPayload : nanDefault;
+      if (payload == 0 || payload > payloadMask) {
+        // TODO: Add error production for out-of-bounds payload.
+        return {};
+      }
+      uint64_t bits;
+      static_assert(sizeof(bits) == sizeof(d));
+      memcpy(&bits, &d, sizeof(bits));
+      bits = (bits & ~payloadMask) | payload;
+      memcpy(&d, &bits, sizeof(bits));
+    }
+    return d;
+  }
+  if (auto* tok = std::get_if<IntTok>(&data)) {
+    if (tok->sign == Neg) {
+      if (tok->n == 0) {
+        return -0.0;
+      }
+      return double(int64_t(tok->n));
+    }
+    return double(tok->n);
+  }
+  return {};
+}
+
+std::optional<float> Token::getF32() const {
+  constexpr int signif = 23;
+  constexpr uint32_t payloadMask = (1u << signif) - 1;
+  constexpr uint64_t nanDefault = 1ull << (signif - 1);
+  if (auto* tok = std::get_if<FloatTok>(&data)) {
+    float f = tok->d;
+    if (std::isnan(f)) {
+      // Validate and inject payload.
+      uint64_t payload = tok->nanPayload ? *tok->nanPayload : nanDefault;
+      if (payload == 0 || payload > payloadMask) {
+        // TODO: Add error production for out-of-bounds payload.
+        return {};
+      }
+      uint32_t bits;
+      static_assert(sizeof(bits) == sizeof(f));
+      memcpy(&bits, &f, sizeof(bits));
+      bits = (bits & ~payloadMask) | payload;
+      memcpy(&f, &bits, sizeof(bits));
+    }
+    return f;
+  }
+  if (auto* tok = std::get_if<IntTok>(&data)) {
+    if (tok->sign == Neg) {
+      if (tok->n == 0) {
+        return -0.0f;
+      }
+      return float(int64_t(tok->n));
+    }
+    return float(tok->n);
+  }
+  return {};
+}
+
+std::optional<std::string_view> Token::getString() const {
+  if (auto* tok = std::get_if<StringTok>(&data)) {
+    if (tok->str) {
+      return std::string_view(*tok->str);
+    }
+    return span.substr(1, span.size() - 2);
+  }
+  return {};
+}
+
+void Lexer::skipSpace() {
+  if (auto ctx = space(next())) {
+    index += ctx->span.size();
+  }
+}
+
+void Lexer::lexToken() {
+  // TODO: Ensure we're getting the longest possible match.
+  Token tok;
+  if (auto t = lparen(next())) {
+    tok = Token{t->span, LParenTok{}};
+  } else if (auto t = rparen(next())) {
+    tok = Token{t->span, RParenTok{}};
+  } else if (auto t = ident(next())) {
+    tok = Token{t->span, IdTok{}};
+  } else if (auto t = integer(next())) {
+    tok = Token{t->span, IntTok{t->n, t->sign}};
+  } else if (auto t = float_(next())) {
+    tok = Token{t->span, FloatTok{t->nanPayload, t->d}};
+  } else if (auto t = str(next())) {
+    tok = Token{t->span, StringTok{t->str}};
+  } else if (auto t = keyword(next())) {
+    tok = Token{t->span, KeywordTok{}};
+  } else {
+    // TODO: Do something about lexing errors.
+    curr = std::nullopt;
+    return;
+  }
+  index += tok.span.size();
+  curr = {tok};
+}
+
+TextPos Lexer::position(const char* c) const {
+  assert(size_t(c - buffer.data()) <= buffer.size());
+  TextPos pos{1, 0};
+  for (const char* p = buffer.data(); p != c; ++p) {
+    if (*p == '\n') {
+      pos.line++;
+      pos.col = 0;
+    } else {
+      pos.col++;
+    }
+  }
+  return pos;
+}
+
+bool TextPos::operator==(const TextPos& other) const {
+  return line == other.line && col == other.col;
+}
+
+bool IntTok::operator==(const IntTok& other) const {
+  return n == other.n && sign == other.sign;
+}
+
+bool FloatTok::operator==(const FloatTok& other) const {
+  return std::signbit(d) == std::signbit(other.d) &&
+         (d == other.d || (std::isnan(d) && std::isnan(other.d) &&
+                           nanPayload == other.nanPayload));
+}
+
+bool Token::operator==(const Token& other) const {
+  return span == other.span &&
+         std::visit(
+           [](auto& t1, auto& t2) {
+             if constexpr (std::is_same_v<decltype(t1), decltype(t2)>) {
+               return t1 == t2;
+             } else {
+               return false;
+             }
+           },
+           data,
+           other.data);
+}
+
+std::ostream& operator<<(std::ostream& os, const TextPos& pos) {
+  return os << pos.line << ":" << pos.col;
+}
+
+std::ostream& operator<<(std::ostream& os, const LParenTok&) {
+  return os << "'('";
+}
+
+std::ostream& operator<<(std::ostream& os, const RParenTok&) {
+  return os << "')'";
+}
+
+std::ostream& operator<<(std::ostream& os, const IdTok&) { return os << "id"; }
+
+std::ostream& operator<<(std::ostream& os, const IntTok& tok) {
+  return os << (tok.sign == Pos ? "+" : tok.sign == Neg ? "-" : "") << tok.n;
+}
+
+std::ostream& operator<<(std::ostream& os, const FloatTok& tok) {
+  if (std::isnan(tok.d)) {
+    os << (std::signbit(tok.d) ? "+" : "-");
+    if (tok.nanPayload) {
+      return os << "nan:0x" << std::hex << *tok.nanPayload << std::dec;
+    }
+    return os << "nan";
+  }
+  return os << tok.d;
+}
+
+std::ostream& operator<<(std::ostream& os, const StringTok& tok) {
+  if (tok.str) {
+    os << '"' << *tok.str << '"';
+  } else {
+    os << "(raw string)";
+  }
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const KeywordTok&) {
+  return os << "keyword";
+}
+
+std::ostream& operator<<(std::ostream& os, const Token& tok) {
+  std::visit([&](const auto& t) { os << t; }, tok.data);
+  return os << " \"" << tok.span << "\"";
+}
+
+} // namespace wasm::WATParser
diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp
new file mode 100644
index 0000000..a87a86c
--- /dev/null
+++ b/src/wasm/wat-parser.cpp
@@ -0,0 +1,3683 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wat-parser.h"
+#include "ir/names.h"
+#include "support/name.h"
+#include "wasm-builder.h"
+#include "wasm-type.h"
+#include "wasm.h"
+#include "wat-lexer.h"
+
+// The WebAssembly text format is recursive in the sense that elements may be
+// referred to before they are declared. Furthermore, elements may be referred
+// to by index or by name. As a result, we need to parse text modules in
+// multiple phases.
+//
+// In the first phase, we find all of the module element declarations and
+// record, but do not interpret, the input spans of their corresponding
+// definitions. This phase establishes the indices and names of each module
+// element so that subsequent phases can look them up.
+//
+// The second phase parses type definitions to construct the types used in the
+// module. This has to be its own phase because we have no way to refer to a
+// type before it has been built along with all the other types, unlike for
+// other module elements that can be referred to by name before their
+// definitions have been parsed.
+//
+// The third phase further parses and constructs types implicitly defined by
+// type uses in functions, blocks, and call_indirect instructions. These
+// implicitly defined types may be referred to by index elsewhere.
+//
+// The fourth phase parses and sets the types of globals, functions, and other
+// top-level module elements. These types need to be set before we parse
+// instructions because they determine the types of instructions such as
+// global.get and ref.func.
+//
+// The fifth and final phase parses the remaining contents of all module
+// elements, including instructions.
+//
+// Each phase of parsing gets its own context type that is passed to the
+// individual parsing functions. There is a parsing function for each element of
+// the grammar given in the spec. Parsing functions are templatized so that they
+// may be passed the appropriate context type and return the correct result type
+// for each phase.
+
+#define CHECK_ERR(val)                                                         \
+  if (auto _val = (val); auto err = _val.getErr()) {                           \
+    return Err{*err};                                                          \
+  }
+
+using namespace std::string_view_literals;
+
+namespace wasm::WATParser {
+
+namespace {
+
+// ============
+// Parser Input
+// ============
+
+// Wraps a lexer and provides utilities for consuming tokens.
+struct ParseInput {
+  Lexer lexer;
+
+  explicit ParseInput(std::string_view in) : lexer(in) {}
+
+  ParseInput(std::string_view in, size_t index) : lexer(in) {
+    lexer.setIndex(index);
+  }
+
+  ParseInput(const ParseInput& other, size_t index) : lexer(other.lexer) {
+    lexer.setIndex(index);
+  }
+
+  bool empty() { return lexer.empty(); }
+
+  std::optional<Token> peek() {
+    if (!empty()) {
+      return *lexer;
+    }
+    return {};
+  }
+
+  bool takeLParen() {
+    auto t = peek();
+    if (!t || !t->isLParen()) {
+      return false;
+    }
+    ++lexer;
+    return true;
+  }
+
+  bool takeRParen() {
+    auto t = peek();
+    if (!t || !t->isRParen()) {
+      return false;
+    }
+    ++lexer;
+    return true;
+  }
+
+  bool takeUntilParen() {
+    while (true) {
+      auto t = peek();
+      if (!t) {
+        return false;
+      }
+      if (t->isLParen() || t->isRParen()) {
+        return true;
+      }
+      ++lexer;
+    }
+  }
+
+  std::optional<Name> takeID() {
+    if (auto t = peek()) {
+      if (auto id = t->getID()) {
+        ++lexer;
+        // See comment on takeName.
+        return Name(std::string(*id));
+      }
+    }
+    return {};
+  }
+
+  std::optional<std::string_view> takeKeyword() {
+    if (auto t = peek()) {
+      if (auto keyword = t->getKeyword()) {
+        ++lexer;
+        return *keyword;
+      }
+    }
+    return {};
+  }
+
+  bool takeKeyword(std::string_view expected) {
+    if (auto t = peek()) {
+      if (auto keyword = t->getKeyword()) {
+        if (*keyword == expected) {
+          ++lexer;
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  std::optional<uint64_t> takeOffset() {
+    if (auto t = peek()) {
+      if (auto keyword = t->getKeyword()) {
+        if (keyword->substr(0, 7) != "offset="sv) {
+          return {};
+        }
+        Lexer subLexer(keyword->substr(7));
+        if (subLexer == subLexer.end()) {
+          return {};
+        }
+        if (auto o = subLexer->getU64()) {
+          ++subLexer;
+          if (subLexer == subLexer.end()) {
+            ++lexer;
+            return o;
+          }
+        }
+      }
+    }
+    return std::nullopt;
+  }
+
+  std::optional<uint32_t> takeAlign() {
+    if (auto t = peek()) {
+      if (auto keyword = t->getKeyword()) {
+        if (keyword->substr(0, 6) != "align="sv) {
+          return {};
+        }
+        Lexer subLexer(keyword->substr(6));
+        if (subLexer == subLexer.end()) {
+          return {};
+        }
+        if (auto a = subLexer->getU32()) {
+          ++subLexer;
+          if (subLexer == subLexer.end()) {
+            ++lexer;
+            return a;
+          }
+        }
+      }
+    }
+    return {};
+  }
+
+  std::optional<uint64_t> takeU64() {
+    if (auto t = peek()) {
+      if (auto n = t->getU64()) {
+        ++lexer;
+        return n;
+      }
+    }
+    return std::nullopt;
+  }
+
+  std::optional<int64_t> takeS64() {
+    if (auto t = peek()) {
+      if (auto n = t->getS64()) {
+        ++lexer;
+        return n;
+      }
+    }
+    return {};
+  }
+
+  std::optional<int64_t> takeI64() {
+    if (auto t = peek()) {
+      if (auto n = t->getI64()) {
+        ++lexer;
+        return n;
+      }
+    }
+    return {};
+  }
+
+  std::optional<uint32_t> takeU32() {
+    if (auto t = peek()) {
+      if (auto n = t->getU32()) {
+        ++lexer;
+        return n;
+      }
+    }
+    return std::nullopt;
+  }
+
+  std::optional<int32_t> takeS32() {
+    if (auto t = peek()) {
+      if (auto n = t->getS32()) {
+        ++lexer;
+        return n;
+      }
+    }
+    return {};
+  }
+
+  std::optional<int32_t> takeI32() {
+    if (auto t = peek()) {
+      if (auto n = t->getI32()) {
+        ++lexer;
+        return n;
+      }
+    }
+    return {};
+  }
+
+  std::optional<uint8_t> takeU8() {
+    if (auto t = peek()) {
+      if (auto n = t->getU32()) {
+        if (n <= std::numeric_limits<uint8_t>::max()) {
+          ++lexer;
+          return uint8_t(*n);
+        }
+      }
+    }
+    return {};
+  }
+
+  std::optional<double> takeF64() {
+    if (auto t = peek()) {
+      if (auto d = t->getF64()) {
+        ++lexer;
+        return d;
+      }
+    }
+    return std::nullopt;
+  }
+
+  std::optional<float> takeF32() {
+    if (auto t = peek()) {
+      if (auto f = t->getF32()) {
+        ++lexer;
+        return f;
+      }
+    }
+    return std::nullopt;
+  }
+
+  std::optional<std::string_view> takeString() {
+    if (auto t = peek()) {
+      if (auto s = t->getString()) {
+        ++lexer;
+        return s;
+      }
+    }
+    return {};
+  }
+
+  std::optional<Name> takeName() {
+    // TODO: Move this to lexer and validate UTF.
+    if (auto str = takeString()) {
+      // Copy to a std::string to make sure we have a null terminator, otherwise
+      // the `Name` constructor won't work correctly.
+      // TODO: Update `Name` to use string_view instead of char* and/or to take
+      // rvalue strings to avoid this extra copy.
+      return Name(std::string(*str));
+    }
+    return {};
+  }
+
+  bool takeSExprStart(std::string_view expected) {
+    auto original = lexer;
+    if (takeLParen() && takeKeyword(expected)) {
+      return true;
+    }
+    lexer = original;
+    return false;
+  }
+
+  Index getPos() {
+    if (auto t = peek()) {
+      return lexer.getIndex() - t->span.size();
+    }
+    return lexer.getIndex();
+  }
+
+  [[nodiscard]] Err err(Index pos, std::string reason) {
+    std::stringstream msg;
+    msg << lexer.position(pos) << ": error: " << reason;
+    return Err{msg.str()};
+  }
+
+  [[nodiscard]] Err err(std::string reason) { return err(getPos(), reason); }
+};
+
+// =========
+// Utilities
+// =========
+
+// The location and possible name of a module-level definition in the input.
+struct DefPos {
+  Name name;
+  Index pos;
+};
+
+struct GlobalType {
+  Mutability mutability;
+  Type type;
+};
+
+// A signature type and parameter names (possibly empty), used for parsing
+// function types.
+struct TypeUse {
+  HeapType type;
+  std::vector<Name> names;
+};
+
+struct ImportNames {
+  Name mod;
+  Name nm;
+};
+
+struct Limits {
+  uint64_t initial;
+  uint64_t max;
+};
+
+struct MemType {
+  Type type;
+  Limits limits;
+  bool shared;
+};
+
+struct Memarg {
+  uint64_t offset;
+  uint32_t align;
+};
+
+// RAII utility for temporarily changing the parsing position of a parsing
+// context.
+template<typename Ctx> struct WithPosition {
+  Ctx& ctx;
+  Index original;
+
+  WithPosition(Ctx& ctx, Index pos) : ctx(ctx), original(ctx.in.getPos()) {
+    ctx.in.lexer.setIndex(pos);
+  }
+
+  ~WithPosition() { ctx.in.lexer.setIndex(original); }
+};
+
+// Deduction guide to satisfy -Wctad-maybe-unsupported.
+template<typename Ctx> WithPosition(Ctx& ctx, Index) -> WithPosition<Ctx>;
+
+using IndexMap = std::unordered_map<Name, Index>;
+
+void applyImportNames(Importable& item, ImportNames* names) {
+  if (names) {
+    item.module = names->mod;
+    item.base = names->nm;
+  }
+}
+
+Result<> addExports(ParseInput& in,
+                    Module& wasm,
+                    const Named* item,
+                    const std::vector<Name>& exports,
+                    ExternalKind kind) {
+  for (auto name : exports) {
+    if (wasm.getExportOrNull(name)) {
+      // TODO: Fix error location
+      return in.err("repeated export name");
+    }
+    wasm.addExport(Builder(wasm).makeExport(name, item->name, kind));
+  }
+  return Ok{};
+}
+
+Result<IndexMap> createIndexMap(ParseInput& in,
+                                const std::vector<DefPos>& defs) {
+  IndexMap indices;
+  for (Index i = 0; i < defs.size(); ++i) {
+    if (defs[i].name.is()) {
+      if (!indices.insert({defs[i].name, i}).second) {
+        return in.err(defs[i].pos, "duplicate element name");
+      }
+    }
+  }
+  return indices;
+}
+
+std::vector<Type> getUnnamedTypes(const std::vector<NameType>& named) {
+  std::vector<Type> types;
+  types.reserve(named.size());
+  for (auto& t : named) {
+    types.push_back(t.type);
+  }
+  return types;
+}
+
+template<typename Ctx>
+Result<> parseDefs(Ctx& ctx,
+                   const std::vector<DefPos>& defs,
+                   MaybeResult<> (*parser)(Ctx&)) {
+  for (Index i = 0; i < defs.size(); ++i) {
+    ctx.index = i;
+    WithPosition with(ctx, defs[i].pos);
+    auto parsed = parser(ctx);
+    CHECK_ERR(parsed);
+    assert(parsed);
+  }
+  return Ok{};
+}
+
+// ===============
+// Parser Contexts
+// ===============
+
+struct NullTypeParserCtx {
+  using IndexT = Ok;
+  using HeapTypeT = Ok;
+  using TypeT = Ok;
+  using ParamsT = Ok;
+  using ResultsT = Ok;
+  using SignatureT = Ok;
+  using StorageT = Ok;
+  using FieldT = Ok;
+  using FieldsT = Ok;
+  using StructT = Ok;
+  using ArrayT = Ok;
+  using LimitsT = Ok;
+  using MemTypeT = Ok;
+  using GlobalTypeT = Ok;
+  using TypeUseT = Ok;
+  using LocalsT = Ok;
+  using DataStringT = Ok;
+
+  HeapTypeT makeFunc() { return Ok{}; }
+  HeapTypeT makeAny() { return Ok{}; }
+  HeapTypeT makeExtern() { return Ok{}; }
+  HeapTypeT makeEq() { return Ok{}; }
+  HeapTypeT makeI31() { return Ok{}; }
+  HeapTypeT makeData() { return Ok{}; }
+
+  TypeT makeI32() { return Ok{}; }
+  TypeT makeI64() { return Ok{}; }
+  TypeT makeF32() { return Ok{}; }
+  TypeT makeF64() { return Ok{}; }
+  TypeT makeV128() { return Ok{}; }
+
+  TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; }
+
+  ParamsT makeParams() { return Ok{}; }
+  void appendParam(ParamsT&, Name, TypeT) {}
+
+  ResultsT makeResults() { return Ok{}; }
+  void appendResult(ResultsT&, TypeT) {}
+
+  SignatureT makeFuncType(ParamsT*, ResultsT*) { return Ok{}; }
+
+  StorageT makeI8() { return Ok{}; }
+  StorageT makeI16() { return Ok{}; }
+  StorageT makeStorageType(TypeT) { return Ok{}; }
+
+  FieldT makeFieldType(StorageT, Mutability) { return Ok{}; }
+
+  FieldsT makeFields() { return Ok{}; }
+  void appendField(FieldsT&, Name, FieldT) {}
+
+  StructT makeStruct(FieldsT&) { return Ok{}; }
+
+  std::optional<ArrayT> makeArray(FieldsT&) { return Ok{}; }
+
+  GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; }
+
+  LocalsT makeLocals() { return Ok{}; }
+  void appendLocal(LocalsT&, Name, TypeT) {}
+
+  Result<Index> getTypeIndex(Name) { return 1; }
+  Result<HeapTypeT> getHeapTypeFromIdx(Index) { return Ok{}; }
+
+  DataStringT makeDataString() { return Ok{}; }
+  void appendDataString(DataStringT&, std::string_view) {}
+
+  LimitsT makeLimits(uint64_t, std::optional<uint64_t>) { return Ok{}; }
+  LimitsT getLimitsFromData(DataStringT) { return Ok{}; }
+
+  MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; }
+};
+
+template<typename Ctx> struct TypeParserCtx {
+  using IndexT = Index;
+  using HeapTypeT = HeapType;
+  using TypeT = Type;
+  using ParamsT = std::vector<NameType>;
+  using ResultsT = std::vector<Type>;
+  using SignatureT = Signature;
+  using StorageT = Field;
+  using FieldT = Field;
+  using FieldsT = std::pair<std::vector<Name>, std::vector<Field>>;
+  using StructT = std::pair<std::vector<Name>, Struct>;
+  using ArrayT = Array;
+  using LimitsT = Limits;
+  using MemTypeT = MemType;
+  using LocalsT = std::vector<NameType>;
+  using DataStringT = std::vector<char>;
+
+  // Map heap type names to their indices.
+  const IndexMap& typeIndices;
+
+  TypeParserCtx(const IndexMap& typeIndices) : typeIndices(typeIndices) {}
+
+  Ctx& self() { return *static_cast<Ctx*>(this); }
+
+  HeapTypeT makeFunc() { return HeapType::func; }
+  HeapTypeT makeAny() { return HeapType::any; }
+  HeapTypeT makeExtern() { return HeapType::ext; }
+  HeapTypeT makeEq() { return HeapType::eq; }
+  HeapTypeT makeI31() { return HeapType::i31; }
+  HeapTypeT makeData() { return HeapType::data; }
+
+  TypeT makeI32() { return Type::i32; }
+  TypeT makeI64() { return Type::i64; }
+  TypeT makeF32() { return Type::f32; }
+  TypeT makeF64() { return Type::f64; }
+  TypeT makeV128() { return Type::v128; }
+
+  TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
+    return Type(ht, nullability);
+  }
+
+  TypeT makeTupleType(const std::vector<Type> types) { return Tuple(types); }
+
+  ParamsT makeParams() { return {}; }
+  void appendParam(ParamsT& params, Name id, TypeT type) {
+    params.push_back({id, type});
+  }
+
+  ResultsT makeResults() { return {}; }
+  void appendResult(ResultsT& results, TypeT type) { results.push_back(type); }
+
+  SignatureT makeFuncType(ParamsT* params, ResultsT* results) {
+    std::vector<Type> empty;
+    const auto& paramTypes = params ? getUnnamedTypes(*params) : empty;
+    const auto& resultTypes = results ? *results : empty;
+    return Signature(self().makeTupleType(paramTypes),
+                     self().makeTupleType(resultTypes));
+  }
+
+  StorageT makeI8() { return Field(Field::i8, Immutable); }
+  StorageT makeI16() { return Field(Field::i16, Immutable); }
+  StorageT makeStorageType(TypeT type) { return Field(type, Immutable); }
+
+  FieldT makeFieldType(FieldT field, Mutability mutability) {
+    if (field.packedType == Field::not_packed) {
+      return Field(field.type, mutability);
+    }
+    return Field(field.packedType, mutability);
+  }
+
+  FieldsT makeFields() { return {}; }
+  void appendField(FieldsT& fields, Name name, FieldT field) {
+    fields.first.push_back(name);
+    fields.second.push_back(field);
+  }
+
+  StructT makeStruct(FieldsT& fields) {
+    return {std::move(fields.first), Struct(std::move(fields.second))};
+  }
+
+  std::optional<ArrayT> makeArray(FieldsT& fields) {
+    if (fields.second.size() == 1) {
+      return Array(fields.second[0]);
+    }
+    return {};
+  }
+
+  LocalsT makeLocals() { return {}; }
+  void appendLocal(LocalsT& locals, Name id, TypeT type) {
+    locals.push_back({id, type});
+  }
+
+  Result<Index> getTypeIndex(Name id) {
+    auto it = typeIndices.find(id);
+    if (it == typeIndices.end()) {
+      return self().in.err("unknown type identifier");
+    }
+    return it->second;
+  }
+
+  std::vector<char> makeDataString() { return {}; }
+  void appendDataString(std::vector<char>& data, std::string_view str) {
+    data.insert(data.end(), str.begin(), str.end());
+  }
+
+  Limits makeLimits(uint64_t n, std::optional<uint64_t> m) {
+    return m ? Limits{n, *m} : Limits{n, Memory::kUnlimitedSize};
+  }
+  Limits getLimitsFromData(const std::vector<char>& data) {
+    uint64_t size = (data.size() + Memory::kPageSize - 1) / Memory::kPageSize;
+    return {size, size};
+  }
+
+  MemType makeMemType(Type type, Limits limits, bool shared) {
+    return {type, limits, shared};
+  }
+};
+
+struct NullInstrParserCtx {
+  using InstrT = Ok;
+  using InstrsT = Ok;
+  using ExprT = Ok;
+
+  using FieldIdxT = Ok;
+  using LocalT = Ok;
+  using GlobalT = Ok;
+  using MemoryT = Ok;
+
+  using MemargT = Ok;
+
+  InstrsT makeInstrs() { return Ok{}; }
+  void appendInstr(InstrsT&, InstrT) {}
+  InstrsT finishInstrs(InstrsT&) { return Ok{}; }
+
+  ExprT makeExpr(InstrsT) { return Ok{}; }
+
+  template<typename HeapTypeT> FieldIdxT getFieldFromIdx(HeapTypeT, uint32_t) {
+    return Ok{};
+  }
+  template<typename HeapTypeT> FieldIdxT getFieldFromName(HeapTypeT, Name) {
+    return Ok{};
+  }
+  LocalT getLocalFromIdx(uint32_t) { return Ok{}; }
+  LocalT getLocalFromName(Name) { return Ok{}; }
+  GlobalT getGlobalFromIdx(uint32_t) { return Ok{}; }
+  GlobalT getGlobalFromName(Name) { return Ok{}; }
+  MemoryT getMemoryFromIdx(uint32_t) { return Ok{}; }
+  MemoryT getMemoryFromName(Name) { return Ok{}; }
+
+  MemargT getMemarg(uint64_t, uint32_t) { return Ok{}; }
+
+  InstrT makeUnreachable(Index) { return Ok{}; }
+  InstrT makeNop(Index) { return Ok{}; }
+  InstrT makeBinary(Index, BinaryOp) { return Ok{}; }
+  InstrT makeUnary(Index, UnaryOp) { return Ok{}; }
+  template<typename ResultsT> InstrT makeSelect(Index, ResultsT*) {
+    return Ok{};
+  }
+  InstrT makeDrop(Index) { return Ok{}; }
+  InstrT makeMemorySize(Index, MemoryT*) { return Ok{}; }
+  InstrT makeMemoryGrow(Index, MemoryT*) { return Ok{}; }
+  InstrT makeLocalGet(Index, LocalT) { return Ok{}; }
+  InstrT makeLocalTee(Index, LocalT) { return Ok{}; }
+  InstrT makeLocalSet(Index, LocalT) { return Ok{}; }
+  InstrT makeGlobalGet(Index, GlobalT) { return Ok{}; }
+  InstrT makeGlobalSet(Index, GlobalT) { return Ok{}; }
+
+  InstrT makeI32Const(Index, uint32_t) { return Ok{}; }
+  InstrT makeI64Const(Index, uint64_t) { return Ok{}; }
+  InstrT makeF32Const(Index, float) { return Ok{}; }
+  InstrT makeF64Const(Index, double) { return Ok{}; }
+  InstrT makeLoad(Index, Type, bool, int, bool, MemoryT*, MemargT) {
+    return Ok{};
+  }
+  InstrT makeStore(Index, Type, int, bool, MemoryT*, MemargT) { return Ok{}; }
+  InstrT makeAtomicRMW(Index, AtomicRMWOp, Type, int, MemoryT*, MemargT) {
+    return Ok{};
+  }
+  InstrT makeAtomicCmpxchg(Index, Type, int, MemoryT*, MemargT) { return Ok{}; }
+  InstrT makeAtomicWait(Index, Type, MemoryT*, MemargT) { return Ok{}; }
+  InstrT makeAtomicNotify(Index, MemoryT*, MemargT) { return Ok{}; }
+  InstrT makeAtomicFence(Index) { return Ok{}; }
+  InstrT makeSIMDExtract(Index, SIMDExtractOp, uint8_t) { return Ok{}; }
+  InstrT makeSIMDReplace(Index, SIMDReplaceOp, uint8_t) { return Ok{}; }
+  InstrT makeSIMDShuffle(Index, const std::array<uint8_t, 16>&) { return Ok{}; }
+  InstrT makeSIMDTernary(Index, SIMDTernaryOp) { return Ok{}; }
+  InstrT makeSIMDShift(Index, SIMDShiftOp) { return Ok{}; }
+  InstrT makeSIMDLoad(Index, SIMDLoadOp, MemoryT*, MemargT) { return Ok{}; }
+  InstrT makeSIMDLoadStoreLane(
+    Index, SIMDLoadStoreLaneOp, MemoryT*, MemargT, uint8_t) {
+    return Ok{};
+  }
+
+  InstrT makeMemoryCopy(Index, MemoryT*, MemoryT*) { return Ok{}; }
+  InstrT makeMemoryFill(Index, MemoryT*) { return Ok{}; }
+
+  InstrT makeReturn(Index) { return Ok{}; }
+  template<typename HeapTypeT> InstrT makeRefNull(Index, HeapTypeT) {
+    return Ok{};
+  }
+  InstrT makeRefIs(Index, RefIsOp) { return Ok{}; }
+
+  InstrT makeRefEq(Index) { return Ok{}; }
+
+  InstrT makeI31New(Index) { return Ok{}; }
+  InstrT makeI31Get(Index, bool) { return Ok{}; }
+
+  template<typename HeapTypeT> InstrT makeStructNew(Index, HeapTypeT) {
+    return Ok{};
+  }
+  template<typename HeapTypeT> InstrT makeStructNewDefault(Index, HeapTypeT) {
+    return Ok{};
+  }
+  template<typename HeapTypeT>
+  InstrT makeStructGet(Index, HeapTypeT, FieldIdxT, bool) {
+    return Ok{};
+  }
+  template<typename HeapTypeT>
+  InstrT makeStructSet(Index, HeapTypeT, FieldIdxT) {
+    return Ok{};
+  }
+};
+
+// Phase 1: Parse definition spans for top-level module elements and determine
+// their indices and names.
+struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx {
+  ParseInput in;
+
+  // At this stage we only look at types to find implicit type definitions,
+  // which are inserted directly in to the context. We cannot materialize or
+  // validate any types because we don't know what types exist yet.
+  //
+  // Declared module elements are inserted into the module, but their bodies are
+  // not filled out until later parsing phases.
+  Module& wasm;
+
+  // The module element definitions we are parsing in this phase.
+  std::vector<DefPos> typeDefs;
+  std::vector<DefPos> subtypeDefs;
+  std::vector<DefPos> funcDefs;
+  std::vector<DefPos> memoryDefs;
+  std::vector<DefPos> globalDefs;
+
+  // Positions of typeuses that might implicitly define new types.
+  std::vector<Index> implicitTypeDefs;
+
+  // Counters used for generating names for module elements.
+  int funcCounter = 0;
+  int memoryCounter = 0;
+  int globalCounter = 0;
+
+  // Used to verify that all imports come before all non-imports.
+  bool hasNonImport = false;
+
+  ParseDeclsCtx(std::string_view in, Module& wasm) : in(in), wasm(wasm) {}
+
+  void addFuncType(SignatureT) {}
+  void addStructType(StructT) {}
+  void addArrayType(ArrayT) {}
+  Result<> addSubtype(Index) { return Ok{}; }
+  void finishSubtype(Name name, Index pos) {
+    subtypeDefs.push_back({name, pos});
+  }
+  size_t getRecGroupStartIndex() { return 0; }
+  void addRecGroup(Index, size_t) {}
+  void finishDeftype(Index pos) { typeDefs.push_back({{}, pos}); }
+
+  Result<TypeUseT>
+  makeTypeUse(Index pos, std::optional<HeapTypeT> type, ParamsT*, ResultsT*) {
+    if (!type) {
+      implicitTypeDefs.push_back(pos);
+    }
+    return Ok{};
+  }
+
+  Result<Function*>
+  addFuncDecl(Index pos, Name name, ImportNames* importNames) {
+    auto f = std::make_unique<Function>();
+    if (name.is()) {
+      if (wasm.getFunctionOrNull(name)) {
+        // TDOO: if the existing function is not explicitly named, fix its name
+        // and continue.
+        return in.err(pos, "repeated function name");
+      }
+      f->setExplicitName(name);
+    } else {
+      name = (importNames ? "fimport$" : "") + std::to_string(funcCounter++);
+      name = Names::getValidFunctionName(wasm, name);
+      f->name = name;
+    }
+    applyImportNames(*f, importNames);
+    return wasm.addFunction(std::move(f));
+  }
+
+  Result<> addFunc(Name name,
+                   const std::vector<Name>& exports,
+                   ImportNames* import,
+                   TypeUseT type,
+                   std::optional<LocalsT>,
+                   std::optional<InstrsT>,
+                   Index pos) {
+    if (import && hasNonImport) {
+      return in.err(pos, "import after non-import");
+    }
+    auto f = addFuncDecl(pos, name, import);
+    CHECK_ERR(f);
+    CHECK_ERR(addExports(in, wasm, *f, exports, ExternalKind::Function));
+    funcDefs.push_back({name, pos});
+    return Ok{};
+  }
+
+  Result<Memory*>
+  addMemoryDecl(Index pos, Name name, ImportNames* importNames) {
+    auto m = std::make_unique<Memory>();
+    if (name) {
+      // TODO: if the existing memory is not explicitly named, fix its name
+      // and continue.
+      if (wasm.getMemoryOrNull(name)) {
+        return in.err(pos, "repeated memory name");
+      }
+      m->setExplicitName(name);
+    } else {
+      name = (importNames ? "mimport$" : "") + std::to_string(memoryCounter++);
+      name = Names::getValidMemoryName(wasm, name);
+      m->name = name;
+    }
+    applyImportNames(*m, importNames);
+    return wasm.addMemory(std::move(m));
+  }
+
+  Result<> addMemory(Name name,
+                     const std::vector<Name>& exports,
+                     ImportNames* import,
+                     MemTypeT,
+                     Index pos) {
+    if (import && hasNonImport) {
+      return in.err(pos, "import after non-import");
+    }
+    auto m = addMemoryDecl(pos, name, import);
+    CHECK_ERR(m);
+    CHECK_ERR(addExports(in, wasm, *m, exports, ExternalKind::Memory));
+    memoryDefs.push_back({name, pos});
+    return Ok{};
+  }
+
+  Result<Global*>
+  addGlobalDecl(Index pos, Name name, ImportNames* importNames) {
+    auto g = std::make_unique<Global>();
+    if (name) {
+      if (wasm.getGlobalOrNull(name)) {
+        // TODO: if the existing global is not explicitly named, fix its name
+        // and continue.
+        return in.err(pos, "repeated global name");
+      }
+      g->setExplicitName(name);
+    } else {
+      name = (importNames ? "gimport$" : "") + std::to_string(globalCounter++);
+      name = Names::getValidGlobalName(wasm, name);
+      g->name = name;
+    }
+    applyImportNames(*g, importNames);
+    return wasm.addGlobal(std::move(g));
+  }
+
+  Result<> addGlobal(Name name,
+                     const std::vector<Name>& exports,
+                     ImportNames* import,
+                     GlobalTypeT,
+                     std::optional<ExprT>,
+                     Index pos) {
+    if (import && hasNonImport) {
+      return in.err(pos, "import after non-import");
+    }
+    auto g = addGlobalDecl(pos, name, import);
+    CHECK_ERR(g);
+    CHECK_ERR(addExports(in, wasm, *g, exports, ExternalKind::Global));
+    globalDefs.push_back({name, pos});
+    return Ok{};
+  }
+};
+
+// Phase 2: Parse type definitions into a TypeBuilder.
+struct ParseTypeDefsCtx : TypeParserCtx<ParseTypeDefsCtx> {
+  ParseInput in;
+
+  // We update slots in this builder as we parse type definitions.
+  TypeBuilder& builder;
+
+  // Parse the names of types and fields as we go.
+  std::vector<TypeNames> names;
+
+  // The index of the subtype definition we are parsing.
+  Index index = 0;
+
+  ParseTypeDefsCtx(std::string_view in,
+                   TypeBuilder& builder,
+                   const IndexMap& typeIndices)
+    : TypeParserCtx<ParseTypeDefsCtx>(typeIndices), in(in), builder(builder),
+      names(builder.size()) {}
+
+  TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
+    return builder.getTempRefType(ht, nullability);
+  }
+
+  TypeT makeTupleType(const std::vector<Type> types) {
+    return builder.getTempTupleType(types);
+  }
+
+  Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
+    if (idx >= builder.size()) {
+      return in.err("type index out of bounds");
+    }
+    return builder[idx];
+  }
+
+  void addFuncType(SignatureT& type) { builder[index] = type; }
+
+  void addStructType(StructT& type) {
+    auto& [fieldNames, str] = type;
+    builder[index] = str;
+    for (Index i = 0; i < fieldNames.size(); ++i) {
+      if (auto name = fieldNames[i]; name.is()) {
+        names[index].fieldNames[i] = name;
+      }
+    }
+  }
+
+  void addArrayType(ArrayT& type) { builder[index] = type; }
+
+  Result<> addSubtype(Index super) {
+    if (super >= builder.size()) {
+      return in.err("supertype index out of bounds");
+    }
+    builder[index].subTypeOf(builder[super]);
+    return Ok{};
+  }
+
+  void finishSubtype(Name name, Index pos) { names[index++].name = name; }
+
+  size_t getRecGroupStartIndex() { return index; }
+
+  void addRecGroup(Index start, size_t len) {
+    builder.createRecGroup(start, len);
+  }
+
+  void finishDeftype(Index) {}
+};
+
+// Phase 3: Parse type uses to find implicitly defined types.
+struct ParseImplicitTypeDefsCtx : TypeParserCtx<ParseImplicitTypeDefsCtx> {
+  using TypeUseT = Ok;
+
+  ParseInput in;
+
+  // Types parsed so far.
+  std::vector<HeapType>& types;
+
+  // Map typeuse positions without an explicit type to the correct type.
+  std::unordered_map<Index, HeapType>& implicitTypes;
+
+  // Map signatures to the first defined heap type they match.
+  std::unordered_map<Signature, HeapType> sigTypes;
+
+  ParseImplicitTypeDefsCtx(std::string_view in,
+                           std::vector<HeapType>& types,
+                           std::unordered_map<Index, HeapType>& implicitTypes,
+                           const IndexMap& typeIndices)
+    : TypeParserCtx<ParseImplicitTypeDefsCtx>(typeIndices), in(in),
+      types(types), implicitTypes(implicitTypes) {
+    for (auto type : types) {
+      if (type.isSignature() && type.getRecGroup().size() == 1) {
+        sigTypes.insert({type.getSignature(), type});
+      }
+    }
+  }
+
+  Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
+    if (idx >= types.size()) {
+      return in.err("type index out of bounds");
+    }
+    return types[idx];
+  }
+
+  Result<TypeUseT> makeTypeUse(Index pos,
+                               std::optional<HeapTypeT>,
+                               ParamsT* params,
+                               ResultsT* results) {
+    std::vector<Type> paramTypes;
+    if (params) {
+      paramTypes = getUnnamedTypes(*params);
+    }
+
+    std::vector<Type> resultTypes;
+    if (results) {
+      resultTypes = *results;
+    }
+
+    auto sig = Signature(Type(paramTypes), Type(resultTypes));
+    auto [it, inserted] = sigTypes.insert({sig, HeapType::func});
+    if (inserted) {
+      auto type = HeapType(sig);
+      it->second = type;
+      types.push_back(type);
+    }
+    implicitTypes.insert({pos, it->second});
+
+    return Ok{};
+  }
+};
+
+// Phase 4: Parse and set the types of module elements.
+struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
+                             NullInstrParserCtx {
+  // In this phase we have constructed all the types, so we can materialize and
+  // validate them when they are used.
+
+  using GlobalTypeT = GlobalType;
+  using TypeUseT = TypeUse;
+
+  ParseInput in;
+
+  Module& wasm;
+
+  const std::vector<HeapType>& types;
+  const std::unordered_map<Index, HeapType>& implicitTypes;
+
+  // The index of the current type.
+  Index index = 0;
+
+  ParseModuleTypesCtx(std::string_view in,
+                      Module& wasm,
+                      const std::vector<HeapType>& types,
+                      const std::unordered_map<Index, HeapType>& implicitTypes,
+                      const IndexMap& typeIndices)
+    : TypeParserCtx<ParseModuleTypesCtx>(typeIndices), in(in), wasm(wasm),
+      types(types), implicitTypes(implicitTypes) {}
+
+  Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
+    if (idx >= types.size()) {
+      return in.err("type index out of bounds");
+    }
+    return types[idx];
+  }
+
+  Result<TypeUseT> makeTypeUse(Index pos,
+                               std::optional<HeapTypeT> type,
+                               ParamsT* params,
+                               ResultsT* results) {
+    std::vector<Name> ids;
+    if (params) {
+      ids.reserve(params->size());
+      for (auto& p : *params) {
+        ids.push_back(p.name);
+      }
+    }
+
+    if (type) {
+      return TypeUse{*type, ids};
+    }
+
+    auto it = implicitTypes.find(pos);
+    assert(it != implicitTypes.end());
+
+    return TypeUse{it->second, ids};
+  }
+
+  GlobalTypeT makeGlobalType(Mutability mutability, TypeT type) {
+    return {mutability, type};
+  }
+
+  Result<> addFunc(Name name,
+                   const std::vector<Name>&,
+                   ImportNames*,
+                   TypeUse type,
+                   std::optional<LocalsT> locals,
+                   std::optional<InstrsT>,
+                   Index pos) {
+    auto& f = wasm.functions[index];
+    if (!type.type.isSignature()) {
+      return in.err(pos, "expected signature type");
+    }
+    f->type = type.type;
+    for (Index i = 0; i < type.names.size(); ++i) {
+      if (type.names[i].is()) {
+        f->setLocalName(i, type.names[i]);
+      }
+    }
+    if (locals) {
+      for (auto& l : *locals) {
+        Builder::addVar(f.get(), l.name, l.type);
+      }
+    }
+    return Ok{};
+  }
+
+  Result<> addMemory(
+    Name, const std::vector<Name>&, ImportNames*, MemType type, Index pos) {
+    auto& m = wasm.memories[index];
+    m->indexType = type.type;
+    m->initial = type.limits.initial;
+    m->max = type.limits.max;
+    m->shared = type.shared;
+    return Ok{};
+  }
+
+  Result<> addGlobal(Name,
+                     const std::vector<Name>&,
+                     ImportNames*,
+                     GlobalType type,
+                     std::optional<ExprT>,
+                     Index) {
+    auto& g = wasm.globals[index];
+    g->mutable_ = type.mutability;
+    g->type = type.type;
+    return Ok{};
+  }
+};
+
+// Phase 5: Parse module element definitions, including instructions.
+struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
+  using GlobalTypeT = Ok;
+  using TypeUseT = HeapType;
+
+  // Keep track of instructions internally rather than letting the general
+  // parser collect them.
+  using InstrT = Ok;
+  using InstrsT = std::vector<Expression*>;
+  using ExprT = Expression*;
+
+  using FieldIdxT = Index;
+  using LocalT = Index;
+  using GlobalT = Name;
+  using MemoryT = Name;
+
+  using MemargT = Memarg;
+
+  ParseInput in;
+
+  Module& wasm;
+  Builder builder;
+
+  const std::vector<HeapType>& types;
+  const std::unordered_map<Index, HeapType>& implicitTypes;
+
+  // The index of the current module element.
+  Index index = 0;
+
+  // The current function being parsed, used to create scratch locals, type
+  // local.get, etc.
+  Function* func = nullptr;
+
+  // The stack of parsed expressions, used as the children of newly parsed
+  // expressions.
+  std::vector<Expression*> exprStack;
+
+  // Whether we have seen an unreachable instruction and are in
+  // stack-polymorphic unreachable mode.
+  bool unreachable = false;
+
+  // The expected result type of the instruction sequence we are parsing.
+  Type type;
+
+  ParseDefsCtx(std::string_view in,
+               Module& wasm,
+               const std::vector<HeapType>& types,
+               const std::unordered_map<Index, HeapType>& implicitTypes,
+               const IndexMap& typeIndices)
+    : TypeParserCtx(typeIndices), in(in), wasm(wasm), builder(wasm),
+      types(types), implicitTypes(implicitTypes) {}
+
+  Result<> push(Index pos, Expression* expr) {
+    if (expr->type == Type::unreachable) {
+      // We want to avoid popping back past this most recent unreachable
+      // instruction. Drop all prior instructions so they won't be consumed by
+      // later instructions but will still be emitted for their side effects, if
+      // any.
+      for (auto& expr : exprStack) {
+        expr = builder.dropIfConcretelyTyped(expr);
+      }
+      unreachable = true;
+      exprStack.push_back(expr);
+    } else if (expr->type.isTuple()) {
+      auto scratchIdx = addScratchLocal(pos, expr->type);
+      CHECK_ERR(scratchIdx);
+      CHECK_ERR(push(pos, builder.makeLocalSet(*scratchIdx, expr)));
+      for (Index i = 0; i < expr->type.size(); ++i) {
+        CHECK_ERR(push(pos,
+                       builder.makeTupleExtract(
+                         builder.makeLocalGet(*scratchIdx, expr->type[i]), i)));
+      }
+    } else {
+      exprStack.push_back(expr);
+    }
+    return Ok{};
+  }
+
+  Result<Expression*> pop(Index pos) {
+    // Find the suffix of expressions that do not produce values.
+    auto firstNone = exprStack.size();
+    for (; firstNone > 0; --firstNone) {
+      auto* expr = exprStack[firstNone - 1];
+      if (expr->type != Type::none) {
+        break;
+      }
+    }
+
+    if (firstNone == 0) {
+      // There are no expressions that produce values.
+      if (unreachable) {
+        return builder.makeUnreachable();
+      }
+      return in.err(pos, "popping from empty stack");
+    }
+
+    if (firstNone == exprStack.size()) {
+      // The last expression produced a value.
+      auto expr = exprStack.back();
+      exprStack.pop_back();
+      return expr;
+    }
+
+    // We need to assemble a block of expressions that returns the value of the
+    // first one using a scratch local (unless it's unreachable, in which case
+    // we can throw the following expressions away).
+    auto* expr = exprStack[firstNone - 1];
+    if (expr->type == Type::unreachable) {
+      exprStack.resize(firstNone - 1);
+      return expr;
+    }
+    auto scratchIdx = addScratchLocal(pos, expr->type);
+    CHECK_ERR(scratchIdx);
+    std::vector<Expression*> exprs;
+    exprs.reserve(exprStack.size() - firstNone + 2);
+    exprs.push_back(builder.makeLocalSet(*scratchIdx, expr));
+    exprs.insert(exprs.end(), exprStack.begin() + firstNone, exprStack.end());
+    exprs.push_back(builder.makeLocalGet(*scratchIdx, expr->type));
+
+    exprStack.resize(firstNone - 1);
+    return builder.makeBlock(exprs, expr->type);
+  }
+
+  Ok makeInstrs() { return Ok{}; }
+
+  void appendInstr(Ok&, InstrT instr) {}
+
+  Result<InstrsT> finishInstrs(Ok&) {
+    // We have finished parsing a sequence of instructions. Fix up the parsed
+    // instructions and reset the context for the next sequence.
+    if (type.isTuple()) {
+      std::vector<Expression*> elems(type.size());
+      bool hadUnreachableElem = false;
+      for (size_t i = 0; i < elems.size(); ++i) {
+        auto elem = pop(self().in.getPos());
+        CHECK_ERR(elem);
+        elems[elems.size() - 1 - i] = *elem;
+        if ((*elem)->type == Type::unreachable) {
+          // We don't want to pop back past an unreachable here. Push the
+          // unreachable back and throw away any post-unreachable values we have
+          // popped.
+          exprStack.push_back(*elem);
+          hadUnreachableElem = true;
+          break;
+        }
+      }
+      if (!hadUnreachableElem) {
+        exprStack.push_back(builder.makeTupleMake(std::move(elems)));
+      }
+    } else if (type != Type::none) {
+      // Ensure the last expression produces the value.
+      auto expr = pop(self().in.getPos());
+      CHECK_ERR(expr);
+      exprStack.push_back(*expr);
+    }
+    unreachable = false;
+    return std::move(exprStack);
+  }
+
+  GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; }
+
+  Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
+    if (idx >= types.size()) {
+      return in.err("type index out of bounds");
+    }
+    return types[idx];
+  }
+
+  Result<Index> getFieldFromIdx(HeapType type, uint32_t idx) {
+    if (!type.isStruct()) {
+      return in.err("expected struct type");
+    }
+    if (idx >= type.getStruct().fields.size()) {
+      return in.err("struct index out of bounds");
+    }
+    return idx;
+  }
+
+  Result<Index> getFieldFromName(HeapType type, Name name) {
+    // TODO: Field names
+    return in.err("symbolic field names note yet supported");
+  }
+
+  Result<Index> getLocalFromIdx(uint32_t idx) {
+    if (!func) {
+      return in.err("cannot access locals outside of a funcion");
+    }
+    if (idx >= func->getNumLocals()) {
+      return in.err("local index out of bounds");
+    }
+    return idx;
+  }
+
+  Result<Index> getLocalFromName(Name name) {
+    if (!func) {
+      return in.err("cannot access locals outside of a function");
+    }
+    if (!func->hasLocalIndex(name)) {
+      return in.err("local $" + name.toString() + " does not exist");
+    }
+    return func->getLocalIndex(name);
+  }
+
+  Result<Name> getGlobalFromIdx(uint32_t idx) {
+    if (idx >= wasm.globals.size()) {
+      return in.err("global index out of bounds");
+    }
+    return wasm.globals[idx]->name;
+  }
+
+  Result<Name> getGlobalFromName(Name name) {
+    if (!wasm.getGlobalOrNull(name)) {
+      return in.err("global $" + name.toString() + " does not exist");
+    }
+    return name;
+  }
+
+  Result<Name> getMemoryFromIdx(uint32_t idx) {
+    if (idx >= wasm.memories.size()) {
+      return in.err("memory index out of bounds");
+    }
+    return wasm.memories[idx]->name;
+  }
+
+  Result<Name> getMemoryFromName(Name name) {
+    if (!wasm.getMemoryOrNull(name)) {
+      return in.err("memory $" + name.toString() + " does not exist");
+    }
+    return name;
+  }
+
+  Result<TypeUseT> makeTypeUse(Index pos,
+                               std::optional<HeapTypeT> type,
+                               ParamsT* params,
+                               ResultsT* results) {
+    if (type && (params || results)) {
+      std::vector<Type> paramTypes;
+      if (params) {
+        paramTypes = getUnnamedTypes(*params);
+      }
+
+      std::vector<Type> resultTypes;
+      if (results) {
+        resultTypes = *results;
+      }
+
+      auto sig = Signature(Type(paramTypes), Type(resultTypes));
+
+      if (!type->isSignature() || type->getSignature() != sig) {
+        return in.err(pos, "type does not match provided signature");
+      }
+    }
+
+    if (type) {
+      return *type;
+    }
+
+    auto it = implicitTypes.find(pos);
+    assert(it != implicitTypes.end());
+    return it->second;
+  }
+
+  Result<> addFunc(Name,
+                   const std::vector<Name>&,
+                   ImportNames*,
+                   TypeUseT,
+                   std::optional<LocalsT>,
+                   std::optional<InstrsT> insts,
+                   Index) {
+    Expression* body;
+    if (insts) {
+      switch (insts->size()) {
+        case 0:
+          body = builder.makeNop();
+          break;
+        case 1:
+          body = insts->back();
+          break;
+        default:
+          body = builder.makeBlock(*insts, wasm.functions[index]->getResults());
+          break;
+      }
+    } else {
+      body = builder.makeNop();
+    }
+    wasm.functions[index]->body = body;
+    return Ok{};
+  }
+
+  Result<> addGlobal(Name,
+                     const std::vector<Name>&,
+                     ImportNames*,
+                     GlobalTypeT,
+                     std::optional<ExprT> exp,
+                     Index) {
+    if (exp) {
+      wasm.globals[index]->init = *exp;
+    }
+    return Ok{};
+  }
+
+  Result<Index> addScratchLocal(Index pos, Type type) {
+    if (!func) {
+      return in.err(pos,
+                    "scratch local required, but there is no function context");
+    }
+    Name name = Names::getValidLocalName(*func, "scratch");
+    return Builder::addVar(func, name, type);
+  }
+
+  Expression* makeExpr(InstrsT& instrs) {
+    switch (instrs.size()) {
+      case 0:
+        return builder.makeNop();
+      case 1:
+        return instrs.front();
+      default:
+        return builder.makeBlock(instrs);
+    }
+  }
+
+  Memarg getMemarg(uint64_t offset, uint32_t align) { return {offset, align}; }
+
+  Result<> validateTypeAnnotation(Index pos, HeapType type, Expression* child) {
+    if (child->type == Type::unreachable) {
+      return Ok{};
+    }
+    if (!child->type.isRef() ||
+        !HeapType::isSubType(child->type.getHeapType(), type)) {
+      return in.err(pos, "invalid reference type on stack");
+    }
+    return Ok{};
+  }
+
+  Result<Name> getMemory(Index pos, Name* mem) {
+    if (mem) {
+      return *mem;
+    }
+    if (wasm.memories.empty()) {
+      return in.err(pos, "memory required, but there is no memory");
+    }
+    return wasm.memories[0]->name;
+  }
+
+  Result<> makeUnreachable(Index pos) {
+    return push(pos, builder.makeUnreachable());
+  }
+
+  Result<> makeNop(Index pos) { return push(pos, builder.makeNop()); }
+
+  Result<> makeBinary(Index pos, BinaryOp op) {
+    auto rhs = pop(pos);
+    CHECK_ERR(rhs);
+    auto lhs = pop(pos);
+    CHECK_ERR(lhs);
+    return push(pos, builder.makeBinary(op, *lhs, *rhs));
+  }
+
+  Result<> makeUnary(Index pos, UnaryOp op) {
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeUnary(op, *val));
+  }
+
+  Result<> makeSelect(Index pos, std::vector<Type>* res) {
+    if (res && res->size() > 1) {
+      return in.err(pos, "select may not have more than one result type");
+    }
+    auto cond = pop(pos);
+    CHECK_ERR(cond);
+    auto ifFalse = pop(pos);
+    CHECK_ERR(ifFalse);
+    auto ifTrue = pop(pos);
+    CHECK_ERR(ifTrue);
+    auto select = builder.makeSelect(*cond, *ifTrue, *ifFalse);
+    if (res && !res->empty() && !Type::isSubType(select->type, res->front())) {
+      return in.err(pos, "select type annotation is incorrect");
+    }
+    return push(pos, builder.makeSelect(*cond, *ifTrue, *ifFalse));
+  }
+
+  Result<> makeDrop(Index pos) {
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeDrop(*val));
+  }
+
+  Result<> makeMemorySize(Index pos, Name* mem) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    return push(pos, builder.makeMemorySize(*m));
+  }
+
+  Result<> makeMemoryGrow(Index pos, Name* mem) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeMemoryGrow(*val, *m));
+  }
+
+  Result<> makeLocalGet(Index pos, Index local) {
+    if (!func) {
+      return in.err(pos, "local.get must be inside a function");
+    }
+    assert(local < func->getNumLocals());
+    return push(pos, builder.makeLocalGet(local, func->getLocalType(local)));
+  }
+
+  Result<> makeLocalTee(Index pos, Index local) {
+    if (!func) {
+      return in.err(pos, "local.tee must be inside a function");
+    }
+    assert(local < func->getNumLocals());
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos,
+                builder.makeLocalTee(local, *val, func->getLocalType(local)));
+  }
+
+  Result<> makeLocalSet(Index pos, Index local) {
+    if (!func) {
+      return in.err(pos, "local.set must be inside a function");
+    }
+    assert(local < func->getNumLocals());
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeLocalSet(local, *val));
+  }
+
+  Result<> makeGlobalGet(Index pos, Name global) {
+    assert(wasm.getGlobalOrNull(global));
+    auto type = wasm.getGlobal(global)->type;
+    return push(pos, builder.makeGlobalGet(global, type));
+  }
+
+  Result<> makeGlobalSet(Index pos, Name global) {
+    assert(wasm.getGlobalOrNull(global));
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeGlobalSet(global, *val));
+  }
+
+  Result<> makeI32Const(Index pos, uint32_t c) {
+    return push(pos, builder.makeConst(Literal(c)));
+  }
+
+  Result<> makeI64Const(Index pos, uint64_t c) {
+    return push(pos, builder.makeConst(Literal(c)));
+  }
+
+  Result<> makeF32Const(Index pos, float c) {
+    return push(pos, builder.makeConst(Literal(c)));
+  }
+
+  Result<> makeF64Const(Index pos, double c) {
+    return push(pos, builder.makeConst(Literal(c)));
+  }
+
+  Result<> makeLoad(Index pos,
+                    Type type,
+                    bool signed_,
+                    int bytes,
+                    bool isAtomic,
+                    Name* mem,
+                    Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    if (isAtomic) {
+      return push(pos,
+                  builder.makeAtomicLoad(bytes, memarg.offset, *ptr, type, *m));
+    }
+    return push(pos,
+                builder.makeLoad(
+                  bytes, signed_, memarg.offset, memarg.align, *ptr, type, *m));
+  }
+
+  Result<> makeStore(
+    Index pos, Type type, int bytes, bool isAtomic, Name* mem, Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    if (isAtomic) {
+      return push(
+        pos,
+        builder.makeAtomicStore(bytes, memarg.offset, *ptr, *val, type, *m));
+    }
+    return push(pos,
+                builder.makeStore(
+                  bytes, memarg.offset, memarg.align, *ptr, *val, type, *m));
+  }
+
+  Result<> makeAtomicRMW(
+    Index pos, AtomicRMWOp op, Type type, int bytes, Name* mem, Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    return push(
+      pos,
+      builder.makeAtomicRMW(op, bytes, memarg.offset, *ptr, *val, type, *m));
+  }
+
+  Result<>
+  makeAtomicCmpxchg(Index pos, Type type, int bytes, Name* mem, Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto replacement = pop(pos);
+    CHECK_ERR(replacement);
+    auto expected = pop(pos);
+    CHECK_ERR(expected);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    return push(
+      pos,
+      builder.makeAtomicCmpxchg(
+        bytes, memarg.offset, *ptr, *expected, *replacement, type, *m));
+  }
+
+  Result<> makeAtomicWait(Index pos, Type type, Name* mem, Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto timeout = pop(pos);
+    CHECK_ERR(timeout);
+    auto expected = pop(pos);
+    CHECK_ERR(expected);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    return push(pos,
+                builder.makeAtomicWait(
+                  *ptr, *expected, *timeout, type, memarg.offset, *m));
+  }
+
+  Result<> makeAtomicNotify(Index pos, Name* mem, Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto count = pop(pos);
+    CHECK_ERR(count);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    return push(pos, builder.makeAtomicNotify(*ptr, *count, memarg.offset, *m));
+  }
+
+  Result<> makeAtomicFence(Index pos) {
+    return push(pos, builder.makeAtomicFence());
+  }
+
+  Result<> makeSIMDExtract(Index pos, SIMDExtractOp op, uint8_t lane) {
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeSIMDExtract(op, *val, lane));
+  }
+
+  Result<> makeSIMDReplace(Index pos, SIMDReplaceOp op, uint8_t lane) {
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    auto vec = pop(pos);
+    CHECK_ERR(vec);
+    return push(pos, builder.makeSIMDReplace(op, *vec, lane, *val));
+  }
+
+  Result<> makeSIMDShuffle(Index pos, const std::array<uint8_t, 16>& lanes) {
+    auto rhs = pop(pos);
+    CHECK_ERR(rhs);
+    auto lhs = pop(pos);
+    CHECK_ERR(lhs);
+    return push(pos, builder.makeSIMDShuffle(*lhs, *rhs, lanes));
+  }
+
+  Result<> makeSIMDTernary(Index pos, SIMDTernaryOp op) {
+    auto c = pop(pos);
+    CHECK_ERR(c);
+    auto b = pop(pos);
+    CHECK_ERR(b);
+    auto a = pop(pos);
+    CHECK_ERR(a);
+    return push(pos, builder.makeSIMDTernary(op, *a, *b, *c));
+  }
+
+  Result<> makeSIMDShift(Index pos, SIMDShiftOp op) {
+    auto shift = pop(pos);
+    CHECK_ERR(shift);
+    auto vec = pop(pos);
+    CHECK_ERR(vec);
+    return push(pos, builder.makeSIMDShift(op, *vec, *shift));
+  }
+
+  Result<> makeSIMDLoad(Index pos, SIMDLoadOp op, Name* mem, Memarg memarg) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    return push(
+      pos, builder.makeSIMDLoad(op, memarg.offset, memarg.align, *ptr, *m));
+  }
+
+  Result<> makeSIMDLoadStoreLane(
+    Index pos, SIMDLoadStoreLaneOp op, Name* mem, Memarg memarg, uint8_t lane) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto vec = pop(pos);
+    CHECK_ERR(vec);
+    auto ptr = pop(pos);
+    CHECK_ERR(ptr);
+    return push(pos,
+                builder.makeSIMDLoadStoreLane(
+                  op, memarg.offset, memarg.align, lane, *ptr, *vec, *m));
+  }
+
+  Result<> makeMemoryCopy(Index pos, Name* destMem, Name* srcMem) {
+    auto destMemory = getMemory(pos, destMem);
+    CHECK_ERR(destMemory);
+    auto srcMemory = getMemory(pos, srcMem);
+    CHECK_ERR(srcMemory);
+    auto size = pop(pos);
+    CHECK_ERR(size);
+    auto src = pop(pos);
+    CHECK_ERR(src);
+    auto dest = pop(pos);
+    CHECK_ERR(dest);
+    return push(
+      pos, builder.makeMemoryCopy(*dest, *src, *size, *destMemory, *srcMemory));
+  }
+
+  Result<> makeMemoryFill(Index pos, Name* mem) {
+    auto m = getMemory(pos, mem);
+    CHECK_ERR(m);
+    auto size = pop(pos);
+    CHECK_ERR(size);
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    auto dest = pop(pos);
+    CHECK_ERR(dest);
+    return push(pos, builder.makeMemoryFill(*dest, *val, *size, *m));
+  }
+
+  Result<> makeReturn(Index pos) {
+    if (!func) {
+      return in.err("cannot return outside of a function");
+    }
+    size_t n = func->getResults().size();
+    if (n == 0) {
+      return push(pos, builder.makeReturn());
+    }
+    if (n == 1) {
+      auto val = pop(pos);
+      CHECK_ERR(val);
+      return push(pos, builder.makeReturn(*val));
+    }
+    std::vector<Expression*> vals(n);
+    for (size_t i = 0; i < n; ++i) {
+      auto val = pop(pos);
+      CHECK_ERR(val);
+      vals[n - i - 1] = *val;
+    }
+    return push(pos, builder.makeReturn(builder.makeTupleMake(vals)));
+  }
+
+  Result<> makeRefNull(Index pos, HeapType type) {
+    return push(pos, builder.makeRefNull(type));
+  }
+
+  Result<> makeRefIs(Index pos, RefIsOp op) {
+    auto ref = pop(pos);
+    CHECK_ERR(ref);
+    return push(pos, builder.makeRefIs(op, *ref));
+  }
+
+  Result<> makeRefEq(Index pos) {
+    auto rhs = pop(pos);
+    CHECK_ERR(rhs);
+    auto lhs = pop(pos);
+    CHECK_ERR(lhs);
+    return push(pos, builder.makeRefEq(*lhs, *rhs));
+  }
+
+  Result<> makeI31New(Index pos) {
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeI31New(*val));
+  }
+
+  Result<> makeI31Get(Index pos, bool signed_) {
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    return push(pos, builder.makeI31Get(*val, signed_));
+  }
+
+  Result<> makeStructNew(Index pos, HeapType type) {
+    if (!type.isStruct()) {
+      return in.err(pos, "expected struct type annotation");
+    }
+    size_t numOps = type.getStruct().fields.size();
+    std::vector<Expression*> ops(numOps);
+    for (size_t i = 0; i < numOps; ++i) {
+      auto op = pop(pos);
+      CHECK_ERR(op);
+      ops[numOps - i - 1] = *op;
+    }
+    return push(pos, builder.makeStructNew(type, ops));
+  }
+
+  Result<> makeStructNewDefault(Index pos, HeapType type) {
+    return push(pos, builder.makeStructNew(type, std::array<Expression*, 0>{}));
+  }
+
+  Result<> makeStructGet(Index pos, HeapType type, Index field, bool signed_) {
+    assert(type.isStruct());
+    const auto& fields = type.getStruct().fields;
+    assert(fields.size() > field);
+    auto fieldType = fields[field].type;
+    auto ref = pop(pos);
+    CHECK_ERR(ref);
+    CHECK_ERR(validateTypeAnnotation(pos, type, *ref));
+    return push(pos, builder.makeStructGet(field, *ref, fieldType, signed_));
+  }
+
+  Result<> makeStructSet(Index pos, HeapType type, Index field) {
+    assert(type.isStruct());
+    assert(type.getStruct().fields.size() > field);
+    auto val = pop(pos);
+    CHECK_ERR(val);
+    auto ref = pop(pos);
+    CHECK_ERR(ref);
+    CHECK_ERR(validateTypeAnnotation(pos, type, *ref));
+    return push(pos, builder.makeStructSet(field, *ref, *val));
+  }
+};
+
+// ================
+// Parser Functions
+// ================
+
+// Types
+template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::RefTypeT> reftype(Ctx&);
+template<typename Ctx> Result<typename Ctx::TypeT> valtype(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::ParamsT> params(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::ResultsT> results(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::SignatureT> functype(Ctx&);
+template<typename Ctx> Result<typename Ctx::FieldT> storagetype(Ctx&);
+template<typename Ctx> Result<typename Ctx::FieldT> fieldtype(Ctx&);
+template<typename Ctx> Result<typename Ctx::FieldsT> fields(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::StructT> structtype(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::ArrayT> arraytype(Ctx&);
+template<typename Ctx> Result<typename Ctx::LimitsT> limits32(Ctx&);
+template<typename Ctx> Result<typename Ctx::LimitsT> limits64(Ctx&);
+template<typename Ctx> Result<typename Ctx::MemTypeT> memtype(Ctx&);
+template<typename Ctx> Result<typename Ctx::GlobalTypeT> globaltype(Ctx&);
+
+// Instructions
+template<typename Ctx> MaybeResult<typename Ctx::InstrT> instr(Ctx&);
+template<typename Ctx> Result<typename Ctx::InstrsT> instrs(Ctx&);
+template<typename Ctx> Result<typename Ctx::ExprT> expr(Ctx&);
+template<typename Ctx> Result<typename Ctx::MemargT> memarg(Ctx&, uint32_t);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeUnreachable(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeNop(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBinary(Ctx&, Index, BinaryOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeUnary(Ctx&, Index, UnaryOp op);
+template<typename Ctx> Result<typename Ctx::InstrT> makeSelect(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeDrop(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeMemorySize(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryGrow(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeLocalGet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeLocalTee(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeLocalSet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeGlobalGet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeGlobalSet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeBlock(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeThenOrElse(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeConst(Ctx&, Index, Type type);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeLoad(Ctx&, Index, Type type, bool signed_, int bytes, bool isAtomic);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStore(Ctx&, Index, Type type, int bytes, bool isAtomic);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeAtomicRMW(Ctx&, Index, AtomicRMWOp op, Type type, uint8_t bytes);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeAtomicCmpxchg(Ctx&, Index, Type type, uint8_t bytes);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeAtomicWait(Ctx&, Index, Type type);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeAtomicNotify(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeAtomicFence(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDExtract(Ctx&, Index, SIMDExtractOp op, size_t lanes);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDReplace(Ctx&, Index, SIMDReplaceOp op, size_t lanes);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeSIMDShuffle(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeSIMDTernary(Ctx&, Index, SIMDTernaryOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeSIMDShift(Ctx&, Index, SIMDShiftOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDLoad(Ctx&, Index, SIMDLoadOp op, int bytes);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDLoadStoreLane(Ctx&, Index, SIMDLoadStoreLaneOp op, int bytes);
+template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryInit(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeDataDrop(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryCopy(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryFill(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makePop(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeIf(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeMaybeBlock(Ctx&, Index, size_t i, Type type);
+template<typename Ctx> Result<typename Ctx::InstrT> makeLoop(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeCall(Ctx&, Index, bool isReturn);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeCallIndirect(Ctx&, Index, bool isReturn);
+template<typename Ctx> Result<typename Ctx::InstrT> makeBreak(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeBreakTable(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeReturn(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeRefNull(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefIs(Ctx&, Index, RefIsOp op);
+template<typename Ctx> Result<typename Ctx::InstrT> makeRefFunc(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeRefEq(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeTableGet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeTableSet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeTableSize(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeTableGrow(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeTry(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeTryOrCatchBody(Ctx&, Index, Type type, bool isTry);
+template<typename Ctx> Result<typename Ctx::InstrT> makeThrow(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeRethrow(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeTupleMake(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTupleExtract(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeCallRef(Ctx&, Index, bool isReturn);
+template<typename Ctx> Result<typename Ctx::InstrT> makeI31New(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeI31Get(Ctx&, Index, bool signed_);
+template<typename Ctx> Result<typename Ctx::InstrT> makeRefTest(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefTestStatic(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeRefCast(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefCastStatic(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefCastNopStatic(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBrOn(Ctx&, Index, BrOnOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBrOnStatic(Ctx&, Index, BrOnOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStructNewStatic(Ctx&, Index, bool default_);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStructGet(Ctx&, Index, bool signed_ = false);
+template<typename Ctx> Result<typename Ctx::InstrT> makeStructSet(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayNewStatic(Ctx&, Index, bool default_);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayNewSeg(Ctx&, Index, ArrayNewSegOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayInitStatic(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayGet(Ctx&, Index, bool signed_ = false);
+template<typename Ctx> Result<typename Ctx::InstrT> makeArraySet(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeArrayLen(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeArrayCopy(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefAs(Ctx&, Index, RefAsOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringNew(Ctx&, Index, StringNewOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringConst(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringMeasure(Ctx&, Index, StringMeasureOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringEncode(Ctx&, Index, StringEncodeOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringConcat(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeStringEq(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringAs(Ctx&, Index, StringAsOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringWTF8Advance(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringWTF16Get(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringIterNext(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringIterMove(Ctx&, Index, StringIterMoveOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringSliceWTF(Ctx&, Index, StringSliceWTFOp op);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringSliceIter(Ctx&, Index);
+
+// Modules
+template<typename Ctx> MaybeResult<Index> maybeTypeidx(Ctx& ctx);
+template<typename Ctx> Result<typename Ctx::HeapTypeT> typeidx(Ctx&);
+template<typename Ctx>
+Result<typename Ctx::FieldIdxT> fieldidx(Ctx&, typename Ctx::HeapTypeT);
+template<typename Ctx> MaybeResult<typename Ctx::MemoryT> maybeMemidx(Ctx&);
+template<typename Ctx> Result<typename Ctx::MemoryT> memidx(Ctx&);
+template<typename Ctx> Result<typename Ctx::GlobalT> globalidx(Ctx&);
+template<typename Ctx> Result<typename Ctx::LocalT> localidx(Ctx&);
+template<typename Ctx> Result<typename Ctx::TypeUseT> typeuse(Ctx&);
+MaybeResult<ImportNames> inlineImport(ParseInput&);
+Result<std::vector<Name>> inlineExports(ParseInput&);
+template<typename Ctx> Result<> strtype(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::ModuleNameT> subtype(Ctx&);
+template<typename Ctx> MaybeResult<> deftype(Ctx&);
+template<typename Ctx> MaybeResult<typename Ctx::LocalsT> locals(Ctx&);
+template<typename Ctx> MaybeResult<> func(Ctx&);
+template<typename Ctx> MaybeResult<> memory(Ctx&);
+template<typename Ctx> MaybeResult<> global(Ctx&);
+template<typename Ctx> Result<typename Ctx::DataStringT> datastring(Ctx&);
+MaybeResult<> modulefield(ParseDeclsCtx&);
+Result<> module(ParseDeclsCtx&);
+
+// =====
+// Types
+// =====
+
+// heaptype ::= x:typeidx => types[x]
+//            | 'func'    => func
+//            | 'extern'  => extern
+template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx& ctx) {
+  if (ctx.in.takeKeyword("func"sv)) {
+    return ctx.makeFunc();
+  }
+  if (ctx.in.takeKeyword("any"sv)) {
+    return ctx.makeAny();
+  }
+  if (ctx.in.takeKeyword("extern"sv)) {
+    return ctx.makeExtern();
+  }
+  if (ctx.in.takeKeyword("eq"sv)) {
+    return ctx.makeEq();
+  }
+  if (ctx.in.takeKeyword("i31"sv)) {
+    return ctx.makeI31();
+  }
+  if (ctx.in.takeKeyword("data"sv)) {
+    return ctx.makeData();
+  }
+  if (ctx.in.takeKeyword("array"sv)) {
+    return ctx.in.err("array heap type not yet supported");
+  }
+  auto type = typeidx(ctx);
+  CHECK_ERR(type);
+  return *type;
+}
+
+// reftype ::= 'funcref'   => funcref
+//           | 'externref' => externref
+//           | 'anyref'    => anyref
+//           | 'eqref'     => eqref
+//           | 'i31ref'    => i31ref
+//           | 'dataref'   => dataref
+//           | 'arrayref'  => arrayref
+//           | '(' ref null? t:heaptype ')' => ref null? t
+template<typename Ctx> MaybeResult<typename Ctx::TypeT> reftype(Ctx& ctx) {
+  if (ctx.in.takeKeyword("funcref"sv)) {
+    return ctx.makeRefType(ctx.makeFunc(), Nullable);
+  }
+  if (ctx.in.takeKeyword("externref"sv)) {
+    return ctx.makeRefType(ctx.makeExtern(), Nullable);
+  }
+  if (ctx.in.takeKeyword("anyref"sv)) {
+    return ctx.makeRefType(ctx.makeAny(), Nullable);
+  }
+  if (ctx.in.takeKeyword("eqref"sv)) {
+    return ctx.makeRefType(ctx.makeEq(), Nullable);
+  }
+  if (ctx.in.takeKeyword("i31ref"sv)) {
+    return ctx.makeRefType(ctx.makeI31(), Nullable);
+  }
+  if (ctx.in.takeKeyword("dataref"sv)) {
+    return ctx.makeRefType(ctx.makeData(), Nullable);
+  }
+  if (ctx.in.takeKeyword("arrayref"sv)) {
+    return ctx.in.err("arrayref not yet supported");
+  }
+
+  if (!ctx.in.takeSExprStart("ref"sv)) {
+    return {};
+  }
+
+  auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable;
+
+  auto type = heaptype(ctx);
+  CHECK_ERR(type);
+
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of reftype");
+  }
+
+  return ctx.makeRefType(*type, nullability);
+}
+
+// numtype ::= 'i32' => i32
+//           | 'i64' => i64
+//           | 'f32' => f32
+//           | 'f64' => f64
+// vectype ::= 'v128' => v128
+// valtype ::= t:numtype => t
+//           | t:vectype => t
+//           | t:reftype => t
+template<typename Ctx> Result<typename Ctx::TypeT> valtype(Ctx& ctx) {
+  if (ctx.in.takeKeyword("i32"sv)) {
+    return ctx.makeI32();
+  } else if (ctx.in.takeKeyword("i64"sv)) {
+    return ctx.makeI64();
+  } else if (ctx.in.takeKeyword("f32"sv)) {
+    return ctx.makeF32();
+  } else if (ctx.in.takeKeyword("f64"sv)) {
+    return ctx.makeF64();
+  } else if (ctx.in.takeKeyword("v128"sv)) {
+    return ctx.makeV128();
+  } else if (auto type = reftype(ctx)) {
+    CHECK_ERR(type);
+    return *type;
+  } else {
+    return ctx.in.err("expected valtype");
+  }
+}
+
+// param  ::= '(' 'param id? t:valtype ')' => [t]
+//          | '(' 'param t*:valtype* ')' => [t*]
+// params ::= param*
+template<typename Ctx> MaybeResult<typename Ctx::ParamsT> params(Ctx& ctx) {
+  bool hasAny = false;
+  auto res = ctx.makeParams();
+  while (ctx.in.takeSExprStart("param"sv)) {
+    hasAny = true;
+    if (auto id = ctx.in.takeID()) {
+      // Single named param
+      auto type = valtype(ctx);
+      CHECK_ERR(type);
+      if (!ctx.in.takeRParen()) {
+        return ctx.in.err("expected end of param");
+      }
+      ctx.appendParam(res, *id, *type);
+    } else {
+      // Repeated unnamed params
+      while (!ctx.in.takeRParen()) {
+        auto type = valtype(ctx);
+        CHECK_ERR(type);
+        ctx.appendParam(res, {}, *type);
+      }
+    }
+  }
+  if (hasAny) {
+    return res;
+  }
+  return {};
+}
+
+// result  ::= '(' 'result' t*:valtype ')' => [t*]
+// results ::= result*
+template<typename Ctx> MaybeResult<typename Ctx::ResultsT> results(Ctx& ctx) {
+  bool hasAny = false;
+  auto res = ctx.makeResults();
+  while (ctx.in.takeSExprStart("result"sv)) {
+    hasAny = true;
+    while (!ctx.in.takeRParen()) {
+      auto type = valtype(ctx);
+      CHECK_ERR(type);
+      ctx.appendResult(res, *type);
+    }
+  }
+  if (hasAny) {
+    return res;
+  }
+  return {};
+}
+
+// functype ::= '(' 'func' t1*:vec(param) t2*:vec(result) ')' => [t1*] -> [t2*]
+template<typename Ctx>
+MaybeResult<typename Ctx::SignatureT> functype(Ctx& ctx) {
+  if (!ctx.in.takeSExprStart("func"sv)) {
+    return {};
+  }
+
+  auto parsedParams = params(ctx);
+  CHECK_ERR(parsedParams);
+
+  auto parsedResults = results(ctx);
+  CHECK_ERR(parsedResults);
+
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of functype");
+  }
+
+  return ctx.makeFuncType(parsedParams.getPtr(), parsedResults.getPtr());
+}
+
+// storagetype ::= valtype | packedtype
+// packedtype  ::= i8 | i16
+template<typename Ctx> Result<typename Ctx::FieldT> storagetype(Ctx& ctx) {
+  if (ctx.in.takeKeyword("i8"sv)) {
+    return ctx.makeI8();
+  }
+  if (ctx.in.takeKeyword("i16"sv)) {
+    return ctx.makeI16();
+  }
+  auto type = valtype(ctx);
+  CHECK_ERR(type);
+  return ctx.makeStorageType(*type);
+}
+
+// fieldtype   ::= t:storagetype               => const t
+//               | '(' 'mut' t:storagetype ')' => var t
+template<typename Ctx> Result<typename Ctx::FieldT> fieldtype(Ctx& ctx) {
+  auto mutability = Immutable;
+  if (ctx.in.takeSExprStart("mut"sv)) {
+    mutability = Mutable;
+  }
+
+  auto field = storagetype(ctx);
+  CHECK_ERR(field);
+
+  if (mutability == Mutable) {
+    if (!ctx.in.takeRParen()) {
+      return ctx.in.err("expected end of field type");
+    }
+  }
+
+  return ctx.makeFieldType(*field, mutability);
+}
+
+// field ::= '(' 'field' id t:fieldtype ')' => [(id, t)]
+//         | '(' 'field' t*:fieldtype* ')'  => [(_, t*)*]
+//         | fieldtype
+template<typename Ctx> Result<typename Ctx::FieldsT> fields(Ctx& ctx) {
+  auto res = ctx.makeFields();
+  while (true) {
+    if (auto t = ctx.in.peek(); !t || t->isRParen()) {
+      return res;
+    }
+    if (ctx.in.takeSExprStart("field")) {
+      if (auto id = ctx.in.takeID()) {
+        auto field = fieldtype(ctx);
+        CHECK_ERR(field);
+        if (!ctx.in.takeRParen()) {
+          return ctx.in.err("expected end of field");
+        }
+        ctx.appendField(res, *id, *field);
+      } else {
+        while (!ctx.in.takeRParen()) {
+          auto field = fieldtype(ctx);
+          CHECK_ERR(field);
+          ctx.appendField(res, {}, *field);
+        }
+      }
+    } else {
+      auto field = fieldtype(ctx);
+      CHECK_ERR(field);
+      ctx.appendField(res, {}, *field);
+    }
+  }
+}
+
+// structtype ::= '(' 'struct' field* ')'
+template<typename Ctx> MaybeResult<typename Ctx::StructT> structtype(Ctx& ctx) {
+  if (!ctx.in.takeSExprStart("struct"sv)) {
+    return {};
+  }
+  auto namedFields = fields(ctx);
+  CHECK_ERR(namedFields);
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of struct definition");
+  }
+
+  return ctx.makeStruct(*namedFields);
+}
+
+// arraytype ::= '(' 'array' field ')'
+template<typename Ctx> MaybeResult<typename Ctx::ArrayT> arraytype(Ctx& ctx) {
+  if (!ctx.in.takeSExprStart("array"sv)) {
+    return {};
+  }
+  auto namedFields = fields(ctx);
+  CHECK_ERR(namedFields);
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of array definition");
+  }
+
+  if (auto array = ctx.makeArray(*namedFields)) {
+    return *array;
+  }
+  return ctx.in.err("expected exactly one field in array definition");
+}
+
+// limits32 ::= n:u32 m:u32?
+template<typename Ctx> Result<typename Ctx::LimitsT> limits32(Ctx& ctx) {
+  auto n = ctx.in.takeU32();
+  if (!n) {
+    return ctx.in.err("expected initial size");
+  }
+  std::optional<uint64_t> m = ctx.in.takeU32();
+  return ctx.makeLimits(uint64_t(*n), m);
+}
+
+// limits64 ::= n:u64 m:u64?
+template<typename Ctx> Result<typename Ctx::LimitsT> limits64(Ctx& ctx) {
+  auto n = ctx.in.takeU64();
+  if (!n) {
+    return ctx.in.err("expected initial size");
+  }
+  std::optional<uint64_t> m = ctx.in.takeU64();
+  return ctx.makeLimits(uint64_t(*n), m);
+}
+
+// memtype ::= (limits32 | 'i32' limits32 | 'i64' limit64) shared?
+template<typename Ctx> Result<typename Ctx::MemTypeT> memtype(Ctx& ctx) {
+  auto type = Type::i32;
+  if (ctx.in.takeKeyword("i64"sv)) {
+    type = Type::i64;
+  } else {
+    ctx.in.takeKeyword("i32"sv);
+  }
+  auto limits = type == Type::i32 ? limits32(ctx) : limits64(ctx);
+  CHECK_ERR(limits);
+  bool shared = false;
+  if (ctx.in.takeKeyword("shared"sv)) {
+    shared = true;
+  }
+  return ctx.makeMemType(type, *limits, shared);
+}
+
+// globaltype ::= t:valtype               => const t
+//              | '(' 'mut' t:valtype ')' => var t
+template<typename Ctx> Result<typename Ctx::GlobalTypeT> globaltype(Ctx& ctx) {
+  auto mutability = Immutable;
+  if (ctx.in.takeSExprStart("mut"sv)) {
+    mutability = Mutable;
+  }
+
+  auto type = valtype(ctx);
+  CHECK_ERR(type);
+
+  if (mutability == Mutable && !ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of globaltype");
+  }
+
+  return ctx.makeGlobalType(mutability, *type);
+}
+
+// ============
+// Instructions
+// ============
+
+template<typename Ctx> MaybeResult<typename Ctx::InstrT> instr(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+  auto keyword = ctx.in.takeKeyword();
+  if (!keyword) {
+    return {};
+  }
+
+#define NEW_INSTRUCTION_PARSER
+#define NEW_WAT_PARSER
+#include <gen-s-parser.inc>
+}
+
+template<typename Ctx> Result<typename Ctx::InstrsT> instrs(Ctx& ctx) {
+  auto insts = ctx.makeInstrs();
+
+  while (true) {
+    if (ctx.in.takeLParen()) {
+      // A stack of (start, end) position pairs defining the positions of
+      // instructions that need to be parsed after their folded children.
+      std::vector<std::pair<Index, std::optional<Index>>> foldedInstrs;
+
+      // Begin a folded instruction. Push its start position and a placeholder
+      // end position.
+      foldedInstrs.push_back({ctx.in.getPos(), {}});
+      while (!foldedInstrs.empty()) {
+        // Consume everything up to the next paren. This span will be parsed as
+        // an instruction later after its folded children have been parsed.
+        if (!ctx.in.takeUntilParen()) {
+          return ctx.in.err(foldedInstrs.back().first,
+                            "unterminated folded instruction");
+        }
+
+        if (!foldedInstrs.back().second) {
+          // The folded instruction we just started should end here.
+          foldedInstrs.back().second = ctx.in.getPos();
+        }
+
+        // We have either the start of a new folded child or the end of the last
+        // one.
+        if (ctx.in.takeLParen()) {
+          foldedInstrs.push_back({ctx.in.getPos(), {}});
+        } else if (ctx.in.takeRParen()) {
+          auto [start, end] = foldedInstrs.back();
+          assert(end && "Should have found end of instruction");
+          foldedInstrs.pop_back();
+
+          WithPosition with(ctx, start);
+          if (auto inst = instr(ctx)) {
+            CHECK_ERR(inst);
+            ctx.appendInstr(insts, *inst);
+          } else {
+            return ctx.in.err(start, "expected folded instruction");
+          }
+
+          if (ctx.in.getPos() != *end) {
+            return ctx.in.err("expected end of instruction");
+          }
+        } else {
+          WASM_UNREACHABLE("expected paren");
+        }
+      }
+      continue;
+    }
+
+    // A non-folded instruction.
+    if (auto inst = instr(ctx)) {
+      CHECK_ERR(inst);
+      ctx.appendInstr(insts, *inst);
+    } else {
+      break;
+    }
+  }
+
+  return ctx.finishInstrs(insts);
+}
+
+template<typename Ctx> Result<typename Ctx::ExprT> expr(Ctx& ctx) {
+  auto insts = instrs(ctx);
+  CHECK_ERR(insts);
+  return ctx.makeExpr(*insts);
+}
+
+// memarg_n ::= o:offset a:align_n
+// offset   ::= 'offset='o:u64 => o | _ => 0
+// align_n  ::= 'align='a:u32 => a | _ => n
+template<typename Ctx>
+Result<typename Ctx::MemargT> memarg(Ctx& ctx, uint32_t n) {
+  uint64_t offset = 0;
+  uint32_t align = n;
+  if (auto o = ctx.in.takeOffset()) {
+    offset = *o;
+  }
+  if (auto a = ctx.in.takeAlign()) {
+    align = *a;
+  }
+  return ctx.getMemarg(offset, align);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeUnreachable(Ctx& ctx, Index pos) {
+  return ctx.makeUnreachable(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeNop(Ctx& ctx, Index pos) {
+  return ctx.makeNop(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBinary(Ctx& ctx, Index pos, BinaryOp op) {
+  return ctx.makeBinary(pos, op);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeUnary(Ctx& ctx, Index pos, UnaryOp op) {
+  return ctx.makeUnary(pos, op);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeSelect(Ctx& ctx, Index pos) {
+  auto res = results(ctx);
+  CHECK_ERR(res);
+  return ctx.makeSelect(pos, res.getPtr());
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeDrop(Ctx& ctx, Index pos) {
+  return ctx.makeDrop(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeMemorySize(Ctx& ctx, Index pos) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  return ctx.makeMemorySize(pos, mem.getPtr());
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeMemoryGrow(Ctx& ctx, Index pos) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  return ctx.makeMemoryGrow(pos, mem.getPtr());
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeLocalGet(Ctx& ctx, Index pos) {
+  auto local = localidx(ctx);
+  CHECK_ERR(local);
+  return ctx.makeLocalGet(pos, *local);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeLocalTee(Ctx& ctx, Index pos) {
+  auto local = localidx(ctx);
+  CHECK_ERR(local);
+  return ctx.makeLocalTee(pos, *local);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeLocalSet(Ctx& ctx, Index pos) {
+  auto local = localidx(ctx);
+  CHECK_ERR(local);
+  return ctx.makeLocalSet(pos, *local);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeGlobalGet(Ctx& ctx, Index pos) {
+  auto global = globalidx(ctx);
+  CHECK_ERR(global);
+  return ctx.makeGlobalGet(pos, *global);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeGlobalSet(Ctx& ctx, Index pos) {
+  auto global = globalidx(ctx);
+  CHECK_ERR(global);
+  return ctx.makeGlobalSet(pos, *global);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBlock(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeThenOrElse(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeConst(Ctx& ctx, Index pos, Type type) {
+  assert(type.isBasic());
+  switch (type.getBasic()) {
+    case Type::i32:
+      if (auto c = ctx.in.takeI32()) {
+        return ctx.makeI32Const(pos, *c);
+      }
+      return ctx.in.err("expected i32");
+    case Type::i64:
+      if (auto c = ctx.in.takeI64()) {
+        return ctx.makeI64Const(pos, *c);
+      }
+      return ctx.in.err("expected i64");
+    case Type::f32:
+      if (auto c = ctx.in.takeF32()) {
+        return ctx.makeF32Const(pos, *c);
+      }
+      return ctx.in.err("expected f32");
+    case Type::f64:
+      if (auto c = ctx.in.takeF64()) {
+        return ctx.makeF64Const(pos, *c);
+      }
+      return ctx.in.err("expected f64");
+    case Type::v128:
+      return ctx.in.err("unimplemented instruction");
+    case Type::none:
+    case Type::unreachable:
+      break;
+  }
+  WASM_UNREACHABLE("unexpected type");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeLoad(
+  Ctx& ctx, Index pos, Type type, bool signed_, int bytes, bool isAtomic) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, bytes);
+  CHECK_ERR(arg);
+  return ctx.makeLoad(pos, type, signed_, bytes, isAtomic, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStore(Ctx& ctx, Index pos, Type type, int bytes, bool isAtomic) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, bytes);
+  CHECK_ERR(arg);
+  return ctx.makeStore(pos, type, bytes, isAtomic, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeAtomicRMW(Ctx& ctx, Index pos, AtomicRMWOp op, Type type, uint8_t bytes) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, bytes);
+  CHECK_ERR(arg);
+  return ctx.makeAtomicRMW(pos, op, type, bytes, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeAtomicCmpxchg(Ctx& ctx, Index pos, Type type, uint8_t bytes) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, bytes);
+  CHECK_ERR(arg);
+  return ctx.makeAtomicCmpxchg(pos, type, bytes, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeAtomicWait(Ctx& ctx, Index pos, Type type) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, type == Type::i32 ? 4 : 8);
+  CHECK_ERR(arg);
+  return ctx.makeAtomicWait(pos, type, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeAtomicNotify(Ctx& ctx, Index pos) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, 4);
+  CHECK_ERR(arg);
+  return ctx.makeAtomicNotify(pos, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeAtomicFence(Ctx& ctx, Index pos) {
+  return ctx.makeAtomicFence(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDExtract(Ctx& ctx, Index pos, SIMDExtractOp op, size_t) {
+  auto lane = ctx.in.takeU8();
+  if (!lane) {
+    return ctx.in.err("expected lane index");
+  }
+  return ctx.makeSIMDExtract(pos, op, *lane);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDReplace(Ctx& ctx, Index pos, SIMDReplaceOp op, size_t lanes) {
+  auto lane = ctx.in.takeU8();
+  if (!lane) {
+    return ctx.in.err("expected lane index");
+  }
+  return ctx.makeSIMDReplace(pos, op, *lane);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeSIMDShuffle(Ctx& ctx, Index pos) {
+  std::array<uint8_t, 16> lanes;
+  for (int i = 0; i < 16; ++i) {
+    auto lane = ctx.in.takeU8();
+    if (!lane) {
+      return ctx.in.err("expected lane index");
+    }
+    lanes[i] = *lane;
+  }
+  return ctx.makeSIMDShuffle(pos, lanes);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDTernary(Ctx& ctx, Index pos, SIMDTernaryOp op) {
+  return ctx.makeSIMDTernary(pos, op);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDShift(Ctx& ctx, Index pos, SIMDShiftOp op) {
+  return ctx.makeSIMDShift(pos, op);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDLoad(Ctx& ctx, Index pos, SIMDLoadOp op, int bytes) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  auto arg = memarg(ctx, bytes);
+  CHECK_ERR(arg);
+  return ctx.makeSIMDLoad(pos, op, mem.getPtr(), *arg);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeSIMDLoadStoreLane(Ctx& ctx, Index pos, SIMDLoadStoreLaneOp op, int bytes) {
+  auto reset = ctx.in.getPos();
+
+  auto retry = [&]() -> Result<typename Ctx::InstrT> {
+    // We failed to parse. Maybe the lane index was accidentally parsed as the
+    // optional memory index. Try again without parsing a memory index.
+    WithPosition with(ctx, reset);
+    auto arg = memarg(ctx, bytes);
+    CHECK_ERR(arg);
+    auto lane = ctx.in.takeU8();
+    if (!lane) {
+      return ctx.in.err("expected lane index");
+    }
+    return ctx.makeSIMDLoadStoreLane(pos, op, nullptr, *arg, *lane);
+  };
+
+  auto mem = maybeMemidx(ctx);
+  if (mem.getErr()) {
+    return retry();
+  }
+  auto arg = memarg(ctx, bytes);
+  CHECK_ERR(arg);
+  auto lane = ctx.in.takeU8();
+  if (!lane) {
+    return retry();
+  }
+  return ctx.makeSIMDLoadStoreLane(pos, op, mem.getPtr(), *arg, *lane);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeMemoryInit(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeDataDrop(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeMemoryCopy(Ctx& ctx, Index pos) {
+  auto destMem = maybeMemidx(ctx);
+  CHECK_ERR(destMem);
+  std::optional<typename Ctx::MemoryT> srcMem = std::nullopt;
+  if (destMem) {
+    auto mem = memidx(ctx);
+    CHECK_ERR(mem);
+    srcMem = *mem;
+  }
+  return ctx.makeMemoryCopy(pos, destMem.getPtr(), srcMem ? &*srcMem : nullptr);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeMemoryFill(Ctx& ctx, Index pos) {
+  auto mem = maybeMemidx(ctx);
+  CHECK_ERR(mem);
+  return ctx.makeMemoryFill(pos, mem.getPtr());
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makePop(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeIf(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeMaybeBlock(Ctx& ctx, Index pos, size_t i, Type type) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeLoop(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeCall(Ctx& ctx, Index pos, bool isReturn) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeCallIndirect(Ctx& ctx, Index pos, bool isReturn) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBreak(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBreakTable(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeReturn(Ctx& ctx, Index pos) {
+  return ctx.makeReturn(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefNull(Ctx& ctx, Index pos) {
+  auto t = heaptype(ctx);
+  CHECK_ERR(t);
+  return ctx.makeRefNull(pos, *t);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefIs(Ctx& ctx, Index pos, RefIsOp op) {
+  return ctx.makeRefIs(pos, op);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefFunc(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefEq(Ctx& ctx, Index pos) {
+  return ctx.makeRefEq(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTableGet(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTableSet(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTableSize(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTableGrow(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTry(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeTryOrCatchBody(Ctx& ctx, Index pos, Type type, bool isTry) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeThrow(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRethrow(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTupleMake(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeTupleExtract(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeCallRef(Ctx& ctx, Index pos, bool isReturn) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeI31New(Ctx& ctx, Index pos) {
+  return ctx.makeI31New(pos);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeI31Get(Ctx& ctx, Index pos, bool signed_) {
+  return ctx.makeI31Get(pos, signed_);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefTest(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefTestStatic(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefCast(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefCastStatic(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefCastNopStatic(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBrOn(Ctx& ctx, Index pos, BrOnOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeBrOnStatic(Ctx& ctx, Index pos, BrOnOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStructNewStatic(Ctx& ctx, Index pos, bool default_) {
+  auto type = typeidx(ctx);
+  CHECK_ERR(type);
+  if (default_) {
+    return ctx.makeStructNewDefault(pos, *type);
+  }
+  return ctx.makeStructNew(pos, *type);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStructGet(Ctx& ctx, Index pos, bool signed_) {
+  auto type = typeidx(ctx);
+  CHECK_ERR(type);
+  auto field = fieldidx(ctx, *type);
+  CHECK_ERR(field);
+  return ctx.makeStructGet(pos, *type, *field, signed_);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStructSet(Ctx& ctx, Index pos) {
+  auto type = typeidx(ctx);
+  CHECK_ERR(type);
+  auto field = fieldidx(ctx, *type);
+  CHECK_ERR(field);
+  return ctx.makeStructSet(pos, *type, *field);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeArrayNewStatic(Ctx& ctx, Index pos, bool default_) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeArrayNewSeg(Ctx& ctx, Index pos, ArrayNewSegOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayInitStatic(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayGet(Ctx& ctx, Index pos, bool signed_) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArraySet(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayLen(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayCopy(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeRefAs(Ctx& ctx, Index pos, RefAsOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringNew(Ctx& ctx, Index pos, StringNewOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringConst(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringMeasure(Ctx& ctx, Index pos, StringMeasureOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringEncode(Ctx& ctx, Index pos, StringEncodeOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringConcat(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringEq(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringAs(Ctx& ctx, Index pos, StringAsOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringWTF8Advance(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringWTF16Get(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringIterNext(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringIterMove(Ctx& ctx, Index pos, StringIterMoveOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeStringSliceWTF(Ctx& ctx, Index pos, StringSliceWTFOp op) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeStringSliceIter(Ctx& ctx, Index pos) {
+  return ctx.in.err("unimplemented instruction");
+}
+
+// =======
+// Modules
+// =======
+
+// typeidx ::= x:u32 => x
+//           | v:id  => x (if types[x] = v)
+template<typename Ctx> MaybeResult<Index> maybeTypeidx(Ctx& ctx) {
+  if (auto x = ctx.in.takeU32()) {
+    return *x;
+  }
+  if (auto id = ctx.in.takeID()) {
+    // TODO: Fix position to point to start of id, not next element.
+    auto idx = ctx.getTypeIndex(*id);
+    CHECK_ERR(idx);
+    return *idx;
+  }
+  return {};
+}
+
+template<typename Ctx> Result<typename Ctx::HeapTypeT> typeidx(Ctx& ctx) {
+  if (auto idx = maybeTypeidx(ctx)) {
+    CHECK_ERR(idx);
+    return ctx.getHeapTypeFromIdx(*idx);
+  }
+  return ctx.in.err("expected type index or identifier");
+}
+
+// fieldidx_t ::= x:u32 => x
+//              | v:id  => x (if t.fields[x] = v)
+template<typename Ctx>
+Result<typename Ctx::FieldIdxT> fieldidx(Ctx& ctx,
+                                         typename Ctx::HeapTypeT type) {
+  if (auto x = ctx.in.takeU32()) {
+    return ctx.getFieldFromIdx(type, *x);
+  }
+  if (auto id = ctx.in.takeID()) {
+    return ctx.getFieldFromName(type, *id);
+  }
+  return ctx.in.err("expected field index or identifier");
+}
+
+// memidx ::= x:u32 => x
+//          | v:id  => x (if memories[x] = v)
+template<typename Ctx>
+MaybeResult<typename Ctx::MemoryT> maybeMemidx(Ctx& ctx) {
+  if (auto x = ctx.in.takeU32()) {
+    return ctx.getMemoryFromIdx(*x);
+  }
+  if (auto id = ctx.in.takeID()) {
+    return ctx.getMemoryFromName(*id);
+  }
+  return {};
+}
+
+template<typename Ctx> Result<typename Ctx::MemoryT> memidx(Ctx& ctx) {
+  if (auto idx = maybeMemidx(ctx)) {
+    CHECK_ERR(idx);
+    return *idx;
+  }
+  return ctx.in.err("expected memory index or identifier");
+}
+
+// globalidx ::= x:u32 => x
+//             | v:id  => x (if globals[x] = v)
+template<typename Ctx> Result<typename Ctx::GlobalT> globalidx(Ctx& ctx) {
+  if (auto x = ctx.in.takeU32()) {
+    return ctx.getGlobalFromIdx(*x);
+  }
+  if (auto id = ctx.in.takeID()) {
+    return ctx.getGlobalFromName(*id);
+  }
+  return ctx.in.err("expected global index or identifier");
+}
+
+// localidx ::= x:u32 => x
+//            | v:id  => x (if locals[x] = v)
+template<typename Ctx> Result<typename Ctx::LocalT> localidx(Ctx& ctx) {
+  if (auto x = ctx.in.takeU32()) {
+    return ctx.getLocalFromIdx(*x);
+  }
+  if (auto id = ctx.in.takeID()) {
+    return ctx.getLocalFromName(*id);
+  }
+  return ctx.in.err("expected local index or identifier");
+}
+
+// typeuse ::= '(' 'type' x:typeidx ')'                                => x, []
+//                 (if typedefs[x] = [t1*] -> [t2*]
+//           | '(' 'type' x:typeidx ')' ((t1,IDs):param)* (t2:result)* => x, IDs
+//                 (if typedefs[x] = [t1*] -> [t2*])
+//           | ((t1,IDs):param)* (t2:result)*                          => x, IDs
+//                 (if x is minimum s.t. typedefs[x] = [t1*] -> [t2*])
+template<typename Ctx> Result<typename Ctx::TypeUseT> typeuse(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+  std::optional<typename Ctx::HeapTypeT> type;
+  if (ctx.in.takeSExprStart("type"sv)) {
+    auto x = typeidx(ctx);
+    CHECK_ERR(x);
+
+    if (!ctx.in.takeRParen()) {
+      return ctx.in.err("expected end of type use");
+    }
+
+    type = *x;
+  }
+
+  auto namedParams = params(ctx);
+  CHECK_ERR(namedParams);
+
+  auto resultTypes = results(ctx);
+  CHECK_ERR(resultTypes);
+
+  return ctx.makeTypeUse(pos, type, namedParams.getPtr(), resultTypes.getPtr());
+}
+
+// ('(' 'import' mod:name nm:name ')')?
+MaybeResult<ImportNames> inlineImport(ParseInput& in) {
+  if (!in.takeSExprStart("import"sv)) {
+    return {};
+  }
+  auto mod = in.takeName();
+  if (!mod) {
+    return in.err("expected import module");
+  }
+  auto nm = in.takeName();
+  if (!nm) {
+    return in.err("expected import name");
+  }
+  if (!in.takeRParen()) {
+    return in.err("expected end of import");
+  }
+  // TODO: Return Ok when parsing Decls.
+  return {{*mod, *nm}};
+}
+
+// ('(' 'export' name ')')*
+Result<std::vector<Name>> inlineExports(ParseInput& in) {
+  std::vector<Name> exports;
+  while (in.takeSExprStart("export"sv)) {
+    auto name = in.takeName();
+    if (!name) {
+      return in.err("expected export name");
+    }
+    if (!in.takeRParen()) {
+      return in.err("expected end of import");
+    }
+    exports.push_back(*name);
+  }
+  return exports;
+}
+
+// strtype ::= ft:functype   => ft
+//           | st:structtype => st
+//           | at:arraytype  => at
+template<typename Ctx> Result<> strtype(Ctx& ctx) {
+  if (auto type = functype(ctx)) {
+    CHECK_ERR(type);
+    ctx.addFuncType(*type);
+    return Ok{};
+  }
+  if (auto type = structtype(ctx)) {
+    CHECK_ERR(type);
+    ctx.addStructType(*type);
+    return Ok{};
+  }
+  if (auto type = arraytype(ctx)) {
+    CHECK_ERR(type);
+    ctx.addArrayType(*type);
+    return Ok{};
+  }
+  return ctx.in.err("expected type description");
+}
+
+// subtype ::= '(' 'type' id? '(' 'sub' typeidx? strtype ')' ')'
+//           | '(' 'type' id? strtype ')'
+template<typename Ctx> MaybeResult<> subtype(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+
+  if (!ctx.in.takeSExprStart("type"sv)) {
+    return {};
+  }
+
+  Name name;
+  if (auto id = ctx.in.takeID()) {
+    name = *id;
+  }
+
+  if (ctx.in.takeSExprStart("sub"sv)) {
+    if (auto super = maybeTypeidx(ctx)) {
+      CHECK_ERR(super);
+      CHECK_ERR(ctx.addSubtype(*super));
+    }
+
+    CHECK_ERR(strtype(ctx));
+
+    if (!ctx.in.takeRParen()) {
+      return ctx.in.err("expected end of subtype definition");
+    }
+  } else {
+    CHECK_ERR(strtype(ctx));
+  }
+
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of type definition");
+  }
+
+  ctx.finishSubtype(name, pos);
+  return Ok{};
+}
+
+// deftype ::= '(' 'rec' subtype* ')'
+//           | subtype
+template<typename Ctx> MaybeResult<> deftype(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+
+  if (ctx.in.takeSExprStart("rec"sv)) {
+    size_t startIndex = ctx.getRecGroupStartIndex();
+    size_t groupLen = 0;
+    while (auto type = subtype(ctx)) {
+      CHECK_ERR(type);
+      ++groupLen;
+    }
+    if (!ctx.in.takeRParen()) {
+      return ctx.in.err("expected type definition or end of recursion group");
+    }
+    ctx.addRecGroup(startIndex, groupLen);
+  } else if (auto type = subtype(ctx)) {
+    CHECK_ERR(type);
+  } else {
+    return {};
+  }
+
+  ctx.finishDeftype(pos);
+  return Ok{};
+}
+
+// local  ::= '(' 'local id? t:valtype ')' => [t]
+//          | '(' 'local t*:valtype* ')' => [t*]
+// locals ::= local*
+template<typename Ctx> MaybeResult<typename Ctx::LocalsT> locals(Ctx& ctx) {
+  bool hasAny = false;
+  auto res = ctx.makeLocals();
+  while (ctx.in.takeSExprStart("local"sv)) {
+    hasAny = true;
+    if (auto id = ctx.in.takeID()) {
+      // Single named local
+      auto type = valtype(ctx);
+      CHECK_ERR(type);
+      if (!ctx.in.takeRParen()) {
+        return ctx.in.err("expected end of local");
+      }
+      ctx.appendLocal(res, *id, *type);
+    } else {
+      // Repeated unnamed locals
+      while (!ctx.in.takeRParen()) {
+        auto type = valtype(ctx);
+        CHECK_ERR(type);
+        ctx.appendLocal(res, {}, *type);
+      }
+    }
+  }
+  if (hasAny) {
+    return res;
+  }
+  return {};
+}
+
+// func ::= '(' 'func' id? ('(' 'export' name ')')*
+//              x,I:typeuse t*:vec(local) (in:instr)* ')'
+//        | '(' 'func' id? ('(' 'export' name ')')*
+//              '(' 'import' mod:name nm:name ')' typeuse ')'
+template<typename Ctx> MaybeResult<> func(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+  if (!ctx.in.takeSExprStart("func"sv)) {
+    return {};
+  }
+
+  Name name;
+  if (auto id = ctx.in.takeID()) {
+    name = *id;
+  }
+
+  auto exports = inlineExports(ctx.in);
+  CHECK_ERR(exports);
+
+  auto import = inlineImport(ctx.in);
+  CHECK_ERR(import);
+
+  auto type = typeuse(ctx);
+  CHECK_ERR(type);
+
+  std::optional<typename Ctx::LocalsT> localVars;
+  if (!import) {
+    if (auto l = locals(ctx)) {
+      CHECK_ERR(l);
+      localVars = *l;
+    }
+  }
+
+  std::optional<typename Ctx::InstrsT> insts;
+  if (!import) {
+    auto i = instrs(ctx);
+    CHECK_ERR(i);
+    insts = *i;
+  }
+
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of function");
+  }
+
+  CHECK_ERR(
+    ctx.addFunc(name, *exports, import.getPtr(), *type, localVars, insts, pos));
+  return Ok{};
+}
+
+// mem ::= '(' 'memory' id? ('(' 'export' name ')')*
+//             ('(' 'data' b:datastring ')' | memtype) ')'
+//       | '(' 'memory' id? ('(' 'export' name ')')*
+//             '(' 'import' mod:name nm:name ')' memtype ')'
+template<typename Ctx> MaybeResult<> memory(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+  if (!ctx.in.takeSExprStart("memory"sv)) {
+    return {};
+  }
+
+  Name name;
+  if (auto id = ctx.in.takeID()) {
+    name = *id;
+  }
+
+  auto exports = inlineExports(ctx.in);
+  CHECK_ERR(exports);
+
+  auto import = inlineImport(ctx.in);
+  CHECK_ERR(import);
+
+  std::optional<typename Ctx::MemTypeT> mtype;
+
+  if (ctx.in.takeSExprStart("data"sv)) {
+    if (import) {
+      return ctx.in.err("imported memories cannot have inline data");
+    }
+    auto data = datastring(ctx);
+    CHECK_ERR(data);
+    if (!ctx.in.takeRParen()) {
+      return ctx.in.err("expected end of inline data");
+    }
+    mtype = ctx.makeMemType(Type::i32, ctx.getLimitsFromData(*data), false);
+    // TODO: addDataSegment as well.
+  } else {
+    auto type = memtype(ctx);
+    CHECK_ERR(type);
+    mtype = *type;
+  }
+
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of memory declaration");
+  }
+
+  CHECK_ERR(ctx.addMemory(name, *exports, import.getPtr(), *mtype, pos));
+  return Ok{};
+}
+
+// global ::= '(' 'global' id? ('(' 'export' name ')')* gt:globaltype e:expr ')'
+//          | '(' 'global' id? ('(' 'export' name ')')*
+//                '(' 'import' mod:name nm:name ')' gt:globaltype ')'
+template<typename Ctx> MaybeResult<> global(Ctx& ctx) {
+  auto pos = ctx.in.getPos();
+  if (!ctx.in.takeSExprStart("global"sv)) {
+    return {};
+  }
+
+  Name name;
+  if (auto id = ctx.in.takeID()) {
+    name = *id;
+  }
+
+  auto exports = inlineExports(ctx.in);
+  CHECK_ERR(exports);
+
+  auto import = inlineImport(ctx.in);
+  CHECK_ERR(import);
+
+  auto type = globaltype(ctx);
+  CHECK_ERR(type);
+
+  std::optional<typename Ctx::ExprT> exp;
+  if (!import) {
+    auto e = expr(ctx);
+    CHECK_ERR(e);
+    exp = *e;
+  }
+
+  if (!ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of global");
+  }
+
+  CHECK_ERR(ctx.addGlobal(name, *exports, import.getPtr(), *type, exp, pos));
+  return Ok{};
+}
+
+// datastring ::= (b:string)* => concat(b*)
+template<typename Ctx> Result<typename Ctx::DataStringT> datastring(Ctx& ctx) {
+  auto data = ctx.makeDataString();
+  while (auto str = ctx.in.takeString()) {
+    ctx.appendDataString(data, *str);
+  }
+  return data;
+}
+
+// modulefield ::= deftype
+//               | import
+//               | func
+//               | table
+//               | memory
+//               | global
+//               | export
+//               | start
+//               | elem
+//               | data
+MaybeResult<> modulefield(ParseDeclsCtx& ctx) {
+  if (auto t = ctx.in.peek(); !t || t->isRParen()) {
+    return {};
+  }
+  if (auto res = deftype(ctx)) {
+    CHECK_ERR(res);
+    return Ok{};
+  }
+  if (auto res = func(ctx)) {
+    CHECK_ERR(res);
+    return Ok{};
+  }
+  if (auto res = memory(ctx)) {
+    CHECK_ERR(res);
+    return Ok{};
+  }
+  if (auto res = global(ctx)) {
+    CHECK_ERR(res);
+    return Ok{};
+  }
+  return ctx.in.err("unrecognized module field");
+}
+
+// module ::= '(' 'module' id? (m:modulefield)* ')'
+//          | (m:modulefield)* eof
+Result<> module(ParseDeclsCtx& ctx) {
+  bool outer = ctx.in.takeSExprStart("module"sv);
+
+  if (outer) {
+    if (auto id = ctx.in.takeID()) {
+      ctx.wasm.name = *id;
+    }
+  }
+
+  while (auto field = modulefield(ctx)) {
+    CHECK_ERR(field);
+  }
+
+  if (outer && !ctx.in.takeRParen()) {
+    return ctx.in.err("expected end of module");
+  }
+
+  return Ok{};
+}
+
+} // anonymous namespace
+
+Result<> parseModule(Module& wasm, std::string_view input) {
+  // Parse module-level declarations.
+  ParseDeclsCtx decls(input, wasm);
+  CHECK_ERR(module(decls));
+  if (!decls.in.empty()) {
+    return decls.in.err("Unexpected tokens after module");
+  }
+
+  auto typeIndices = createIndexMap(decls.in, decls.subtypeDefs);
+  CHECK_ERR(typeIndices);
+
+  // Parse type definitions.
+  std::vector<HeapType> types;
+  {
+    TypeBuilder builder(decls.subtypeDefs.size());
+    ParseTypeDefsCtx ctx(input, builder, *typeIndices);
+    for (auto& typeDef : decls.typeDefs) {
+      WithPosition with(ctx, typeDef.pos);
+      CHECK_ERR(deftype(ctx));
+    }
+    auto built = builder.build();
+    if (auto* err = built.getError()) {
+      std::stringstream msg;
+      msg << "invalid type: " << err->reason;
+      return ctx.in.err(decls.typeDefs[err->index].pos, msg.str());
+    }
+    types = *built;
+    // Record type names on the module.
+    for (size_t i = 0; i < types.size(); ++i) {
+      auto& names = ctx.names[i];
+      if (names.name.is() || names.fieldNames.size()) {
+        wasm.typeNames.insert({types[i], names});
+      }
+    }
+  }
+
+  // Parse implicit type definitions and map typeuses without explicit types to
+  // the correct types.
+  std::unordered_map<Index, HeapType> implicitTypes;
+  {
+    ParseImplicitTypeDefsCtx ctx(input, types, implicitTypes, *typeIndices);
+    for (Index pos : decls.implicitTypeDefs) {
+      WithPosition with(ctx, pos);
+      CHECK_ERR(typeuse(ctx));
+    }
+  }
+
+  {
+    // Parse module-level types.
+    ParseModuleTypesCtx ctx(input, wasm, types, implicitTypes, *typeIndices);
+    CHECK_ERR(parseDefs(ctx, decls.funcDefs, func));
+    CHECK_ERR(parseDefs(ctx, decls.memoryDefs, memory));
+    CHECK_ERR(parseDefs(ctx, decls.globalDefs, global));
+    // TODO: Parse types of other module elements.
+  }
+  {
+    // Parse definitions.
+    // TODO: Parallelize this.
+    ParseDefsCtx ctx(input, wasm, types, implicitTypes, *typeIndices);
+    CHECK_ERR(parseDefs(ctx, decls.globalDefs, global));
+
+    for (Index i = 0; i < decls.funcDefs.size(); ++i) {
+      ctx.index = i;
+      ctx.func = wasm.functions[i].get();
+      ctx.type = ctx.func->getResults();
+      WithPosition with(ctx, decls.funcDefs[i].pos);
+      auto parsed = func(ctx);
+      CHECK_ERR(parsed);
+      assert(parsed);
+    }
+  }
+
+  return Ok{};
+}
+
+} // namespace wasm::WATParser
diff --git a/src/wasm2js.h b/src/wasm2js.h
index b74572c..9821845 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -53,6 +53,8 @@ namespace wasm {
 
 using namespace cashew;
 
+static IString importObject("imports");
+
 // Appends extra to block, flattening out if extra is a block as well
 void flattenAppend(Ref ast, Ref extra) {
   int index;
@@ -94,8 +96,8 @@ bool isTableExported(Module& wasm) {
 }
 
 bool hasActiveSegments(Module& wasm) {
-  for (Index i = 0; i < wasm.memory.segments.size(); i++) {
-    if (!wasm.memory.segments[i].isPassive) {
+  for (Index i = 0; i < wasm.dataSegments.size(); i++) {
+    if (!wasm.dataSegments[i]->isPassive) {
       return true;
     }
   }
@@ -103,7 +105,7 @@ bool hasActiveSegments(Module& wasm) {
 }
 
 bool needsBufferView(Module& wasm) {
-  if (!wasm.memory.exists) {
+  if (wasm.memories.empty()) {
     return false;
   }
 
@@ -228,7 +230,7 @@ public:
     // First up check our cached of mangled names to avoid doing extra work
     // below
     auto& map = wasmNameToMangledName[(int)scope];
-    auto it = map.find(name.c_str());
+    auto it = map.find(name.str.data());
     if (it != map.end()) {
       return it->second;
     }
@@ -247,7 +249,7 @@ public:
     IString ret;
     for (int i = 0;; i++) {
       std::ostringstream out;
-      out << name.c_str();
+      out << name;
       if (i > 0) {
         out << "_" << i;
       }
@@ -274,7 +276,7 @@ public:
       }
       // We found a good name, use it.
       scopeMangledNames.insert(ret);
-      map[name.c_str()] = ret;
+      map[name.str.data()] = ret;
       return ret;
     }
   }
@@ -294,12 +296,15 @@ private:
     wasmNameToMangledName[(int)NameScope::Max];
   // Set of all mangled names in each scope.
   std::unordered_set<IString> mangledNames[(int)NameScope::Max];
+  std::unordered_set<IString> seenModuleImports;
 
   // If a function is callable from outside, we'll need to cast the inputs
   // and our return value. Otherwise, internally, casts are only needed
   // on operations.
   std::unordered_set<Name> functionsCallableFromOutside;
 
+  void ensureModuleVar(Ref ast, const Importable& imp);
+  Ref getImportName(const Importable& imp);
   void addBasics(Ref ast, Module* wasm);
   void addFunctionImport(Ref ast, Function* import);
   void addGlobalImport(Ref ast, Global* import);
@@ -411,19 +416,18 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
   Ref ret = ValueBuilder::makeToplevel();
   Ref asmFunc = ValueBuilder::makeFunction(funcName);
   ret[1]->push_back(asmFunc);
-  ValueBuilder::appendArgumentToFunction(asmFunc, ENV);
+  ValueBuilder::appendArgumentToFunction(asmFunc, importObject);
 
   // add memory import
-  if (wasm->memory.exists) {
-    if (wasm->memory.imported()) {
+  if (!wasm->memories.empty()) {
+    if (wasm->memories[0]->imported()) {
+      ensureModuleVar(asmFunc[3], *wasm->memories[0]);
+
       // find memory and buffer in imports
       Ref theVar = ValueBuilder::makeVar();
       asmFunc[3]->push_back(theVar);
       ValueBuilder::appendToVar(
-        theVar,
-        "memory",
-        ValueBuilder::makeDot(ValueBuilder::makeName(ENV),
-                              ValueBuilder::makeName(wasm->memory.base)));
+        theVar, "memory", getImportName(*wasm->memories[0]));
 
       // Assign `buffer = memory.buffer`
       Ref buf = ValueBuilder::makeVar();
@@ -436,7 +440,7 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
 
       // If memory is growable, override the imported memory's grow method to
       // ensure so that when grow is called from the output it works as expected
-      if (wasm->memory.max > wasm->memory.initial) {
+      if (wasm->memories[0]->max > wasm->memories[0]->initial) {
         asmFunc[3]->push_back(
           ValueBuilder::makeStatement(ValueBuilder::makeBinary(
             ValueBuilder::makeDot(ValueBuilder::makeName("memory"),
@@ -452,19 +456,17 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
         BUFFER,
         ValueBuilder::makeNew(ValueBuilder::makeCall(
           ValueBuilder::makeName("ArrayBuffer"),
-          ValueBuilder::makeInt(Address::address32_t(wasm->memory.initial.addr *
-                                                     Memory::kPageSize)))));
+          ValueBuilder::makeInt(Address::address32_t(
+            wasm->memories[0]->initial.addr * Memory::kPageSize)))));
     }
   }
 
   // add imported tables
   ModuleUtils::iterImportedTables(*wasm, [&](Table* table) {
+    ensureModuleVar(asmFunc[3], *table);
     Ref theVar = ValueBuilder::makeVar();
     asmFunc[3]->push_back(theVar);
-    ValueBuilder::appendToVar(
-      theVar,
-      FUNCTION_TABLE,
-      ValueBuilder::makeDot(ValueBuilder::makeName(ENV), table->base));
+    ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, getImportName(*table));
   });
 
   // create heaps, etc
@@ -526,7 +528,7 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
   if (hasActiveSegments(*wasm)) {
     asmFunc[3]->push_back(
       ValueBuilder::makeCall(ValueBuilder::makeName("initActiveSegments"),
-                             ValueBuilder::makeName(ENV)));
+                             ValueBuilder::makeName(importObject)));
   }
 
   addTable(asmFunc[3], wasm);
@@ -536,7 +538,7 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
 }
 
 void Wasm2JSBuilder::addBasics(Ref ast, Module* wasm) {
-  if (wasm->memory.exists) {
+  if (!wasm->memories.empty()) {
     // heaps, var HEAP8 = new global.Int8Array(buffer); etc
     auto addHeap = [&](IString name, IString view) {
       Ref theVar = ValueBuilder::makeVar();
@@ -572,13 +574,6 @@ void Wasm2JSBuilder::addBasics(Ref ast, Module* wasm) {
   addMath(MATH_CEIL, CEIL);
   addMath(MATH_TRUNC, TRUNC);
   addMath(MATH_SQRT, SQRT);
-  // abort function
-  Ref abortVar = ValueBuilder::makeVar();
-  ast->push_back(abortVar);
-  ValueBuilder::appendToVar(
-    abortVar,
-    "abort",
-    ValueBuilder::makeDot(ValueBuilder::makeName(ENV), ABORT_FUNC));
   // TODO: this shouldn't be needed once we stop generating literal asm.js code
   // NaN and Infinity variables
   Ref nanVar = ValueBuilder::makeVar();
@@ -590,29 +585,60 @@ void Wasm2JSBuilder::addBasics(Ref ast, Module* wasm) {
     infinityVar, "infinity", ValueBuilder::makeName("Infinity"));
 }
 
+static bool needsQuoting(Name name) {
+  auto mangled = asmangle(name.toString());
+  return mangled != name.str;
+}
+
+void Wasm2JSBuilder::ensureModuleVar(Ref ast, const Importable& imp) {
+  if (seenModuleImports.count(imp.module) > 0) {
+    return;
+  }
+  Ref theVar = ValueBuilder::makeVar();
+  ast->push_back(theVar);
+  Ref rhs;
+  if (needsQuoting(imp.module)) {
+    rhs = ValueBuilder::makeSub(ValueBuilder::makeName(importObject),
+                                ValueBuilder::makeString(imp.module));
+  } else {
+    rhs = ValueBuilder::makeDot(ValueBuilder::makeName(importObject),
+                                ValueBuilder::makeName(imp.module));
+  }
+
+  ValueBuilder::appendToVar(theVar, fromName(imp.module, NameScope::Top), rhs);
+  seenModuleImports.insert(imp.module);
+}
+
+Ref Wasm2JSBuilder::getImportName(const Importable& imp) {
+  if (needsQuoting(imp.base)) {
+    return ValueBuilder::makeSub(
+      ValueBuilder::makeName(fromName(imp.module, NameScope::Top)),
+      ValueBuilder::makeString(imp.base));
+  } else {
+    return ValueBuilder::makeDot(
+      ValueBuilder::makeName(fromName(imp.module, NameScope::Top)),
+      ValueBuilder::makeName(imp.base));
+  }
+}
+
 void Wasm2JSBuilder::addFunctionImport(Ref ast, Function* import) {
   // The scratch memory helpers are emitted in the glue, see code and comments
   // below.
   if (ABI::wasm2js::isHelper(import->base)) {
     return;
   }
+  ensureModuleVar(ast, *import);
   Ref theVar = ValueBuilder::makeVar();
   ast->push_back(theVar);
-  // TODO: handle nested module imports
-  Ref module = ValueBuilder::makeName(ENV);
   ValueBuilder::appendToVar(
-    theVar,
-    fromName(import->name, NameScope::Top),
-    ValueBuilder::makeDot(module, fromName(import->base, NameScope::Top)));
+    theVar, fromName(import->name, NameScope::Top), getImportName(*import));
 }
 
 void Wasm2JSBuilder::addGlobalImport(Ref ast, Global* import) {
+  ensureModuleVar(ast, *import);
   Ref theVar = ValueBuilder::makeVar();
   ast->push_back(theVar);
-  // TODO: handle nested module imports
-  Ref module = ValueBuilder::makeName(ENV);
-  Ref value =
-    ValueBuilder::makeDot(module, fromName(import->base, NameScope::Top));
+  Ref value = getImportName(*import);
   if (import->type == Type::i32) {
     value = makeJsCoercion(value, JS_INT);
   }
@@ -691,7 +717,7 @@ void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) {
               } else if (auto* get = offset->dynCast<GlobalGet>()) {
                 index = ValueBuilder::makeBinary(
                   ValueBuilder::makeName(
-                    stringToIString(asmangle(get->name.str))),
+                    stringToIString(asmangle(get->name.toString()))),
                   PLUS,
                   ValueBuilder::makeNum(i));
               } else {
@@ -732,7 +758,7 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
         Ref growDesc = ValueBuilder::makeObject();
         ValueBuilder::appendToObjectWithQuotes(
           descs, IString("grow"), growDesc);
-        if (wasm->memory.max > wasm->memory.initial) {
+        if (wasm->memories[0]->max > wasm->memories[0]->initial) {
           ValueBuilder::appendToObjectWithQuotes(
             growDesc,
             IString("value"),
@@ -781,7 +807,7 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
         // setter
         {
           std::ostringstream buffer;
-          buffer << '_' << identName.c_str();
+          buffer << '_' << identName;
           auto setterParam = stringToIString(buffer.str());
 
           auto block = ValueBuilder::makeBlock();
@@ -805,7 +831,7 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
         Fatal() << "unsupported export type: " << export_->name << "\n";
     }
   }
-  if (wasm->memory.exists) {
+  if (!wasm->memories.empty()) {
     addMemoryFuncs(ast, wasm);
   }
   ast->push_back(
@@ -1474,7 +1500,8 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
     }
 
     Ref visitStore(Store* curr) {
-      if (module->memory.initial < module->memory.max &&
+      if (!module->memories.empty() &&
+          module->memories[0]->initial < module->memories[0]->max &&
           curr->type != Type::unreachable) {
         // In JS, if memory grows then it is dangerous to write
         //  HEAP[f()] = ..
@@ -2006,20 +2033,22 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
     }
 
     Ref visitMemoryGrow(MemoryGrow* curr) {
-      if (module->memory.exists &&
-          module->memory.max > module->memory.initial) {
+      if (!module->memories.empty() &&
+          module->memories[0]->max > module->memories[0]->initial) {
         return ValueBuilder::makeCall(
           WASM_MEMORY_GROW,
           makeJsCoercion(visit(curr->delta, EXPRESSION_RESULT),
                          wasmToJsType(curr->delta->type)));
       } else {
-        return ValueBuilder::makeCall(ABORT_FUNC);
+        ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TRAP);
+        return ValueBuilder::makeCall(ABI::wasm2js::TRAP);
       }
     }
 
     Ref visitNop(Nop* curr) { return ValueBuilder::makeToplevel(); }
     Ref visitUnreachable(Unreachable* curr) {
-      return ValueBuilder::makeCall(ABORT_FUNC);
+      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TRAP);
+      return ValueBuilder::makeCall(ABI::wasm2js::TRAP);
     }
 
     // Atomics
@@ -2259,14 +2288,6 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
     }
-    Ref visitRttCanon(RttCanon* curr) {
-      unimplemented(curr);
-      WASM_UNREACHABLE("unimp");
-    }
-    Ref visitRttSub(RttSub* curr) {
-      unimplemented(curr);
-      WASM_UNREACHABLE("unimp");
-    }
     Ref visitStructNew(StructNew* curr) {
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
@@ -2283,6 +2304,10 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
     }
+    Ref visitArrayNewSeg(ArrayNewSeg* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
     Ref visitArrayInit(ArrayInit* curr) {
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
@@ -2303,6 +2328,58 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
     }
+    Ref visitStringNew(StringNew* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringConst(StringConst* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringMeasure(StringMeasure* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringEncode(StringEncode* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringConcat(StringConcat* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringEq(StringEq* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringAs(StringAs* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringWTF8Advance(StringWTF8Advance* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringWTF16Get(StringWTF16Get* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringIterNext(StringIterNext* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringIterMove(StringIterMove* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringSliceWTF(StringSliceWTF* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitStringSliceIter(StringSliceIter* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
     Ref visitRefAs(RefAs* curr) {
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
@@ -2338,7 +2415,8 @@ void Wasm2JSBuilder::addMemoryFuncs(Ref ast, Module* wasm) {
                    JsType::JS_INT)));
   ast->push_back(memorySizeFunc);
 
-  if (wasm->memory.max > wasm->memory.initial) {
+  if (!wasm->memories.empty() &&
+      wasm->memories[0]->max > wasm->memories[0]->initial) {
     addMemoryGrowFunc(ast, wasm);
   }
 }
@@ -2438,7 +2516,7 @@ void Wasm2JSBuilder::addMemoryGrowFunc(Ref ast, Module* wasm) {
                              ValueBuilder::makeName(IString("newBuffer"))));
 
   // apply the changes to the memory import
-  if (wasm->memory.imported()) {
+  if (!wasm->memories.empty() && wasm->memories[0]->imported()) {
     ValueBuilder::appendToBlock(
       block,
       ValueBuilder::makeBinary(
@@ -2462,7 +2540,7 @@ void Wasm2JSBuilder::addMemoryGrowFunc(Ref ast, Module* wasm) {
   ast->push_back(memoryGrowFunc);
 }
 
-// Wasm2JSGlue emits the core of the module - the functions etc. that would
+// Wasm2JSBuilder emits the core of the module - the functions etc. that would
 // be the asm.js function in an asm.js world. This class emits the rest of the
 // "glue" around that.
 class Wasm2JSGlue {
@@ -2524,11 +2602,12 @@ void Wasm2JSGlue::emitPre() {
 }
 
 void Wasm2JSGlue::emitPreEmscripten() {
-  out << "function instantiate(asmLibraryArg) {\n";
+  out << "function instantiate(info) {\n";
 }
 
 void Wasm2JSGlue::emitPreES6() {
   std::unordered_map<Name, Name> baseModuleMap;
+  std::unordered_set<Name> seenModules;
 
   auto noteImport = [&](Name module, Name base) {
     // Right now codegen requires a flat namespace going into the module,
@@ -2539,9 +2618,11 @@ void Wasm2JSGlue::emitPreES6() {
               << "two different modules yet";
     }
     baseModuleMap[base] = module;
-
-    out << "import { " << asmangle(base.str) << " } from '" << module.str
-        << "';\n";
+    if (seenModules.count(module) == 0) {
+      out << "import * as " << asmangle(module.toString()) << " from '"
+          << module << "';\n";
+      seenModules.insert(module);
+    }
   };
 
   ImportInfo imports(wasm);
@@ -2571,7 +2652,7 @@ void Wasm2JSGlue::emitPost() {
 }
 
 void Wasm2JSGlue::emitPostEmscripten() {
-  out << "  return asmFunc(asmLibraryArg);\n}\n";
+  out << "  return asmFunc(info);\n}\n";
 }
 
 void Wasm2JSGlue::emitPostES6() {
@@ -2581,15 +2662,16 @@ void Wasm2JSGlue::emitPostES6() {
   //
   // Note that the translation here expects that the lower values of this memory
   // can be used for conversions, so make sure there's at least one page.
-  if (wasm.memory.exists && wasm.memory.imported()) {
+  if (!wasm.memories.empty() && wasm.memories[0]->imported()) {
     out << "var mem" << moduleName.str << " = new ArrayBuffer("
-        << wasm.memory.initial.addr * Memory::kPageSize << ");\n";
+        << wasm.memories[0]->initial.addr * Memory::kPageSize << ");\n";
   }
 
   // Actually invoke the `asmFunc` generated function, passing in all global
   // values followed by all imports
-  out << "var ret" << moduleName.str << " = " << moduleName.str << "(";
-  out << "  { abort: function() { throw new Error('abort'); }";
+  out << "var ret" << moduleName.str << " = " << moduleName.str << "({\n";
+
+  std::unordered_set<Name> seenModules;
 
   ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
     // The special helpers are emitted in the glue, see code and comments
@@ -2597,7 +2679,12 @@ void Wasm2JSGlue::emitPostES6() {
     if (ABI::wasm2js::isHelper(import->base)) {
       return;
     }
-    out << ",\n    " << asmangle(import->base.str);
+    if (seenModules.count(import->module) > 0) {
+      return;
+    }
+    out << "  \"" << import->module
+        << "\": " << asmangle(import->module.toString()) << ",\n";
+    seenModules.insert(import->module);
   });
 
   ModuleUtils::iterImportedMemories(wasm, [&](Memory* import) {
@@ -2606,8 +2693,10 @@ void Wasm2JSGlue::emitPostES6() {
     if (ABI::wasm2js::isHelper(import->base)) {
       return;
     }
-    out << ",\n    " << asmangle(import->base.str) << ": { buffer : mem"
-        << moduleName.str << " }";
+    out << "  \"" << import->module << "\": {\n";
+    out << "    " << asmangle(import->base.toString()) << ": { buffer : mem"
+        << moduleName.str << " }\n";
+    out << "  },\n";
   });
 
   ModuleUtils::iterImportedTables(wasm, [&](Table* import) {
@@ -2616,10 +2705,15 @@ void Wasm2JSGlue::emitPostES6() {
     if (ABI::wasm2js::isHelper(import->base)) {
       return;
     }
-    out << ",\n    " << asmangle(import->base.str);
+    if (seenModules.count(import->module) > 0) {
+      return;
+    }
+    out << "  \"" << import->module
+        << "\": " << asmangle(import->module.toString()) << ",\n";
+    seenModules.insert(import->module);
   });
 
-  out << "\n  });\n";
+  out << "});\n";
 
   if (flags.allowAsserts) {
     return;
@@ -2639,15 +2733,15 @@ void Wasm2JSGlue::emitPostES6() {
         continue;
     }
     std::ostringstream export_name;
-    for (auto* ptr = exp->name.str; *ptr; ptr++) {
-      if (*ptr == '-') {
+    for (char c : exp->name.str) {
+      if (c == '-') {
         export_name << '_';
       } else {
-        export_name << *ptr;
+        export_name << c;
       }
     }
-    out << "export var " << asmangle(exp->name.str) << " = ret"
-        << moduleName.str << "." << asmangle(exp->name.str) << ";\n";
+    out << "export var " << asmangle(exp->name.toString()) << " = ret"
+        << moduleName << "." << asmangle(exp->name.toString()) << ";\n";
   }
 }
 
@@ -2665,13 +2759,13 @@ void Wasm2JSGlue::emitMemory() {
 
   // If there are no memory segments, we don't need to emit any support code for
   // segment creation.
-  if ((!wasm.memory.exists) || wasm.memory.segments.empty()) {
+  if (wasm.dataSegments.empty()) {
     return;
   }
 
   // If we have passive memory segments, we need to store those.
-  for (auto& seg : wasm.memory.segments) {
-    if (seg.isPassive) {
+  for (auto& seg : wasm.dataSegments) {
+    if (seg->isPassive) {
       out << "  var memorySegments = {};\n";
       break;
     }
@@ -2706,38 +2800,39 @@ void Wasm2JSGlue::emitMemory() {
   }
 )";
 
-  for (Index i = 0; i < wasm.memory.segments.size(); i++) {
-    auto& seg = wasm.memory.segments[i];
-    if (seg.isPassive) {
+  for (Index i = 0; i < wasm.dataSegments.size(); i++) {
+    auto& seg = wasm.dataSegments[i];
+    if (seg->isPassive) {
       // Fancy passive segments are decoded into typed arrays on the side, for
       // later copying.
       out << "memorySegments[" << i
           << "] = base64DecodeToExistingUint8Array(new Uint8Array("
-          << seg.data.size() << ")"
-          << ", 0, \"" << base64Encode(seg.data) << "\");\n";
+          << seg->data.size() << ")"
+          << ", 0, \"" << base64Encode(seg->data) << "\");\n";
     }
   }
 
   if (hasActiveSegments(wasm)) {
-    auto globalOffset = [&](const Memory::Segment& segment) {
+    auto globalOffset = [&](const DataSegment& segment) {
       if (auto* c = segment.offset->dynCast<Const>()) {
         return std::to_string(c->value.getInteger());
       }
       if (auto* get = segment.offset->dynCast<GlobalGet>()) {
         auto internalName = get->name;
-        auto importedName = wasm.getGlobal(internalName)->base;
-        return std::string("imports[") + asmangle(importedName.str) + "]";
+        auto importedGlobal = wasm.getGlobal(internalName);
+        return std::string("imports['") + importedGlobal->module.toString() +
+               "']['" + importedGlobal->base.toString() + "']";
       }
       Fatal() << "non-constant offsets aren't supported yet\n";
     };
 
     out << "function initActiveSegments(imports) {\n";
-    for (Index i = 0; i < wasm.memory.segments.size(); i++) {
-      auto& seg = wasm.memory.segments[i];
-      if (!seg.isPassive) {
+    for (Index i = 0; i < wasm.dataSegments.size(); i++) {
+      auto& seg = wasm.dataSegments[i];
+      if (!seg->isPassive) {
         // Plain active segments are decoded directly into the main memory.
         out << "  base64DecodeToExistingUint8Array(bufferView, "
-            << globalOffset(seg) << ", \"" << base64Encode(seg.data)
+            << globalOffset(*seg) << ", \"" << base64Encode(seg->data)
             << "\");\n";
       }
     }
@@ -2749,10 +2844,19 @@ void Wasm2JSGlue::emitSpecialSupport() {
   // The special support functions are emitted as part of the JS glue, if we
   // need them.
   bool need = false;
+  bool needScratch = false;
   ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
     if (ABI::wasm2js::isHelper(import->base)) {
       need = true;
     }
+    if (import->base == ABI::wasm2js::SCRATCH_STORE_I32 ||
+        import->base == ABI::wasm2js::SCRATCH_LOAD_I32 ||
+        import->base == ABI::wasm2js::SCRATCH_STORE_F32 ||
+        import->base == ABI::wasm2js::SCRATCH_LOAD_F32 ||
+        import->base == ABI::wasm2js::SCRATCH_STORE_F64 ||
+        import->base == ABI::wasm2js::SCRATCH_LOAD_F64) {
+      needScratch = true;
+    }
   });
   if (!need) {
     return;
@@ -2810,14 +2914,19 @@ void Wasm2JSGlue::emitSpecialSupport() {
   // optimizer runs), so they are guaranteed to be adjacent (and a JS optimizer
   // that runs later will handle that ok since they are calls, which can always
   // have side effects).
-  out << R"(
+  if (needScratch) {
+    out << R"(
   var scratchBuffer = new ArrayBuffer(16);
   var i32ScratchView = new Int32Array(scratchBuffer);
   var f32ScratchView = new Float32Array(scratchBuffer);
   var f64ScratchView = new Float64Array(scratchBuffer);
   )";
+  }
 
   ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
+    if (!ABI::wasm2js::isHelper(import->base)) {
+      return;
+    }
     if (import->base == ABI::wasm2js::SCRATCH_STORE_I32) {
       out << R"(
   function wasm2js_scratch_store_i32(index, value) {
@@ -2950,6 +3059,10 @@ void Wasm2JSGlue::emitSpecialSupport() {
     return stashedBits;
   }
       )";
+    } else if (import->base == ABI::wasm2js::TRAP) {
+      out << "function wasm2js_trap() { throw new Error('abort'); }\n";
+    } else {
+      WASM_UNREACHABLE("bad helper function");
     }
   });
 
diff --git a/src/wat-lexer.h b/src/wat-lexer.h
new file mode 100644
index 0000000..d73c11f
--- /dev/null
+++ b/src/wat-lexer.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstddef>
+#include <cstring>
+#include <iterator>
+#include <optional>
+#include <ostream>
+#include <string_view>
+#include <variant>
+
+#ifndef wasm_wat_lexer_h
+#define wasm_wat_lexer_h
+
+namespace wasm::WATParser {
+
+struct TextPos {
+  size_t line;
+  size_t col;
+
+  bool operator==(const TextPos& other) const;
+  bool operator!=(const TextPos& other) const { return !(*this == other); }
+
+  friend std::ostream& operator<<(std::ostream& os, const TextPos& pos);
+};
+
+// ======
+// Tokens
+// ======
+
+struct LParenTok {
+  bool operator==(const LParenTok&) const { return true; }
+  friend std::ostream& operator<<(std::ostream&, const LParenTok&);
+};
+
+struct RParenTok {
+  bool operator==(const RParenTok&) const { return true; }
+  friend std::ostream& operator<<(std::ostream&, const RParenTok&);
+};
+
+struct IdTok {
+  bool operator==(const IdTok&) const { return true; }
+  friend std::ostream& operator<<(std::ostream&, const IdTok&);
+};
+
+enum Sign { NoSign, Pos, Neg };
+
+struct IntTok {
+  uint64_t n;
+  Sign sign;
+
+  bool operator==(const IntTok&) const;
+  friend std::ostream& operator<<(std::ostream&, const IntTok&);
+};
+
+struct FloatTok {
+  // The payload if we lexed a nan with payload. We cannot store the payload
+  // directly in `d` because we do not know at this point whether we are parsing
+  // an f32 or f64 and therefore we do not know what the allowable payloads are.
+  // No payload with NaN means to use the default payload for the expected float
+  // width.
+  std::optional<uint64_t> nanPayload;
+  double d;
+
+  bool operator==(const FloatTok&) const;
+  friend std::ostream& operator<<(std::ostream&, const FloatTok&);
+};
+
+struct StringTok {
+  std::optional<std::string> str;
+
+  bool operator==(const StringTok& other) const { return str == other.str; }
+  friend std::ostream& operator<<(std::ostream&, const StringTok&);
+};
+
+struct KeywordTok {
+  bool operator==(const KeywordTok&) const { return true; }
+  friend std::ostream& operator<<(std::ostream&, const KeywordTok&);
+};
+
+struct Token {
+  using Data = std::variant<LParenTok,
+                            RParenTok,
+                            IdTok,
+                            IntTok,
+                            FloatTok,
+                            StringTok,
+                            KeywordTok>;
+  std::string_view span;
+  Data data;
+
+  // ====================
+  // Token classification
+  // ====================
+
+  bool isLParen() const { return std::get_if<LParenTok>(&data); }
+
+  bool isRParen() const { return std::get_if<RParenTok>(&data); }
+
+  std::optional<std::string_view> getID() const {
+    if (std::get_if<IdTok>(&data)) {
+      // Drop leading '$'.
+      return span.substr(1);
+    }
+    return {};
+  }
+
+  std::optional<std::string_view> getKeyword() const {
+    if (std::get_if<KeywordTok>(&data)) {
+      return span;
+    }
+    return {};
+  }
+  std::optional<uint64_t> getU64() const;
+  std::optional<int64_t> getS64() const;
+  std::optional<uint64_t> getI64() const;
+  std::optional<uint32_t> getU32() const;
+  std::optional<int32_t> getS32() const;
+  std::optional<uint32_t> getI32() const;
+  std::optional<double> getF64() const;
+  std::optional<float> getF32() const;
+  std::optional<std::string_view> getString() const;
+
+  bool operator==(const Token&) const;
+  friend std::ostream& operator<<(std::ostream& os, const Token&);
+};
+
+// =====
+// Lexer
+// =====
+
+// Lexer's purpose is twofold. First, it wraps a buffer to provide a tokenizing
+// iterator over it. Second, it implements that iterator itself. Also provides
+// utilities for locating the text position of tokens within the buffer. Text
+// positions are computed on demand rather than eagerly because they are
+// typically only needed when there is an error to report.
+struct Lexer {
+  using iterator = Lexer;
+  using difference_type = std::ptrdiff_t;
+  using value_type = Token;
+  using pointer = const Token*;
+  using reference = const Token&;
+  using iterator_category = std::forward_iterator_tag;
+
+private:
+  std::string_view buffer;
+  size_t index = 0;
+  std::optional<Token> curr;
+
+public:
+  // The end sentinel.
+  Lexer() = default;
+
+  Lexer(std::string_view buffer) : buffer(buffer) { setIndex(0); }
+
+  size_t getIndex() const { return index; }
+
+  void setIndex(size_t i) {
+    index = i;
+    skipSpace();
+    lexToken();
+  }
+
+  std::string_view next() const { return buffer.substr(index); }
+  Lexer& operator++() {
+    // Preincrement
+    skipSpace();
+    lexToken();
+    return *this;
+  }
+
+  Lexer operator++(int) {
+    // Postincrement
+    Lexer ret = *this;
+    ++(*this);
+    return ret;
+  }
+
+  const Token& operator*() { return *curr; }
+  const Token* operator->() { return &*curr; }
+
+  bool operator==(const Lexer& other) const {
+    // The iterator is equal to the end sentinel when there is no current token.
+    if (!curr && !other.curr) {
+      return true;
+    }
+    // Otherwise they are equivalent when they are at the same position.
+    return index == other.index;
+  }
+
+  bool operator!=(const Lexer& other) const { return !(*this == other); }
+
+  Lexer begin() { return *this; }
+
+  Lexer end() const { return Lexer(); }
+
+  bool empty() const { return *this == end(); }
+
+  TextPos position(const char* c) const;
+  TextPos position(size_t i) const { return position(buffer.data() + i); }
+  TextPos position(std::string_view span) const {
+    return position(span.data());
+  }
+  TextPos position(Token tok) const { return position(tok.span); }
+
+private:
+  void skipSpace();
+  void lexToken();
+};
+
+} // namespace wasm::WATParser
+
+#endif // wasm_wat_lexer_h
diff --git a/src/wat-parser.h b/src/wat-parser.h
new file mode 100644
index 0000000..ebeb566
--- /dev/null
+++ b/src/wat-parser.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_wat_parser_h
+#define wasm_wat_parser_h
+
+#include <string_view>
+
+#include "wasm.h"
+
+namespace wasm::WATParser {
+
+struct Ok {};
+
+struct None {};
+
+struct Err {
+  std::string msg;
+};
+
+template<typename T = Ok> struct Result {
+  std::variant<T, Err> val;
+
+  Result(Result<T>& other) = default;
+  Result(Result<T>&& other) = default;
+  Result(const Err& e) : val(std::in_place_type<Err>, e) {}
+  Result(Err&& e) : val(std::in_place_type<Err>, std::move(e)) {}
+  template<typename U = T>
+  Result(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {}
+
+  Err* getErr() { return std::get_if<Err>(&val); }
+  T& operator*() { return *std::get_if<T>(&val); }
+  T* operator->() { return std::get_if<T>(&val); }
+};
+
+template<typename T = Ok> struct MaybeResult {
+  std::variant<T, None, Err> val;
+
+  MaybeResult() : val(None{}) {}
+  MaybeResult(MaybeResult<T>& other) = default;
+  MaybeResult(MaybeResult<T>&& other) = default;
+  MaybeResult(const Err& e) : val(std::in_place_type<Err>, e) {}
+  MaybeResult(Err&& e) : val(std::in_place_type<Err>, std::move(e)) {}
+  template<typename U = T>
+  MaybeResult(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {}
+  template<typename U = T>
+  MaybeResult(Result<U>&& u)
+    : val(u.getErr() ? std::variant<T, None, Err>{*u.getErr()}
+                     : std::variant<T, None, Err>{*u}) {}
+
+  // Whether we have an error or a value. Useful for assignment in loops and if
+  // conditions where errors should not get lost.
+  operator bool() const { return !std::holds_alternative<None>(val); }
+
+  Err* getErr() { return std::get_if<Err>(&val); }
+  T& operator*() { return *std::get_if<T>(&val); }
+  T* operator->() { return std::get_if<T>(&val); }
+
+  T* getPtr() { return std::get_if<T>(&val); }
+};
+
+// Parse a single WAT module.
+Result<> parseModule(Module& wasm, std::string_view in);
+
+} // namespace wasm::WATParser
+
+#endif // wasm_wat_parser.h
diff --git a/test/binaryen.js/emit_asmjs.js.txt b/test/binaryen.js/emit_asmjs.js.txt
index 90babcb..b451145 100644
--- a/test/binaryen.js/emit_asmjs.js.txt
+++ b/test/binaryen.js/emit_asmjs.js.txt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function main($0) {
@@ -23,7 +22,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var main = retasmFunc.main;
 
diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js
index 40d4194..c0bc402 100644
--- a/test/binaryen.js/expressions.js
+++ b/test/binaryen.js/expressions.js
@@ -476,7 +476,7 @@ console.log("# GlobalSet");
 console.log("# MemorySize");
 (function testMemorySize() {
   const module = new binaryen.Module();
-
+  module.setMemory(1, 1, null);
   var type = binaryen.i32;
   const theMemorySize = binaryen.MemorySize(module.memory.size());
   assert(theMemorySize instanceof binaryen.MemorySize);
@@ -492,7 +492,7 @@ console.log("# MemorySize");
   assert(
     theMemorySize.toText()
     ==
-    "(memory.size)\n"
+    "(memory.size $0)\n"
   );
 
   module.dispose();
@@ -501,6 +501,7 @@ console.log("# MemorySize");
 console.log("# MemoryGrow");
 (function testMemoryGrow() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var type = binaryen.i32;
   var delta = module.i32.const(1);
@@ -521,7 +522,7 @@ console.log("# MemoryGrow");
   assert(
     theMemoryGrow.toText()
     ==
-    "(memory.grow\n (i32.const 2)\n)\n"
+    "(memory.grow $0\n (i32.const 2)\n)\n"
   );
 
   module.dispose();
@@ -530,6 +531,7 @@ console.log("# MemoryGrow");
 console.log("# Load");
 (function testLoad() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var offset = 16;
   var align = 2;
@@ -566,7 +568,7 @@ console.log("# Load");
   assert(
     theLoad.toText()
     ==
-    "(i64.atomic.load offset=32 align=4\n (i32.const 128)\n)\n"
+    "(i64.atomic.load $0 offset=32 align=4\n (i32.const 128)\n)\n"
   );
 
   module.dispose();
@@ -575,6 +577,7 @@ console.log("# Load");
 console.log("# Store");
 (function testStore() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var offset = 16;
   var align = 2;
@@ -615,7 +618,7 @@ console.log("# Store");
   assert(
     theStore.toText()
     ==
-    "(i64.atomic.store offset=32 align=4\n (i32.const 128)\n (i32.const 2)\n)\n"
+    "(i64.atomic.store $0 offset=32 align=4\n (i32.const 128)\n (i32.const 2)\n)\n"
   );
 
   module.dispose();
@@ -822,6 +825,7 @@ console.log("# Return");
 console.log("# AtomicRMW");
 (function testAtomicRMW() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var op = binaryen.Operations.AtomicRMWAdd;
   var offset = 8;
@@ -855,7 +859,7 @@ console.log("# AtomicRMW");
   assert(
     theAtomicRMW.toText()
     ==
-    "(i64.atomic.rmw16.sub_u offset=16\n (i32.const 4)\n (i64.const 5)\n)\n"
+    "(i64.atomic.rmw16.sub_u $0 offset=16\n (i32.const 4)\n (i64.const 5)\n)\n"
   );
 
   module.dispose();
@@ -864,6 +868,7 @@ console.log("# AtomicRMW");
 console.log("# AtomicCmpxchg");
 (function testAtomicCmpxchg() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var offset = 8;
   var ptr = module.i32.const(2);
@@ -897,7 +902,7 @@ console.log("# AtomicCmpxchg");
   assert(
     theAtomicCmpxchg.toText()
     ==
-    "(i64.atomic.rmw16.cmpxchg_u offset=16\n (i32.const 5)\n (i64.const 6)\n (i64.const 7)\n)\n"
+    "(i64.atomic.rmw16.cmpxchg_u $0 offset=16\n (i32.const 5)\n (i64.const 6)\n (i64.const 7)\n)\n"
   );
 
   module.dispose();
@@ -906,6 +911,7 @@ console.log("# AtomicCmpxchg");
 console.log("# AtomicWait");
 (function testAtomicWait() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var ptr = module.i32.const(2);
   var expected = module.i32.const(3);
@@ -935,7 +941,7 @@ console.log("# AtomicWait");
   assert(
     theAtomicWait.toText()
     ==
-    "(memory.atomic.wait64\n (i32.const 5)\n (i32.const 6)\n (i64.const 7)\n)\n"
+    "(memory.atomic.wait64 $0\n (i32.const 5)\n (i32.const 6)\n (i64.const 7)\n)\n"
   );
 
   module.dispose();
@@ -944,6 +950,7 @@ console.log("# AtomicWait");
 console.log("# AtomicNotify");
 (function testAtomicNotify() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var ptr = module.i32.const(1);
   var notifyCount = module.i32.const(2);
@@ -966,7 +973,7 @@ console.log("# AtomicNotify");
   assert(
     theAtomicNotify.toText()
     ==
-    "(memory.atomic.notify\n (i32.const 3)\n (i32.const 4)\n)\n"
+    "(memory.atomic.notify $0\n (i32.const 3)\n (i32.const 4)\n)\n"
   );
 
   module.dispose();
@@ -1172,6 +1179,7 @@ console.log("# SIMDShift");
 console.log("# SIMDLoad");
 (function testSIMDLoad() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var op = binaryen.Operations.Load8x8SVec128;
   var offset = 16;
@@ -1201,7 +1209,7 @@ console.log("# SIMDLoad");
   assert(
     theSIMDLoad.toText()
     ==
-    "(v128.load8_splat offset=32 align=4\n (i32.const 2)\n)\n"
+    "(v128.load8_splat $0 offset=32 align=4\n (i32.const 2)\n)\n"
   );
 
   module.dispose();
@@ -1210,6 +1218,7 @@ console.log("# SIMDLoad");
 console.log("# SIMDLoadStoreLane");
 (function testSIMDLoadStoreLane() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var op = binaryen.Operations.Load8LaneVec128;
   var offset = 16;
@@ -1249,7 +1258,7 @@ console.log("# SIMDLoadStoreLane");
   assert(
     theSIMDLoadStoreLane.toText()
     ==
-    "(v128.load16_lane offset=32 2\n (i32.const 2)\n (v128.const i32x4 0x01010101 0x01010101 0x01010101 0x01010101)\n)\n"
+    "(v128.load16_lane $0 offset=32 2\n (i32.const 2)\n (v128.const i32x4 0x01010101 0x01010101 0x01010101 0x01010101)\n)\n"
   );
 
   theSIMDLoadStoreLane.op = op = binaryen.Operations.Store16LaneVec128;
@@ -1263,7 +1272,7 @@ console.log("# SIMDLoadStoreLane");
   assert(
     theSIMDLoadStoreLane.toText()
     ==
-    "(v128.store16_lane offset=32 2\n (i32.const 2)\n (v128.const i32x4 0x01010101 0x01010101 0x01010101 0x01010101)\n)\n"
+    "(v128.store16_lane $0 offset=32 2\n (i32.const 2)\n (v128.const i32x4 0x01010101 0x01010101 0x01010101 0x01010101)\n)\n"
   );
 
   module.dispose();
@@ -1272,6 +1281,7 @@ console.log("# SIMDLoadStoreLane");
 console.log("# MemoryInit");
 (function testMemoryInit() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var segment = 1;
   var dest = module.i32.const(2);
@@ -1302,7 +1312,7 @@ console.log("# MemoryInit");
   assert(
     theMemoryInit.toText()
     ==
-    "(memory.init 5\n (i32.const 6)\n (i32.const 7)\n (i32.const 8)\n)\n"
+    "(memory.init $0 5\n (i32.const 6)\n (i32.const 7)\n (i32.const 8)\n)\n"
   );
 
   module.dispose();
@@ -1338,6 +1348,7 @@ console.log("# DataDrop");
 console.log("# MemoryCopy");
 (function testMemoryCopy() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var dest = module.i32.const(1);
   var source = module.i32.const(2);
@@ -1364,7 +1375,7 @@ console.log("# MemoryCopy");
   assert(
     theMemoryCopy.toText()
     ==
-    "(memory.copy\n (i32.const 4)\n (i32.const 5)\n (i32.const 6)\n)\n"
+    "(memory.copy $0 $0\n (i32.const 4)\n (i32.const 5)\n (i32.const 6)\n)\n"
   );
 
   module.dispose();
@@ -1373,6 +1384,7 @@ console.log("# MemoryCopy");
 console.log("# MemoryFill");
 (function testMemoryFill() {
   const module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   var dest = module.i32.const(1);
   var value = module.i32.const(2);
@@ -1399,7 +1411,7 @@ console.log("# MemoryFill");
   assert(
     theMemoryFill.toText()
     ==
-    "(memory.fill\n (i32.const 4)\n (i32.const 5)\n (i32.const 6)\n)\n"
+    "(memory.fill $0\n (i32.const 4)\n (i32.const 5)\n (i32.const 6)\n)\n"
   );
 
   module.dispose();
@@ -1802,13 +1814,13 @@ console.log("# I31New");
   assert(theI31New instanceof binaryen.I31New);
   assert(theI31New instanceof binaryen.Expression);
   assert(theI31New.value === value);
-  assert(theI31New.type === binaryen.i31ref);
+  // assert(theI31New.type === binaryen.?); // TODO: (ref i31)
 
   theI31New.value = value = module.local.get(2, binaryen.i32);
   assert(theI31New.value === value);
   theI31New.type = binaryen.f64;
   theI31New.finalize();
-  assert(theI31New.type === binaryen.i31ref);
+  // assert(theI31New.type === binaryen.?); // TODO: (ref i31)
 
   console.log(theI31New.toText());
   assert(
diff --git a/test/binaryen.js/expressions.js.txt b/test/binaryen.js/expressions.js.txt
index b9107f5..fbf6988 100644
--- a/test/binaryen.js/expressions.js.txt
+++ b/test/binaryen.js/expressions.js.txt
@@ -65,20 +65,20 @@
 )
 
 # MemorySize
-(memory.size)
+(memory.size $0)
 
 # MemoryGrow
-(memory.grow
+(memory.grow $0
  (i32.const 2)
 )
 
 # Load
-(i64.atomic.load offset=32 align=4
+(i64.atomic.load $0 offset=32 align=4
  (i32.const 128)
 )
 
 # Store
-(i64.atomic.store offset=32 align=4
+(i64.atomic.store $0 offset=32 align=4
  (i32.const 128)
  (i32.const 2)
 )
@@ -115,27 +115,27 @@
 )
 
 # AtomicRMW
-(i64.atomic.rmw16.sub_u offset=16
+(i64.atomic.rmw16.sub_u $0 offset=16
  (i32.const 4)
  (i64.const 5)
 )
 
 # AtomicCmpxchg
-(i64.atomic.rmw16.cmpxchg_u offset=16
+(i64.atomic.rmw16.cmpxchg_u $0 offset=16
  (i32.const 5)
  (i64.const 6)
  (i64.const 7)
 )
 
 # AtomicWait
-(memory.atomic.wait64
+(memory.atomic.wait64 $0
  (i32.const 5)
  (i32.const 6)
  (i64.const 7)
 )
 
 # AtomicNotify
-(memory.atomic.notify
+(memory.atomic.notify $0
  (i32.const 3)
  (i32.const 4)
 )
@@ -175,23 +175,23 @@
 )
 
 # SIMDLoad
-(v128.load8_splat offset=32 align=4
+(v128.load8_splat $0 offset=32 align=4
  (i32.const 2)
 )
 
 # SIMDLoadStoreLane
-(v128.load16_lane offset=32 2
+(v128.load16_lane $0 offset=32 2
  (i32.const 2)
  (v128.const i32x4 0x01010101 0x01010101 0x01010101 0x01010101)
 )
 
-(v128.store16_lane offset=32 2
+(v128.store16_lane $0 offset=32 2
  (i32.const 2)
  (v128.const i32x4 0x01010101 0x01010101 0x01010101 0x01010101)
 )
 
 # MemoryInit
-(memory.init 5
+(memory.init $0 5
  (i32.const 6)
  (i32.const 7)
  (i32.const 8)
@@ -201,14 +201,14 @@
 (data.drop 2)
 
 # MemoryCopy
-(memory.copy
+(memory.copy $0 $0
  (i32.const 4)
  (i32.const 5)
  (i32.const 6)
 )
 
 # MemoryFill
-(memory.fill
+(memory.fill $0
  (i32.const 4)
  (i32.const 5)
  (i32.const 6)
diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js
index d72ffce..52d8fd1 100644
--- a/test/binaryen.js/kitchen-sink.js
+++ b/test/binaryen.js/kitchen-sink.js
@@ -68,24 +68,6 @@ function test_types() {
   console.log("  // BinaryenTypeVec128: " + binaryen.v128);
   console.log("  //", binaryen.expandType(binaryen.v128).join(","));
 
-  console.log("  // BinaryenTypeFuncref: " + binaryen.funcref);
-  console.log("  //", binaryen.expandType(binaryen.funcref).join(","));
-
-  console.log("  // BinaryenTypeExternref: " + binaryen.externref);
-  console.log("  //", binaryen.expandType(binaryen.externref).join(","));
-
-  console.log("  // BinaryenTypeAnyref: " + binaryen.anyref);
-  console.log("  //", binaryen.expandType(binaryen.anyref).join(","));
-
-  console.log("  // BinaryenTypeEqref: " + binaryen.eqref);
-  console.log("  //", binaryen.expandType(binaryen.eqref).join(","));
-
-  console.log("  // BinaryenTypeI31ref: " + binaryen.i31ref);
-  console.log("  //", binaryen.expandType(binaryen.i31ref).join(","));
-
-  console.log("  // BinaryenTypeDataref: " + binaryen.dataref);
-  console.log("  //", binaryen.expandType(binaryen.dataref).join(","));
-
   console.log("  // BinaryenTypeAuto: " + binaryen.auto);
 
   var i32_pair = binaryen.createType([binaryen.i32, binaryen.i32]);
@@ -114,9 +96,10 @@ function test_features() {
   console.log("Features.Multivalue: " + binaryen.Features.Multivalue);
   console.log("Features.GC: " + binaryen.Features.GC);
   console.log("Features.Memory64: " + binaryen.Features.Memory64);
-  console.log("Features.TypedFunctionReferences: " + binaryen.Features.TypedFunctionReferences);
   console.log("Features.RelaxedSIMD: " + binaryen.Features.RelaxedSIMD);
   console.log("Features.ExtendedConst: " + binaryen.Features.ExtendedConst);
+  console.log("Features.Strings: " + binaryen.Features.Strings);
+  console.log("Features.MultiMemories: " + binaryen.Features.MultiMemories);
   console.log("Features.All: " + binaryen.Features.All);
 }
 
@@ -180,8 +163,6 @@ function test_ids() {
   console.log("RefTestId: " + binaryen.RefTestId);
   console.log("RefCastId: " + binaryen.RefCastId);
   console.log("BrOnId: " + binaryen.BrOnId);
-  console.log("RttCanonId: " + binaryen.RttCanonId);
-  console.log("RttSubId: " + binaryen.RttSubId);
   console.log("StructNewId: " + binaryen.StructNewId);
   console.log("StructGetId: " + binaryen.StructGetId);
   console.log("StructSetId: " + binaryen.StructSetId);
@@ -190,6 +171,21 @@ function test_ids() {
   console.log("ArrayGetId: " + binaryen.ArrayGetId);
   console.log("ArraySetId: " + binaryen.ArraySetId);
   console.log("ArrayLenId: " + binaryen.ArrayLenId);
+  console.log("ArrayCopy: " + binaryen.ArrayCopyId);
+  console.log("RefAs: " + binaryen.RefAsId);
+  console.log("StringNew: " + binaryen.StringNewId);
+  console.log("StringConst: " + binaryen.StringConstId);
+  console.log("StringMeasure: " + binaryen.StringMeasureId);
+  console.log("StringEncode: " + binaryen.StringEncodeId);
+  console.log("StringConcat: " + binaryen.StringConcatId);
+  console.log("StringEq: " + binaryen.StringEqId);
+  console.log("StringAs: " + binaryen.StringAsId);
+  console.log("StringWTF8Advance: " + binaryen.StringWTF8AdvanceId);
+  console.log("StringWTF16Get: " + binaryen.StringWTF16GetId);
+  console.log("StringIterNext: " + binaryen.StringIterNextId);
+  console.log("StringIterMove: " + binaryen.StringIterMoveId);
+  console.log("StringSliceWTF: " + binaryen.StringSliceWTFId);
+  console.log("StringSliceIter: " + binaryen.StringSliceIterId);
 }
 
 function test_core() {
@@ -197,6 +193,19 @@ function test_core() {
   // Module creation
 
   module = new binaryen.Module();
+  // Memory
+  module.setMemory(1, 256, "mem", [
+    {
+      passive: false,
+      offset: module.i32.const(10),
+      data: "hello, world".split('').map(function(x) { return x.charCodeAt(0) })
+    },
+    {
+      passive: true,
+      offset: null,
+      data: "I am passive".split('').map(function(x) { return x.charCodeAt(0) })
+    }
+  ], true);
 
   // Create a tag
   var tag = module.addTag("a-tag", binaryen.i32, binaryen.none);
@@ -650,6 +659,10 @@ function test_core() {
     module.eqref.pop(),
     module.i31ref.pop(),
     module.dataref.pop(),
+    module.stringref.pop(),
+    module.stringview_wtf8.pop(),
+    module.stringview_wtf16.pop(),
+    module.stringview_iter.pop(),
 
     // Memory
     module.memory.size(),
@@ -738,21 +751,6 @@ function test_core() {
   assert(module.getNumTables() === 1);
   assert(module.getNumElementSegments() === 1);
 
-  // Memory. One per module
-
-  module.setMemory(1, 256, "mem", [
-    {
-      passive: false,
-      offset: module.i32.const(10),
-      data: "hello, world".split('').map(function(x) { return x.charCodeAt(0) })
-    },
-    {
-      passive: true,
-      offset: null,
-      data: "I am passive".split('').map(function(x) { return x.charCodeAt(0) })
-    }
-  ], true);
-
   // Start function. One per module
   var starter = module.addFunction("starter", binaryen.none, binaryen.none, [], module.nop());
   module.setStart(starter);
@@ -1091,9 +1089,9 @@ function test_for_each() {
     assert(module.getExportByIndex(i) === exps[i]);
   }
 
-  var expected_offsets = [10, 125];
-  var expected_data = ["hello, world", "segment data 2"];
-  var expected_passive = [false, false];
+  var expected_offsets = [10, 125, null];
+  var expected_data = ["hello, world", "segment data 2", "hello, passive"];
+  var expected_passive = [false, false, true];
 
   var glos = [
     module.addGlobal("a-global", binaryen.i32, false, module.i32.const(expected_offsets[1])),
@@ -1115,6 +1113,11 @@ function test_for_each() {
       passive: expected_passive[1],
       offset: module.global.get("a-global"),
       data: expected_data[1].split('').map(function(x) { return x.charCodeAt(0) })
+    },
+    {
+      passive: expected_passive[2],
+      offset: expected_offsets[2],
+      data: expected_data[2].split('').map(function(x) { return x.charCodeAt(0) })
     }
   ], false);
   for (i = 0; i < module.getNumMemorySegments(); i++) {
@@ -1151,6 +1154,7 @@ function test_for_each() {
 
 function test_expression_info() {
   module = new binaryen.Module();
+  module.setMemory(1, 1, null);
 
   // Issue #2392
   console.log("getExpressionInfo(memory.grow)=" + JSON.stringify(binaryen.getExpressionInfo(module.memory.grow(1))));
diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt
index 70be70e..9200bac 100644
--- a/test/binaryen.js/kitchen-sink.js.txt
+++ b/test/binaryen.js/kitchen-sink.js.txt
@@ -12,18 +12,6 @@
   // 5
   // BinaryenTypeVec128: 6
   // 6
-  // BinaryenTypeFuncref: 7
-  // 7
-  // BinaryenTypeExternref: 8
-  // 8
-  // BinaryenTypeAnyref: 8
-  // 8
-  // BinaryenTypeEqref: 9
-  // 9
-  // BinaryenTypeI31ref: 10
-  // 10
-  // BinaryenTypeDataref: 11
-  // 11
   // BinaryenTypeAuto: -1
   // 2,2
   // 2,2
@@ -41,10 +29,11 @@ Features.ReferenceTypes: 256
 Features.Multivalue: 512
 Features.GC: 1024
 Features.Memory64: 2048
-Features.TypedFunctionReferences: 4096
-Features.RelaxedSIMD: 16384
-Features.ExtendedConst: 32768
-Features.All: 57343
+Features.RelaxedSIMD: 8192
+Features.ExtendedConst: 16384
+Features.Strings: 32768
+Features.MultiMemories: 65536
+Features.All: 126975
 InvalidId: 0
 BlockId: 1
 IfId: 2
@@ -104,16 +93,29 @@ CallRefId: 56
 RefTestId: 57
 RefCastId: 58
 BrOnId: 59
-RttCanonId: 60
-RttSubId: 61
-StructNewId: 62
-StructGetId: 63
-StructSetId: 64
-ArrayNewId: 65
-ArrayInitId: 66
-ArrayGetId: 67
-ArraySetId: 68
-ArrayLenId: 69
+StructNewId: 60
+StructGetId: 61
+StructSetId: 62
+ArrayNewId: 63
+ArrayInitId: 65
+ArrayGetId: 66
+ArraySetId: 67
+ArrayLenId: 68
+ArrayCopy: 69
+RefAs: 70
+StringNew: 71
+StringConst: 72
+StringMeasure: 73
+StringEncode: 74
+StringConcat: 75
+StringEq: 76
+StringAs: 77
+StringWTF8Advance: 78
+StringWTF16Get: 79
+StringIterNext: 80
+StringIterMove: 81
+StringSliceWTF: 82
+StringSliceIter: 83
 getExpressionInfo={"id":15,"type":4,"op":6}
 (f32.neg
  (f32.const -33.61199951171875)
@@ -143,10 +145,10 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
  (table $t0 1 funcref)
  (elem $e0 (i32.const 0) "$kitchen()sinker")
  (tag $a-tag (param i32))
+ (export "mem" (memory $0))
  (export "kitchen_sinker" (func "$kitchen()sinker"))
  (export "a-global-exp" (global $a-global))
  (export "a-tag-exp" (tag $a-tag))
- (export "mem" (memory $0))
  (start $starter)
  (func "$kitchen()sinker" (param $0 i32) (param $1 i64) (param $2 f32) (param $3 f64) (result i32)
   (local $4 i32)
@@ -2070,12 +2072,12 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
       )
       (drop
        (ref.is_null
-        (ref.null any)
+        (ref.null noextern)
        )
       )
       (drop
        (ref.is_null
-        (ref.null func)
+        (ref.null nofunc)
        )
       )
       (drop
@@ -2085,15 +2087,15 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
       )
       (drop
        (select (result funcref)
-        (ref.null func)
+        (ref.null nofunc)
         (ref.func "$kitchen()sinker")
         (i32.const 1)
        )
       )
       (drop
        (ref.eq
-        (ref.null eq)
-        (ref.null eq)
+        (ref.null none)
+        (ref.null none)
        )
       )
       (try
@@ -2165,7 +2167,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
        (pop funcref)
       )
       (drop
-       (pop anyref)
+       (pop externref)
       )
       (drop
        (pop anyref)
@@ -2179,6 +2181,18 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
       (drop
        (pop dataref)
       )
+      (drop
+       (pop stringref)
+      )
+      (drop
+       (pop stringview_wtf8)
+      )
+      (drop
+       (pop stringview_wtf16)
+      )
+      (drop
+       (pop stringview_iter)
+      )
       (drop
        (memory.size)
       )
@@ -2235,10 +2249,10 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
  (table $t0 1 funcref)
  (elem $e0 (i32.const 0) "$kitchen()sinker")
  (tag $a-tag (param i32))
+ (export "mem" (memory $0))
  (export "kitchen_sinker" (func "$kitchen()sinker"))
  (export "a-global-exp" (global $a-global))
  (export "a-tag-exp" (tag $a-tag))
- (export "mem" (memory $0))
  (start $starter)
  (func "$kitchen()sinker" (param $0 i32) (param $1 i64) (param $2 f32) (param $3 f64) (result i32)
   (local $4 i32)
@@ -4162,12 +4176,12 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
       )
       (drop
        (ref.is_null
-        (ref.null any)
+        (ref.null noextern)
        )
       )
       (drop
        (ref.is_null
-        (ref.null func)
+        (ref.null nofunc)
        )
       )
       (drop
@@ -4177,15 +4191,15 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
       )
       (drop
        (select (result funcref)
-        (ref.null func)
+        (ref.null nofunc)
         (ref.func "$kitchen()sinker")
         (i32.const 1)
        )
       )
       (drop
        (ref.eq
-        (ref.null eq)
-        (ref.null eq)
+        (ref.null none)
+        (ref.null none)
        )
       )
       (try
@@ -4257,7 +4271,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
        (pop funcref)
       )
       (drop
-       (pop anyref)
+       (pop externref)
       )
       (drop
        (pop anyref)
@@ -4271,6 +4285,18 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
       (drop
        (pop dataref)
       )
+      (drop
+       (pop stringref)
+      )
+      (drop
+       (pop stringview_wtf8)
+      )
+      (drop
+       (pop stringview_wtf16)
+      )
+      (drop
+       (pop stringview_iter)
+      )
       (drop
        (memory.size)
       )
@@ -4786,7 +4812,7 @@ module loaded from binary form:
  (type $i32_i32_=>_none (func (param i32 i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (global $a-global i32 (i32.const 3))
- (tag $tag$0 (param i32 i32))
+ (tag $a-tag (param i32 i32))
  (func $adder (param $0 i32) (param $1 i32) (result i32)
   (i32.add
    (local.get $0)
@@ -4860,6 +4886,7 @@ sizeof Literal: 24
  (memory $0 1 256)
  (data (i32.const 10) "hello, world")
  (data (global.get $a-global) "segment data 2")
+ (data "hello, passive")
  (table $t0 1 funcref)
  (elem $e0 (i32.const 0) $fn0 $fn1 $fn2)
  (export "export0" (func $fn0))
diff --git a/test/binaryen.js/memory-info.js b/test/binaryen.js/memory-info.js
new file mode 100644
index 0000000..16ec867
--- /dev/null
+++ b/test/binaryen.js/memory-info.js
@@ -0,0 +1,35 @@
+var module = new binaryen.Module();
+assert(module.validate());
+console.log(JSON.stringify(module.hasMemory()));
+
+var initial = 1, maximum = 64;
+
+// Not shared
+module = new binaryen.Module();
+module.setMemory(initial, maximum, '');
+assert(module.validate());
+console.log(JSON.stringify(module.hasMemory()));
+console.log(JSON.stringify(module.getMemoryInfo()));
+
+// Shared
+module = new binaryen.Module();
+module.setFeatures(binaryen.Features.MVP | binaryen.Features.Atomics);
+module.setMemory(initial, maximum, '', [], true);
+assert(module.validate());
+console.log(JSON.stringify(module.hasMemory()));
+console.log(JSON.stringify(module.getMemoryInfo()));
+
+// Imported, not shared
+module = new binaryen.Module();
+module.addMemoryImport('my_mem', 'env', 'memory', false);
+assert(module.validate());
+console.log(JSON.stringify(module.hasMemory()));
+console.log(JSON.stringify(module.getMemoryInfo()));
+
+// Imported, shared
+module = new binaryen.Module();
+module.setFeatures(binaryen.Features.MVP | binaryen.Features.Atomics);
+module.addMemoryImport('my_mem', 'env', 'memory', true);
+assert(module.validate());
+console.log(JSON.stringify(module.hasMemory()));
+console.log(JSON.stringify(module.getMemoryInfo()));
diff --git a/test/binaryen.js/memory-info.js.txt b/test/binaryen.js/memory-info.js.txt
new file mode 100644
index 0000000..079b7ab
--- /dev/null
+++ b/test/binaryen.js/memory-info.js.txt
@@ -0,0 +1,9 @@
+false
+true
+{"module":"","base":"","initial":1,"shared":false,"is64":false,"max":64}
+true
+{"module":"","base":"","initial":1,"shared":true,"is64":false,"max":64}
+true
+{"module":"env","base":"memory","initial":0,"shared":false,"is64":false,"max":65536}
+true
+{"module":"env","base":"memory","initial":0,"shared":true,"is64":false,"max":65536}
diff --git a/test/binaryen.js/optimize-levels.js.txt b/test/binaryen.js/optimize-levels.js.txt
index e8d918a..05fe235 100644
--- a/test/binaryen.js/optimize-levels.js.txt
+++ b/test/binaryen.js/optimize-levels.js.txt
@@ -20,12 +20,10 @@
  (memory $0 0)
  (export "test" (func $test))
  (func $test (param $0 i32) (result i32)
-  (block $block (result i32)
-   (if (result i32)
-    (local.get $0)
-    (local.get $0)
-    (i32.const 0)
-   )
+  (if (result i32)
+   (local.get $0)
+   (local.get $0)
+   (i32.const 0)
   )
  )
 )
diff --git a/test/binaryen.js/sideffects.js b/test/binaryen.js/sideffects.js
index e318704..be5414a 100644
--- a/test/binaryen.js/sideffects.js
+++ b/test/binaryen.js/sideffects.js
@@ -17,6 +17,7 @@ console.log("SideEffects.TrapsNeverHappen=" + binaryen.SideEffects.TrapsNeverHap
 console.log("SideEffects.Any=" + binaryen.SideEffects.Any);
 
 var module = new binaryen.Module();
+module.setMemory(1, 1, null);
 assert(
   binaryen.getSideEffects(
     module.i32.const(1),
diff --git a/test/binaryen.js/stackir.js.txt b/test/binaryen.js/stackir.js.txt
index 976dd81..a49c6bb 100644
--- a/test/binaryen.js/stackir.js.txt
+++ b/test/binaryen.js/stackir.js.txt
@@ -22,13 +22,11 @@
  (memory $0 0)
  (export "test" (func $test))
  (func $test (param $0 i32) (result i32)
-  block $block0 (result i32)
+  local.get $0
+  if (result i32)
    local.get $0
-   if (result i32)
-    local.get $0
-   else
-    i32.const 0
-   end
+  else
+   i32.const 0
   end
  )
 )
diff --git a/test/ctor-eval/bad-indirect-call3.wast.out b/test/ctor-eval/bad-indirect-call3.wast.out
index 257bf31..2f3cd97 100644
--- a/test/ctor-eval/bad-indirect-call3.wast.out
+++ b/test/ctor-eval/bad-indirect-call3.wast.out
@@ -1,5 +1,5 @@
 (module
- (type $anyref_=>_none (func (param anyref)))
+ (type $externref_=>_none (func (param externref)))
  (type $none_=>_none (func))
  (type $funcref_=>_none (func (param funcref)))
  (memory $0 256 256)
@@ -7,7 +7,7 @@
  (table $0 1 1 funcref)
  (elem (i32.const 0) $callee)
  (export "sig_mismatch" (func $sig_mismatch))
- (func $callee (param $0 anyref)
+ (func $callee (param $0 externref)
   (i32.store8
    (i32.const 40)
    (i32.const 67)
@@ -15,7 +15,7 @@
  )
  (func $sig_mismatch
   (call_indirect $0 (type $funcref_=>_none)
-   (ref.null func)
+   (ref.null nofunc)
    (i32.const 0)
   )
  )
diff --git a/test/ctor-eval/gc.wast b/test/ctor-eval/gc.wast
index 0449b61..965f6f3 100644
--- a/test/ctor-eval/gc.wast
+++ b/test/ctor-eval/gc.wast
@@ -22,8 +22,8 @@
   (global $global2 (mut (ref null $struct)) (ref.null $struct))
 
   (func "test1"
-    ;; Leave the first local as null, which we should handle properly (we will
-    ;; end up emitting nothing and still using the default null value).
+    ;; The locals will be optimized into a single non-nullable one by the
+    ;; optimizer.
     (local $temp1 (ref null $struct))
     (local $temp2 (ref null $struct))
 
diff --git a/test/ctor-eval/gc.wast.out b/test/ctor-eval/gc.wast.out
index b926b5a..45e5ff5 100644
--- a/test/ctor-eval/gc.wast.out
+++ b/test/ctor-eval/gc.wast.out
@@ -27,12 +27,12 @@
   )
  )
  (func $0_0
-  (local $0 (ref null $struct))
+  (local $0 (ref $struct))
   (local.set $0
    (global.get $ctor-eval$global_0)
   )
   (call $import
-   (ref.null $struct)
+   (ref.null none)
   )
   (call $import
    (local.get $0)
diff --git a/test/dylib.wasm.fromBinary b/test/dylib.wasm.fromBinary
index 8c8a517..51b9c24 100644
--- a/test/dylib.wasm.fromBinary
+++ b/test/dylib.wasm.fromBinary
@@ -4,7 +4,6 @@
  (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 0))
- (data (global.get $gimport$0) "*\00\00\00")
  (import "env" "__memory_base" (global $gimport$0 i32))
  (import "env" "g$waka_mine" (func $fimport$0 (result i32)))
  (import "env" "g$waka_others" (func $fimport$1 (result i32)))
@@ -16,6 +15,7 @@
  (global $global$3 (mut i32) (i32.const 0))
  (global $global$4 i32 (i32.const 0))
  (global $global$5 i32 (i32.const 0))
+ (data (global.get $gimport$0) "*\00\00\00")
  (export "__wasm_apply_relocs" (func $0))
  (export "_Z14waka_func_minei" (func $1))
  (export "__original_main" (func $2))
diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c
index e3199bd..d59c9e0 100644
--- a/test/example/c-api-kitchen-sink.c
+++ b/test/example/c-api-kitchen-sink.c
@@ -160,7 +160,7 @@ BinaryenExpressionRef makeMemoryInit(BinaryenModuleRef module) {
   BinaryenExpressionRef dest = makeInt32(module, 1024);
   BinaryenExpressionRef offset = makeInt32(module, 0);
   BinaryenExpressionRef size = makeInt32(module, 12);
-  return BinaryenMemoryInit(module, 0, dest, offset, size);
+  return BinaryenMemoryInit(module, 0, dest, offset, size, "0");
 };
 
 BinaryenExpressionRef makeDataDrop(BinaryenModuleRef module) {
@@ -171,14 +171,14 @@ BinaryenExpressionRef makeMemoryCopy(BinaryenModuleRef module) {
   BinaryenExpressionRef dest = makeInt32(module, 2048);
   BinaryenExpressionRef source = makeInt32(module, 1024);
   BinaryenExpressionRef size = makeInt32(module, 12);
-  return BinaryenMemoryCopy(module, dest, source, size);
+  return BinaryenMemoryCopy(module, dest, source, size, "0", "0");
 };
 
 BinaryenExpressionRef makeMemoryFill(BinaryenModuleRef module) {
   BinaryenExpressionRef dest = makeInt32(module, 0);
   BinaryenExpressionRef value = makeInt32(module, 42);
   BinaryenExpressionRef size = makeInt32(module, 1024);
-  return BinaryenMemoryFill(module, dest, value, size);
+  return BinaryenMemoryFill(module, dest, value, size, "0");
 };
 
 // tests
@@ -187,84 +187,139 @@ void test_types() {
   BinaryenType valueType = 0xdeadbeef;
 
   BinaryenType none = BinaryenTypeNone();
-  printf("  // BinaryenTypeNone: %d\n", none);
+  printf("BinaryenTypeNone: %zd\n", none);
   assert(BinaryenTypeArity(none) == 0);
   BinaryenTypeExpand(none, &valueType);
   assert(valueType == 0xdeadbeef);
 
   BinaryenType unreachable = BinaryenTypeUnreachable();
-  printf("  // BinaryenTypeUnreachable: %d\n", unreachable);
+  printf("BinaryenTypeUnreachable: %zd\n", unreachable);
   assert(BinaryenTypeArity(unreachable) == 1);
   BinaryenTypeExpand(unreachable, &valueType);
   assert(valueType == unreachable);
 
   BinaryenType i32 = BinaryenTypeInt32();
-  printf("  // BinaryenTypeInt32: %d\n", i32);
+  printf("BinaryenTypeInt32: %zd\n", i32);
   assert(BinaryenTypeArity(i32) == 1);
   BinaryenTypeExpand(i32, &valueType);
   assert(valueType == i32);
 
   BinaryenType i64 = BinaryenTypeInt64();
-  printf("  // BinaryenTypeInt64: %d\n", i64);
+  printf("BinaryenTypeInt64: %zd\n", i64);
   assert(BinaryenTypeArity(i64) == 1);
   BinaryenTypeExpand(i64, &valueType);
   assert(valueType == i64);
 
   BinaryenType f32 = BinaryenTypeFloat32();
-  printf("  // BinaryenTypeFloat32: %d\n", f32);
+  printf("BinaryenTypeFloat32: %zd\n", f32);
   assert(BinaryenTypeArity(f32) == 1);
   BinaryenTypeExpand(f32, &valueType);
   assert(valueType == f32);
 
   BinaryenType f64 = BinaryenTypeFloat64();
-  printf("  // BinaryenTypeFloat64: %d\n", f64);
+  printf("BinaryenTypeFloat64: %zd\n", f64);
   assert(BinaryenTypeArity(f64) == 1);
   BinaryenTypeExpand(f64, &valueType);
   assert(valueType == f64);
 
   BinaryenType v128 = BinaryenTypeVec128();
-  printf("  // BinaryenTypeVec128: %d\n", v128);
+  printf("BinaryenTypeVec128: %zd\n", v128);
   assert(BinaryenTypeArity(v128) == 1);
   BinaryenTypeExpand(v128, &valueType);
   assert(valueType == v128);
 
   BinaryenType funcref = BinaryenTypeFuncref();
-  printf("  // BinaryenTypeFuncref: %d\n", funcref);
+  printf("BinaryenTypeFuncref: (ptr)\n");
+  assert(funcref == BinaryenTypeFuncref());
   assert(BinaryenTypeArity(funcref) == 1);
   BinaryenTypeExpand(funcref, &valueType);
   assert(valueType == funcref);
 
   BinaryenType externref = BinaryenTypeExternref();
-  printf("  // BinaryenTypeExternref: %d\n", externref);
+  printf("BinaryenTypeExternref: (ptr)\n");
+  assert(externref == BinaryenTypeExternref());
   assert(BinaryenTypeArity(externref) == 1);
   BinaryenTypeExpand(externref, &valueType);
   assert(valueType == externref);
 
   BinaryenType anyref = BinaryenTypeAnyref();
-  printf("  // BinaryenTypeAnyref: %d\n", anyref);
+  printf("BinaryenTypeAnyref: (ptr)\n");
+  assert(anyref == BinaryenTypeAnyref());
   assert(BinaryenTypeArity(anyref) == 1);
   BinaryenTypeExpand(anyref, &valueType);
   assert(valueType == anyref);
 
   BinaryenType eqref = BinaryenTypeEqref();
-  printf("  // BinaryenTypeEqref: %d\n", eqref);
+  printf("BinaryenTypeEqref: (ptr)\n");
+  assert(eqref == BinaryenTypeEqref());
   assert(BinaryenTypeArity(eqref) == 1);
   BinaryenTypeExpand(eqref, &valueType);
   assert(valueType == eqref);
 
   BinaryenType i31ref = BinaryenTypeI31ref();
-  printf("  // BinaryenTypeI31ref: %d\n", i31ref);
+  printf("BinaryenTypeI31ref: (ptr)\n");
+  assert(i31ref == BinaryenTypeI31ref());
   assert(BinaryenTypeArity(i31ref) == 1);
   BinaryenTypeExpand(i31ref, &valueType);
   assert(valueType == i31ref);
 
   BinaryenType dataref = BinaryenTypeDataref();
-  printf("  // BinaryenTypeDataref: %d\n", dataref);
+  printf("BinaryenTypeDataref: (ptr)\n");
+  assert(dataref == BinaryenTypeDataref());
   assert(BinaryenTypeArity(dataref) == 1);
   BinaryenTypeExpand(dataref, &valueType);
   assert(valueType == dataref);
 
-  printf("  // BinaryenTypeAuto: %d\n", BinaryenTypeAuto());
+  BinaryenType arrayref = BinaryenTypeArrayref();
+  printf("BinaryenTypeArrayref: (ptr)\n");
+  assert(arrayref == BinaryenTypeArrayref());
+  assert(BinaryenTypeArity(arrayref) == 1);
+  BinaryenTypeExpand(arrayref, &valueType);
+  assert(valueType == arrayref);
+
+  BinaryenType stringref = BinaryenTypeStringref();
+  printf("BinaryenTypeStringref: (ptr)\n");
+  assert(BinaryenTypeArity(stringref) == 1);
+  BinaryenTypeExpand(stringref, &valueType);
+  assert(valueType == stringref);
+
+  BinaryenType stringview_wtf8_ = BinaryenTypeStringviewWTF8();
+  printf("BinaryenTypeStringviewWTF8: (ptr)\n");
+  assert(BinaryenTypeArity(stringview_wtf8_) == 1);
+  BinaryenTypeExpand(stringview_wtf8_, &valueType);
+  assert(valueType == stringview_wtf8_);
+
+  BinaryenType stringview_wtf16_ = BinaryenTypeStringviewWTF16();
+  printf("BinaryenTypeStringviewWTF16: (ptr)\n");
+  assert(BinaryenTypeArity(stringview_wtf16_) == 1);
+  BinaryenTypeExpand(stringview_wtf16_, &valueType);
+  assert(valueType == stringview_wtf16_);
+
+  BinaryenType stringview_iter_ = BinaryenTypeStringviewIter();
+  printf("BinaryenTypeStringviewIter: (ptr)\n");
+  assert(BinaryenTypeArity(stringview_iter_) == 1);
+  BinaryenTypeExpand(stringview_iter_, &valueType);
+  assert(valueType == stringview_iter_);
+
+  BinaryenType nullref = BinaryenTypeNullref();
+  printf("BinaryenTypeNullref: (ptr)\n");
+  assert(BinaryenTypeArity(nullref) == 1);
+  BinaryenTypeExpand(nullref, &valueType);
+  assert(valueType == nullref);
+
+  BinaryenType nullexternref = BinaryenTypeNullExternref();
+  printf("BinaryenTypeNullExternref: (ptr)\n");
+  assert(BinaryenTypeArity(nullexternref) == 1);
+  BinaryenTypeExpand(nullexternref, &valueType);
+  assert(valueType == nullexternref);
+
+  BinaryenType nullfuncref = BinaryenTypeNullFuncref();
+  printf("BinaryenTypeNullFuncref: (ptr)\n");
+  assert(BinaryenTypeArity(nullfuncref) == 1);
+  BinaryenTypeExpand(nullfuncref, &valueType);
+  assert(valueType == nullfuncref);
+
+  printf("BinaryenTypeAuto: %zd\n", BinaryenTypeAuto());
 
   BinaryenType pair[] = {i32, i32};
 
@@ -280,6 +335,46 @@ void test_types() {
   pair[0] = pair[1] = f32;
   BinaryenType float_pair = BinaryenTypeCreate(pair, 2);
   assert(float_pair != i32_pair);
+
+  BinaryenPackedType notPacked = BinaryenPackedTypeNotPacked();
+  printf("BinaryenPackedTypeNotPacked: %d\n", notPacked);
+  BinaryenPackedType i8 = BinaryenPackedTypeInt8();
+  printf("BinaryenPackedTypeInt8: %d\n", i8);
+  BinaryenPackedType i16 = BinaryenPackedTypeInt16();
+  printf("BinaryenPackedTypeInt16: %d\n", i16);
+
+  printf("BinaryenHeapTypeExt: %zd\n", BinaryenHeapTypeExt());
+  printf("BinaryenHeapTypeFunc: %zd\n", BinaryenHeapTypeFunc());
+  printf("BinaryenHeapTypeAny: %zd\n", BinaryenHeapTypeAny());
+  printf("BinaryenHeapTypeEq: %zd\n", BinaryenHeapTypeEq());
+  printf("BinaryenHeapTypeI31: %zd\n", BinaryenHeapTypeI31());
+  printf("BinaryenHeapTypeData: %zd\n", BinaryenHeapTypeData());
+  printf("BinaryenHeapTypeArray: %zd\n", BinaryenHeapTypeArray());
+  printf("BinaryenHeapTypeString: %zd\n", BinaryenHeapTypeString());
+  printf("BinaryenHeapTypeStringviewWTF8: %zd\n",
+         BinaryenHeapTypeStringviewWTF8());
+  printf("BinaryenHeapTypeStringviewWTF16: %zd\n",
+         BinaryenHeapTypeStringviewWTF16());
+  printf("BinaryenHeapTypeStringviewIter: %zd\n",
+         BinaryenHeapTypeStringviewIter());
+  printf("BinaryenHeapTypeNone: %zd\n", BinaryenHeapTypeNone());
+  printf("BinaryenHeapTypeNoext: %zd\n", BinaryenHeapTypeNoext());
+  printf("BinaryenHeapTypeNofunc: %zd\n", BinaryenHeapTypeNofunc());
+
+  assert(!BinaryenHeapTypeIsBottom(BinaryenHeapTypeExt()));
+  assert(BinaryenHeapTypeIsBottom(BinaryenHeapTypeNoext()));
+  assert(BinaryenHeapTypeGetBottom(BinaryenHeapTypeExt()) ==
+         BinaryenHeapTypeNoext());
+
+  BinaryenHeapType eq = BinaryenTypeGetHeapType(eqref);
+  assert(eq == BinaryenHeapTypeEq());
+  BinaryenType ref_null_eq = BinaryenTypeFromHeapType(eq, true);
+  assert(BinaryenTypeGetHeapType(ref_null_eq) == eq);
+  assert(BinaryenTypeIsNullable(ref_null_eq));
+  BinaryenType ref_eq = BinaryenTypeFromHeapType(eq, false);
+  assert(ref_eq != ref_null_eq);
+  assert(BinaryenTypeGetHeapType(ref_eq) == eq);
+  assert(!BinaryenTypeIsNullable(ref_eq));
 }
 
 void test_features() {
@@ -300,10 +395,9 @@ void test_features() {
   printf("BinaryenFeatureMultivalue: %d\n", BinaryenFeatureMultivalue());
   printf("BinaryenFeatureGC: %d\n", BinaryenFeatureGC());
   printf("BinaryenFeatureMemory64: %d\n", BinaryenFeatureMemory64());
-  printf("BinaryenFeatureTypedFunctionReferences: %d\n",
-         BinaryenFeatureTypedFunctionReferences());
   printf("BinaryenFeatureRelaxedSIMD: %d\n", BinaryenFeatureRelaxedSIMD());
   printf("BinaryenFeatureExtendedConst: %d\n", BinaryenFeatureExtendedConst());
+  printf("BinaryenFeatureStrings: %d\n", BinaryenFeatureStrings());
   printf("BinaryenFeatureAll: %d\n", BinaryenFeatureAll());
 }
 
@@ -371,9 +465,9 @@ void test_core() {
                         temp15 = makeInt32(module, 110),
                         temp16 = makeInt64(module, 111);
   BinaryenExpressionRef externrefExpr =
-    BinaryenRefNull(module, BinaryenTypeExternref());
+    BinaryenRefNull(module, BinaryenTypeNullExternref());
   BinaryenExpressionRef funcrefExpr =
-    BinaryenRefNull(module, BinaryenTypeFuncref());
+    BinaryenRefNull(module, BinaryenTypeNullFuncref());
   funcrefExpr =
     BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref());
   BinaryenExpressionRef i31refExpr =
@@ -411,6 +505,48 @@ void test_core() {
   BinaryenType f32 = BinaryenTypeFloat32();
   BinaryenType f64 = BinaryenTypeFloat64();
   BinaryenType v128 = BinaryenTypeVec128();
+  BinaryenType i8Array;
+  BinaryenType i16Array;
+  BinaryenType i32Struct;
+  {
+    TypeBuilderRef tb = TypeBuilderCreate(3);
+    TypeBuilderSetArrayType(
+      tb, 0, BinaryenTypeInt32(), BinaryenPackedTypeInt8(), true);
+    TypeBuilderSetArrayType(
+      tb, 1, BinaryenTypeInt32(), BinaryenPackedTypeInt16(), true);
+    TypeBuilderSetStructType(
+      tb,
+      2,
+      (BinaryenType[]){BinaryenTypeInt32()},
+      (BinaryenPackedType[]){BinaryenPackedTypeNotPacked()},
+      (bool[]){true},
+      1);
+    BinaryenHeapType builtHeapTypes[3];
+    TypeBuilderBuildAndDispose(tb, (BinaryenHeapType*)&builtHeapTypes, 0, 0);
+    i8Array = BinaryenTypeFromHeapType(builtHeapTypes[0], true);
+    i16Array = BinaryenTypeFromHeapType(builtHeapTypes[1], true);
+    i32Struct = BinaryenTypeFromHeapType(builtHeapTypes[2], true);
+  }
+
+  // Memory. Add it before creating any memory-using instructions.
+
+  const char* segments[] = {"hello, world", "I am passive"};
+  bool segmentPassive[] = {false, true};
+  BinaryenExpressionRef segmentOffsets[] = {
+    BinaryenConst(module, BinaryenLiteralInt32(10)), NULL};
+  BinaryenIndex segmentSizes[] = {12, 12};
+  BinaryenSetMemory(module,
+                    1,
+                    256,
+                    "mem",
+                    segments,
+                    segmentPassive,
+                    segmentOffsets,
+                    segmentSizes,
+                    2,
+                    1,
+                    0,
+                    "0");
 
   BinaryenExpressionRef valueList[] = {
     // Unary
@@ -706,29 +842,29 @@ void test_core() {
     makeSIMDShift(module, BinaryenShrUVecI64x2()),
     // SIMD load
     BinaryenSIMDLoad(
-      module, BinaryenLoad8SplatVec128(), 0, 1, makeInt32(module, 128)),
+      module, BinaryenLoad8SplatVec128(), 0, 1, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad16SplatVec128(), 16, 1, makeInt32(module, 128)),
+      module, BinaryenLoad16SplatVec128(), 16, 1, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad32SplatVec128(), 16, 4, makeInt32(module, 128)),
+      module, BinaryenLoad32SplatVec128(), 16, 4, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad64SplatVec128(), 0, 4, makeInt32(module, 128)),
+      module, BinaryenLoad64SplatVec128(), 0, 4, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad8x8SVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad8x8SVec128(), 0, 8, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad8x8UVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad8x8UVec128(), 0, 8, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad16x4SVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad16x4SVec128(), 0, 8, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad16x4UVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad16x4UVec128(), 0, 8, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad32x2SVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad32x2SVec128(), 0, 8, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad32x2UVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad32x2UVec128(), 0, 8, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad32ZeroVec128(), 0, 4, makeInt32(module, 128)),
+      module, BinaryenLoad32ZeroVec128(), 0, 4, makeInt32(module, 128), "0"),
     BinaryenSIMDLoad(
-      module, BinaryenLoad64ZeroVec128(), 0, 8, makeInt32(module, 128)),
+      module, BinaryenLoad64ZeroVec128(), 0, 8, makeInt32(module, 128), "0"),
     // SIMD load/store lane
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenLoad8LaneVec128(),
@@ -736,57 +872,64 @@ void test_core() {
                               1,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenLoad16LaneVec128(),
                               0,
                               2,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenLoad32LaneVec128(),
                               0,
                               4,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenLoad64LaneVec128(),
                               0,
                               8,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
-
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenStore8LaneVec128(),
                               0,
                               1,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenStore16LaneVec128(),
                               0,
                               2,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenStore32LaneVec128(),
                               0,
                               4,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     BinaryenSIMDLoadStoreLane(module,
                               BinaryenStore64LaneVec128(),
                               0,
                               8,
                               0,
                               makeInt32(module, 128),
-                              makeVec128(module, v128_bytes)),
+                              makeVec128(module, v128_bytes),
+                              "0"),
     // Other SIMD
     makeSIMDShuffle(module),
     makeSIMDTernary(module, BinaryenBitselectVec128()),
@@ -836,14 +979,16 @@ void test_core() {
     BinaryenDrop(
       module,
       BinaryenLocalTee(module, 0, makeInt32(module, 102), BinaryenTypeInt32())),
-    BinaryenLoad(module, 4, 0, 0, 0, BinaryenTypeInt32(), makeInt32(module, 1)),
-    BinaryenLoad(module, 2, 1, 2, 1, BinaryenTypeInt64(), makeInt32(module, 8)),
     BinaryenLoad(
-      module, 4, 0, 0, 0, BinaryenTypeFloat32(), makeInt32(module, 2)),
+      module, 4, 0, 0, 0, BinaryenTypeInt32(), makeInt32(module, 1), "0"),
     BinaryenLoad(
-      module, 8, 0, 2, 8, BinaryenTypeFloat64(), makeInt32(module, 9)),
-    BinaryenStore(module, 4, 0, 0, temp13, temp14, BinaryenTypeInt32()),
-    BinaryenStore(module, 8, 2, 4, temp15, temp16, BinaryenTypeInt64()),
+      module, 2, 1, 2, 1, BinaryenTypeInt64(), makeInt32(module, 8), "0"),
+    BinaryenLoad(
+      module, 4, 0, 0, 0, BinaryenTypeFloat32(), makeInt32(module, 2), "0"),
+    BinaryenLoad(
+      module, 8, 0, 2, 8, BinaryenTypeFloat64(), makeInt32(module, 9), "0"),
+    BinaryenStore(module, 4, 0, 0, temp13, temp14, BinaryenTypeInt32(), "0"),
+    BinaryenStore(module, 8, 2, 4, temp15, temp16, BinaryenTypeInt64(), "0"),
     BinaryenSelect(module, temp10, temp11, temp12, BinaryenTypeAuto()),
     BinaryenReturn(module, makeInt32(module, 1337)),
     // Tail call
@@ -862,34 +1007,40 @@ void test_core() {
     BinaryenSelect(
       module,
       temp10,
-      BinaryenRefNull(module, BinaryenTypeFuncref()),
+      BinaryenRefNull(module, BinaryenTypeNullFuncref()),
       BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref()),
       BinaryenTypeFuncref()),
     // GC
     BinaryenRefEq(module,
-                  BinaryenRefNull(module, BinaryenTypeEqref()),
-                  BinaryenRefNull(module, BinaryenTypeEqref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref()),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefIs(module,
                   BinaryenRefIsFunc(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefIs(module,
                   BinaryenRefIsData(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefIs(module,
                   BinaryenRefIsI31(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefAs(module,
                   BinaryenRefAsNonNull(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefAs(module,
                   BinaryenRefAsFunc(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefAs(module,
                   BinaryenRefAsData(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     BinaryenRefAs(module,
                   BinaryenRefAsI31(),
-                  BinaryenRefNull(module, BinaryenTypeAnyref())),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
+    BinaryenRefAs(module,
+                  BinaryenRefAsExternInternalize(),
+                  BinaryenRefNull(module, BinaryenTypeNullExternref())),
+    BinaryenRefAs(module,
+                  BinaryenRefAsExternExternalize(),
+                  BinaryenRefNull(module, BinaryenTypeNullref())),
     // Exception handling
     BinaryenTry(module, NULL, tryBody, catchTags, 1, catchBodies, 2, NULL),
     // (try $try_outer
@@ -924,12 +1075,13 @@ void test_core() {
       4,
       0,
       temp6,
-      BinaryenAtomicLoad(module, 4, 0, BinaryenTypeInt32(), temp6),
-      BinaryenTypeInt32()),
-    BinaryenDrop(
-      module,
-      BinaryenAtomicWait(module, temp6, temp6, temp16, BinaryenTypeInt32())),
-    BinaryenDrop(module, BinaryenAtomicNotify(module, temp6, temp6)),
+      BinaryenAtomicLoad(module, 4, 0, BinaryenTypeInt32(), temp6, "0"),
+      BinaryenTypeInt32(),
+      "0"),
+    BinaryenDrop(module,
+                 BinaryenAtomicWait(
+                   module, temp6, temp6, temp16, BinaryenTypeInt32(), "0")),
+    BinaryenDrop(module, BinaryenAtomicNotify(module, temp6, temp6, "0")),
     BinaryenAtomicFence(module),
     // Tuples
     BinaryenTupleMake(module, tupleElements4a, 4),
@@ -944,12 +1096,252 @@ void test_core() {
     BinaryenPop(module, BinaryenTypeExternref()),
     BinaryenPop(module, iIfF),
     // Memory
-    BinaryenMemorySize(module),
-    BinaryenMemoryGrow(module, makeInt32(module, 0)),
+    BinaryenMemorySize(module, "0", false),
+    BinaryenMemoryGrow(module, makeInt32(module, 0), "0", false),
     // GC
     BinaryenI31New(module, makeInt32(module, 0)),
     BinaryenI31Get(module, i31refExpr, 1),
     BinaryenI31Get(module, BinaryenI31New(module, makeInt32(module, 2)), 0),
+    BinaryenRefTest(module,
+                    BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                    BinaryenTypeGetHeapType(i8Array)),
+    BinaryenRefCast(module,
+                    BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                    BinaryenTypeGetHeapType(i8Array)),
+    BinaryenStructNew(module, 0, 0, BinaryenTypeGetHeapType(i32Struct)),
+    BinaryenStructNew(module,
+                      (BinaryenExpressionRef[]){makeInt32(module, 0)},
+                      1,
+                      BinaryenTypeGetHeapType(i32Struct)),
+    BinaryenStructGet(module,
+                      0,
+                      BinaryenGlobalGet(module, "i32Struct-global", i32Struct),
+                      BinaryenTypeInt32(),
+                      false),
+    BinaryenStructSet(module,
+                      0,
+                      BinaryenGlobalGet(module, "i32Struct-global", i32Struct),
+                      makeInt32(module, 0)),
+    BinaryenArrayNew(
+      module, BinaryenTypeGetHeapType(i8Array), makeInt32(module, 3), 0),
+    BinaryenArrayNew(module,
+                     BinaryenTypeGetHeapType(i8Array),
+                     makeInt32(module, 3),
+                     makeInt32(module, 42)),
+    BinaryenArrayInit(module,
+                      BinaryenTypeGetHeapType(i8Array),
+                      (BinaryenExpressionRef[]){makeInt32(module, 1),
+                                                makeInt32(module, 2),
+                                                makeInt32(module, 3)},
+                      3),
+    BinaryenArrayGet(module,
+                     BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                     makeInt32(module, 0),
+                     BinaryenTypeInt32(),
+                     true),
+    BinaryenArraySet(module,
+                     BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                     makeInt32(module, 0),
+                     makeInt32(module, 42)),
+    BinaryenArrayLen(module,
+                     BinaryenGlobalGet(module, "i8Array-global", i8Array)),
+    BinaryenArrayCopy(module,
+                      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                      makeInt32(module, 0),
+                      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                      makeInt32(module, 1),
+                      makeInt32(module, 2)),
+    // Strings
+    BinaryenStringNew(module,
+                      BinaryenStringNewUTF8(),
+                      makeInt32(module, 0),
+                      makeInt32(module, 0),
+                      0,
+                      0),
+    BinaryenStringNew(module,
+                      BinaryenStringNewWTF8(),
+                      makeInt32(module, 0),
+                      makeInt32(module, 0),
+                      0,
+                      0),
+    BinaryenStringNew(module,
+                      BinaryenStringNewReplace(),
+                      makeInt32(module, 0),
+                      makeInt32(module, 0),
+                      0,
+                      0),
+    BinaryenStringNew(module,
+                      BinaryenStringNewWTF16(),
+                      makeInt32(module, 0),
+                      makeInt32(module, 0),
+                      0,
+                      0),
+    BinaryenStringNew(module,
+                      BinaryenStringNewUTF8Array(),
+                      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                      0,
+                      makeInt32(module, 0),
+                      makeInt32(module, 0)),
+    BinaryenStringNew(module,
+                      BinaryenStringNewWTF8Array(),
+                      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                      0,
+                      makeInt32(module, 0),
+                      makeInt32(module, 0)),
+    BinaryenStringNew(module,
+                      BinaryenStringNewReplaceArray(),
+                      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+                      0,
+                      makeInt32(module, 0),
+                      makeInt32(module, 0)),
+    BinaryenStringNew(module,
+                      BinaryenStringNewWTF16Array(),
+                      BinaryenGlobalGet(module, "i16Array-global", i8Array),
+                      0,
+                      makeInt32(module, 0),
+                      makeInt32(module, 0)),
+    BinaryenStringConst(module, "hello world"),
+    BinaryenStringMeasure(
+      module,
+      BinaryenStringMeasureUTF8(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringMeasure(
+      module,
+      BinaryenStringMeasureWTF8(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringMeasure(
+      module,
+      BinaryenStringMeasureWTF16(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringMeasure(
+      module,
+      BinaryenStringMeasureIsUSV(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringMeasure(
+      module,
+      BinaryenStringMeasureWTF16View(),
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsWTF16(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()))),
+    BinaryenStringEncode(
+      module,
+      BinaryenStringEncodeUTF8(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      makeInt32(module, 0),
+      0),
+    BinaryenStringEncode(
+      module,
+      BinaryenStringEncodeWTF8(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      makeInt32(module, 0),
+      0),
+    BinaryenStringEncode(
+      module,
+      BinaryenStringEncodeWTF16(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      makeInt32(module, 0),
+      0),
+    BinaryenStringEncode(
+      module,
+      BinaryenStringEncodeUTF8Array(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+      makeInt32(module, 0)),
+    BinaryenStringEncode(
+      module,
+      BinaryenStringEncodeWTF8Array(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      BinaryenGlobalGet(module, "i8Array-global", i8Array),
+      makeInt32(module, 0)),
+    BinaryenStringEncode(
+      module,
+      BinaryenStringEncodeWTF16Array(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      BinaryenGlobalGet(module, "i16Array-global", i16Array),
+      makeInt32(module, 0)),
+    BinaryenStringConcat(
+      module,
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringEq(
+      module,
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringAs(
+      module,
+      BinaryenStringAsWTF8(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringAs(
+      module,
+      BinaryenStringAsWTF16(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringAs(
+      module,
+      BinaryenStringAsIter(),
+      BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+    BinaryenStringWTF8Advance(
+      module,
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsWTF8(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 0),
+      makeInt32(module, 0)),
+    BinaryenStringWTF16Get(
+      module,
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsWTF16(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 0)),
+    BinaryenStringIterNext(
+      module,
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsIter(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref()))),
+    BinaryenStringIterMove(
+      module,
+      BinaryenStringIterMoveAdvance(),
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsIter(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 1)),
+    BinaryenStringIterMove(
+      module,
+      BinaryenStringIterMoveRewind(),
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsIter(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 1)),
+    BinaryenStringSliceWTF(
+      module,
+      BinaryenStringSliceWTF8(),
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsWTF8(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 0),
+      makeInt32(module, 0)),
+    BinaryenStringSliceWTF(
+      module,
+      BinaryenStringSliceWTF16(),
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsWTF16(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 0),
+      makeInt32(module, 0)),
+    BinaryenStringSliceIter(
+      module,
+      BinaryenStringAs(
+        module,
+        BinaryenStringAsIter(),
+        BinaryenGlobalGet(module, "string-global", BinaryenTypeStringref())),
+      makeInt32(module, 0)),
     // Other
     BinaryenNop(module),
     BinaryenUnreachable(module),
@@ -987,6 +1379,31 @@ void test_core() {
                     BinaryenTypeFloat32(),
                     1,
                     makeFloat32(module, 7.5));
+  BinaryenAddGlobal(
+    module,
+    "i8Array-global",
+    i8Array,
+    true,
+    BinaryenArrayNew(
+      module, BinaryenTypeGetHeapType(i8Array), makeInt32(module, 0), 0));
+  BinaryenAddGlobal(
+    module,
+    "i16Array-global",
+    i16Array,
+    true,
+    BinaryenArrayNew(
+      module, BinaryenTypeGetHeapType(i16Array), makeInt32(module, 0), 0));
+  BinaryenAddGlobal(
+    module,
+    "i32Struct-global",
+    i32Struct,
+    true,
+    BinaryenStructNew(module, 0, 0, BinaryenTypeGetHeapType(i32Struct)));
+  BinaryenAddGlobal(module,
+                    "string-global",
+                    BinaryenTypeStringref(),
+                    true,
+                    BinaryenStringConst(module, ""));
 
   // Imports
 
@@ -1034,30 +1451,12 @@ void test_core() {
   BinaryenTableSizeSetTable(tablesize, table);
 
   BinaryenExpressionRef valueExpr =
-    BinaryenRefNull(module, BinaryenTypeFuncref());
+    BinaryenRefNull(module, BinaryenTypeNullFuncref());
   BinaryenExpressionRef sizeExpr = makeInt32(module, 0);
   BinaryenExpressionRef growExpr =
     BinaryenTableGrow(module, "0", valueExpr, sizeExpr);
   BinaryenExpressionPrint(growExpr);
 
-  // Memory. One per module
-
-  const char* segments[] = {"hello, world", "I am passive"};
-  bool segmentPassive[] = {false, true};
-  BinaryenExpressionRef segmentOffsets[] = {
-    BinaryenConst(module, BinaryenLiteralInt32(10)), NULL};
-  BinaryenIndex segmentSizes[] = {12, 12};
-  BinaryenSetMemory(module,
-                    1,
-                    256,
-                    "mem",
-                    segments,
-                    segmentPassive,
-                    segmentOffsets,
-                    segmentSizes,
-                    2,
-                    1);
-
   // Start function. One per module
 
   BinaryenFunctionRef starter = BinaryenAddFunction(module,
@@ -1623,7 +2022,9 @@ void test_for_each() {
                       segmentOffsets,
                       segmentSizes,
                       2,
-                      0);
+                      0,
+                      0,
+                      "0");
     BinaryenAddGlobal(module,
                       "a-global",
                       BinaryenTypeInt32(),
@@ -1686,6 +2087,233 @@ void test_func_opt() {
   BinaryenModuleDispose(module);
 }
 
+void test_typesystem() {
+  BinaryenTypeSystem defaultTypeSystem = BinaryenGetTypeSystem();
+  assert(defaultTypeSystem == BinaryenTypeSystemEquirecursive());
+  printf("BinaryenTypeSystemEquirecursive: %d\n",
+         BinaryenTypeSystemEquirecursive());
+  BinaryenSetTypeSystem(BinaryenTypeSystemNominal());
+  assert(BinaryenGetTypeSystem() == BinaryenTypeSystemNominal());
+  printf("BinaryenTypeSystemNominal: %d\n", BinaryenTypeSystemNominal());
+  BinaryenSetTypeSystem(BinaryenTypeSystemIsorecursive());
+  assert(BinaryenGetTypeSystem() == BinaryenTypeSystemIsorecursive());
+  printf("BinaryenTypeSystemIsorecursive: %d\n",
+         BinaryenTypeSystemIsorecursive());
+  BinaryenSetTypeSystem(defaultTypeSystem);
+}
+
+void test_typebuilder() {
+  BinaryenTypeSystem defaultTypeSystem = BinaryenGetTypeSystem();
+  BinaryenSetTypeSystem(BinaryenTypeSystemIsorecursive());
+
+  printf("TypeBuilderErrorReasonSelfSupertype: %d\n",
+         TypeBuilderErrorReasonSelfSupertype());
+  printf("TypeBuilderErrorReasonInvalidSupertype: %d\n",
+         TypeBuilderErrorReasonInvalidSupertype());
+  printf("TypeBuilderErrorReasonForwardSupertypeReference: %d\n",
+         TypeBuilderErrorReasonForwardSupertypeReference());
+  printf("TypeBuilderErrorReasonForwardChildReference: %d\n",
+         TypeBuilderErrorReasonForwardChildReference());
+
+  TypeBuilderRef builder = TypeBuilderCreate(0);
+  assert(TypeBuilderGetSize(builder) == 0);
+  TypeBuilderGrow(builder, 5);
+  assert(TypeBuilderGetSize(builder) == 5);
+
+  // Create a recursive array of its own type
+  const BinaryenIndex tempArrayIndex = 0;
+  BinaryenHeapType tempArrayHeapType =
+    TypeBuilderGetTempHeapType(builder, tempArrayIndex);
+  BinaryenType tempArrayType =
+    TypeBuilderGetTempRefType(builder, tempArrayHeapType, true);
+  TypeBuilderSetArrayType(builder,
+                          tempArrayIndex,
+                          tempArrayType,
+                          BinaryenPackedTypeNotPacked(),
+                          true);
+
+  // Create a recursive struct with a field of its own type
+  const BinaryenIndex tempStructIndex = 1;
+  BinaryenHeapType tempStructHeapType =
+    TypeBuilderGetTempHeapType(builder, tempStructIndex);
+  BinaryenType tempStructType =
+    TypeBuilderGetTempRefType(builder, tempStructHeapType, true);
+  {
+    BinaryenType fieldTypes[] = {tempStructType};
+    BinaryenPackedType fieldPackedTypes[] = {BinaryenPackedTypeNotPacked()};
+    bool fieldMutables[] = {true};
+    TypeBuilderSetStructType(
+      builder, tempStructIndex, fieldTypes, fieldPackedTypes, fieldMutables, 1);
+  }
+
+  // Create a recursive signature with parameter and result including its own
+  // type
+  const BinaryenIndex tempSignatureIndex = 2;
+  BinaryenHeapType tempSignatureHeapType =
+    TypeBuilderGetTempHeapType(builder, tempSignatureIndex);
+  BinaryenType tempSignatureType =
+    TypeBuilderGetTempRefType(builder, tempSignatureHeapType, true);
+  {
+    BinaryenType paramTypes[] = {tempSignatureType, tempArrayType};
+    TypeBuilderSetSignatureType(
+      builder,
+      tempSignatureIndex,
+      TypeBuilderGetTempTupleType(builder, (BinaryenType*)&paramTypes, 2),
+      tempSignatureType);
+  }
+
+  // Create a basic heap type
+  const BinaryenIndex tempBasicIndex = 3;
+  TypeBuilderSetBasicHeapType(
+    builder, 3, BinaryenTypeGetHeapType(BinaryenTypeEqref()));
+  assert(TypeBuilderIsBasic(builder, tempBasicIndex));
+  assert(TypeBuilderGetBasic(builder, tempBasicIndex) ==
+         BinaryenTypeGetHeapType(BinaryenTypeEqref()));
+  assert(!TypeBuilderIsBasic(builder, tempArrayIndex));
+  assert(!TypeBuilderIsBasic(builder, tempStructIndex));
+  assert(!TypeBuilderIsBasic(builder, tempSignatureIndex));
+
+  // Create a subtype (with an additional immutable packed field)
+  const BinaryenIndex tempSubStructIndex = 4;
+  BinaryenHeapType tempSubStructHeapType =
+    TypeBuilderGetTempHeapType(builder, tempSubStructIndex);
+  BinaryenType tempSubStructType =
+    TypeBuilderGetTempRefType(builder, tempSubStructHeapType, true);
+  {
+    BinaryenType fieldTypes[] = {
+      tempStructType, BinaryenTypeInt32()}; // must repeat existing fields
+    BinaryenPackedType fieldPackedTypes[] = {BinaryenPackedTypeNotPacked(),
+                                             BinaryenPackedTypeInt8()};
+    bool fieldMutables[] = {true, false};
+    TypeBuilderSetStructType(builder,
+                             tempSubStructIndex,
+                             fieldTypes,
+                             fieldPackedTypes,
+                             fieldMutables,
+                             2);
+  }
+  TypeBuilderSetSubType(builder, tempSubStructIndex, tempStructHeapType);
+
+  // TODO: Rtts (post-MVP?)
+
+  // Build the type hierarchy and dispose the builder
+  BinaryenHeapType heapTypes[5];
+  BinaryenIndex errorIndex;
+  TypeBuilderErrorReason errorReason;
+  bool didBuildAndDispose = TypeBuilderBuildAndDispose(
+    builder, (BinaryenHeapType*)&heapTypes, &errorIndex, &errorReason);
+  assert(didBuildAndDispose);
+
+  BinaryenHeapType arrayHeapType = heapTypes[tempArrayIndex];
+  assert(!BinaryenHeapTypeIsBasic(arrayHeapType));
+  assert(!BinaryenHeapTypeIsSignature(arrayHeapType));
+  assert(!BinaryenHeapTypeIsStruct(arrayHeapType));
+  assert(BinaryenHeapTypeIsArray(arrayHeapType));
+  assert(!BinaryenHeapTypeIsBottom(arrayHeapType));
+  assert(BinaryenHeapTypeIsSubType(arrayHeapType, BinaryenHeapTypeArray()));
+  BinaryenType arrayType = BinaryenTypeFromHeapType(arrayHeapType, true);
+  assert(BinaryenArrayTypeGetElementType(arrayHeapType) == arrayType);
+  assert(BinaryenArrayTypeGetElementPackedType(arrayHeapType) ==
+         BinaryenPackedTypeNotPacked());
+  assert(BinaryenArrayTypeIsElementMutable(arrayHeapType));
+
+  BinaryenHeapType structHeapType = heapTypes[tempStructIndex];
+  assert(!BinaryenHeapTypeIsBasic(structHeapType));
+  assert(!BinaryenHeapTypeIsSignature(structHeapType));
+  assert(BinaryenHeapTypeIsStruct(structHeapType));
+  assert(!BinaryenHeapTypeIsArray(structHeapType));
+  assert(!BinaryenHeapTypeIsBottom(structHeapType));
+  assert(BinaryenHeapTypeIsSubType(structHeapType, BinaryenHeapTypeData()));
+  BinaryenType structType = BinaryenTypeFromHeapType(structHeapType, true);
+  assert(BinaryenStructTypeGetNumFields(structHeapType) == 1);
+  assert(BinaryenStructTypeGetFieldType(structHeapType, 0) == structType);
+  assert(BinaryenStructTypeGetFieldPackedType(structHeapType, 0) ==
+         BinaryenPackedTypeNotPacked());
+  assert(BinaryenStructTypeIsFieldMutable(structHeapType, 0));
+
+  BinaryenHeapType signatureHeapType = heapTypes[tempSignatureIndex];
+  assert(!BinaryenHeapTypeIsBasic(signatureHeapType));
+  assert(BinaryenHeapTypeIsSignature(signatureHeapType));
+  assert(!BinaryenHeapTypeIsStruct(signatureHeapType));
+  assert(!BinaryenHeapTypeIsArray(signatureHeapType));
+  assert(!BinaryenHeapTypeIsBottom(signatureHeapType));
+  assert(BinaryenHeapTypeIsSubType(signatureHeapType, BinaryenHeapTypeFunc()));
+  BinaryenType signatureType =
+    BinaryenTypeFromHeapType(signatureHeapType, true);
+  BinaryenType signatureParams =
+    BinaryenSignatureTypeGetParams(signatureHeapType);
+  assert(BinaryenTypeArity(signatureParams) == 2);
+  BinaryenType expandedSignatureParams[2];
+  BinaryenTypeExpand(signatureParams, (BinaryenType*)expandedSignatureParams);
+  assert(expandedSignatureParams[0] == signatureType);
+  assert(expandedSignatureParams[1] == arrayType);
+  BinaryenType signatureResults =
+    BinaryenSignatureTypeGetResults(signatureHeapType);
+  assert(BinaryenTypeArity(signatureResults) == 1);
+  assert(signatureResults == signatureType);
+
+  BinaryenHeapType basicHeapType = heapTypes[tempBasicIndex]; // = eq
+  assert(BinaryenHeapTypeIsBasic(basicHeapType));
+  assert(!BinaryenHeapTypeIsSignature(basicHeapType));
+  assert(!BinaryenHeapTypeIsStruct(basicHeapType));
+  assert(!BinaryenHeapTypeIsArray(basicHeapType));
+  assert(!BinaryenHeapTypeIsBottom(basicHeapType));
+  assert(BinaryenHeapTypeIsSubType(basicHeapType, BinaryenHeapTypeAny()));
+  BinaryenType basicType = BinaryenTypeFromHeapType(basicHeapType, true);
+
+  BinaryenHeapType subStructHeapType = heapTypes[tempSubStructIndex];
+  assert(!BinaryenHeapTypeIsBasic(subStructHeapType));
+  assert(!BinaryenHeapTypeIsSignature(subStructHeapType));
+  assert(BinaryenHeapTypeIsStruct(subStructHeapType));
+  assert(!BinaryenHeapTypeIsArray(subStructHeapType));
+  assert(!BinaryenHeapTypeIsBottom(subStructHeapType));
+  assert(BinaryenHeapTypeIsSubType(subStructHeapType, BinaryenHeapTypeData()));
+  assert(BinaryenHeapTypeIsSubType(subStructHeapType, structHeapType));
+  BinaryenType subStructType =
+    BinaryenTypeFromHeapType(subStructHeapType, true);
+  assert(BinaryenStructTypeGetNumFields(subStructHeapType) == 2);
+  assert(BinaryenStructTypeGetFieldType(subStructHeapType, 0) == structType);
+  assert(BinaryenStructTypeGetFieldType(subStructHeapType, 1) ==
+         BinaryenTypeInt32());
+  assert(BinaryenStructTypeGetFieldPackedType(subStructHeapType, 0) ==
+         BinaryenPackedTypeNotPacked());
+  assert(BinaryenStructTypeGetFieldPackedType(subStructHeapType, 1) ==
+         BinaryenPackedTypeInt8());
+  assert(BinaryenStructTypeIsFieldMutable(subStructHeapType, 0));
+  assert(!BinaryenStructTypeIsFieldMutable(subStructHeapType, 1));
+
+  // Build a simple test module, validate and print it
+  BinaryenModuleRef module = BinaryenModuleCreate();
+  BinaryenModuleSetTypeName(module, arrayHeapType, "SomeArray");
+  BinaryenModuleSetTypeName(module, structHeapType, "SomeStruct");
+  BinaryenModuleSetFieldName(module, structHeapType, 0, "SomeField");
+  BinaryenModuleSetTypeName(module, signatureHeapType, "SomeSignature");
+  BinaryenModuleSetTypeName(module, basicHeapType, "does-nothing");
+  BinaryenModuleSetTypeName(module, subStructHeapType, "SomeSubStruct");
+  BinaryenModuleSetFieldName(module, subStructHeapType, 0, "SomeField");
+  BinaryenModuleSetFieldName(module, subStructHeapType, 1, "SomePackedField");
+  BinaryenModuleSetFeatures(
+    module, BinaryenFeatureReferenceTypes() | BinaryenFeatureGC());
+  {
+    BinaryenType varTypes[] = {
+      arrayType, structType, signatureType, basicType, subStructType};
+    BinaryenAddFunction(module,
+                        "test",
+                        BinaryenTypeNone(),
+                        BinaryenTypeNone(),
+                        varTypes,
+                        5,
+                        BinaryenNop(module));
+  }
+  bool didValidate = BinaryenModuleValidate(module);
+  assert(didValidate);
+  printf("module with recursive GC types:\n");
+  BinaryenModulePrint(module);
+  BinaryenModuleDispose(module);
+
+  BinaryenSetTypeSystem(defaultTypeSystem);
+}
+
 int main() {
   test_types();
   test_features();
@@ -1698,6 +2326,8 @@ int main() {
   test_color_status();
   test_for_each();
   test_func_opt();
+  test_typesystem();
+  test_typebuilder();
 
   return 0;
 }
diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt
index 49a3d74..408c064 100644
--- a/test/example/c-api-kitchen-sink.txt
+++ b/test/example/c-api-kitchen-sink.txt
@@ -1,17 +1,42 @@
-  // BinaryenTypeNone: 0
-  // BinaryenTypeUnreachable: 1
-  // BinaryenTypeInt32: 2
-  // BinaryenTypeInt64: 3
-  // BinaryenTypeFloat32: 4
-  // BinaryenTypeFloat64: 5
-  // BinaryenTypeVec128: 6
-  // BinaryenTypeFuncref: 7
-  // BinaryenTypeExternref: 8
-  // BinaryenTypeAnyref: 8
-  // BinaryenTypeEqref: 9
-  // BinaryenTypeI31ref: 10
-  // BinaryenTypeDataref: 11
-  // BinaryenTypeAuto: -1
+BinaryenTypeNone: 0
+BinaryenTypeUnreachable: 1
+BinaryenTypeInt32: 2
+BinaryenTypeInt64: 3
+BinaryenTypeFloat32: 4
+BinaryenTypeFloat64: 5
+BinaryenTypeVec128: 6
+BinaryenTypeFuncref: (ptr)
+BinaryenTypeExternref: (ptr)
+BinaryenTypeAnyref: (ptr)
+BinaryenTypeEqref: (ptr)
+BinaryenTypeI31ref: (ptr)
+BinaryenTypeDataref: (ptr)
+BinaryenTypeArrayref: (ptr)
+BinaryenTypeStringref: (ptr)
+BinaryenTypeStringviewWTF8: (ptr)
+BinaryenTypeStringviewWTF16: (ptr)
+BinaryenTypeStringviewIter: (ptr)
+BinaryenTypeNullref: (ptr)
+BinaryenTypeNullExternref: (ptr)
+BinaryenTypeNullFuncref: (ptr)
+BinaryenTypeAuto: -1
+BinaryenPackedTypeNotPacked: 0
+BinaryenPackedTypeInt8: 1
+BinaryenPackedTypeInt16: 2
+BinaryenHeapTypeExt: 0
+BinaryenHeapTypeFunc: 1
+BinaryenHeapTypeAny: 2
+BinaryenHeapTypeEq: 3
+BinaryenHeapTypeI31: 4
+BinaryenHeapTypeData: 5
+BinaryenHeapTypeArray: 6
+BinaryenHeapTypeString: 7
+BinaryenHeapTypeStringviewWTF8: 8
+BinaryenHeapTypeStringviewWTF16: 9
+BinaryenHeapTypeStringviewIter: 10
+BinaryenHeapTypeNone: 11
+BinaryenHeapTypeNoext: 12
+BinaryenHeapTypeNofunc: 13
 BinaryenFeatureMVP: 0
 BinaryenFeatureAtomics: 1
 BinaryenFeatureBulkMemory: 16
@@ -25,10 +50,10 @@ BinaryenFeatureReferenceTypes: 256
 BinaryenFeatureMultivalue: 512
 BinaryenFeatureGC: 1024
 BinaryenFeatureMemory64: 2048
-BinaryenFeatureTypedFunctionReferences: 4096
-BinaryenFeatureRelaxedSIMD: 16384
-BinaryenFeatureExtendedConst: 32768
-BinaryenFeatureAll: 57343
+BinaryenFeatureRelaxedSIMD: 8192
+BinaryenFeatureExtendedConst: 16384
+BinaryenFeatureStrings: 32768
+BinaryenFeatureAll: 126975
 (f32.neg
  (f32.const -33.61199951171875)
 )
@@ -41,17 +66,28 @@ BinaryenFeatureAll: 57343
 )
 (table.size $0)
 (table.grow $0
- (ref.null func)
+ (ref.null nofunc)
  (i32.const 0)
 )
 (module
+ (type $[mut:i8] (array (mut i8)))
+ (type ${mut:i32} (struct (field (mut i32))))
  (type $i32_i64_f32_f64_=>_i32 (func (param i32 i64 f32 f64) (result i32)))
+ (type $[mut:i16] (array (mut i16)))
  (type $i32_=>_none (func (param i32)))
  (type $i32_f64_=>_f32 (func (param i32 f64) (result f32)))
  (type $none_=>_none (func))
  (import "module" "base" (func $an-imported (param i32 f64) (result f32)))
  (global $a-global i32 (i32.const 7))
  (global $a-mutable-global (mut f32) (f32.const 7.5))
+ (global $i8Array-global (mut (ref null $[mut:i8])) (array.new_default $[mut:i8]
+  (i32.const 0)
+ ))
+ (global $i16Array-global (mut (ref null $[mut:i16])) (array.new_default $[mut:i16]
+  (i32.const 0)
+ ))
+ (global $i32Struct-global (mut (ref null ${mut:i32})) (struct.new_default ${mut:i32}))
+ (global $string-global (mut stringref) (string.const ""))
  (memory $0 (shared 1 256))
  (data (i32.const 10) "hello, world")
  (data "I am passive")
@@ -60,12 +96,12 @@ BinaryenFeatureAll: 57343
  (elem $0 (table $0) (i32.const 0) func "$kitchen()sinker")
  (elem $passive func "$kitchen()sinker")
  (tag $a-tag (param i32))
- (export "kitchen_sinker" (func "$kitchen()sinker"))
  (export "mem" (memory $0))
+ (export "kitchen_sinker" (func "$kitchen()sinker"))
  (start $starter)
  (func "$kitchen()sinker" (param $0 i32) (param $1 i64) (param $2 f32) (param $3 f64) (result i32)
   (local $4 i32)
-  (local $5 anyref)
+  (local $5 externref)
   (block $the-body (result i32)
    (block $the-nothing
     (drop
@@ -1946,7 +1982,7 @@ BinaryenFeatureAll: 57343
       )
       (drop
        (ref.is_null
-        (ref.null any)
+        (ref.null noextern)
        )
       )
       (drop
@@ -1956,50 +1992,60 @@ BinaryenFeatureAll: 57343
       )
       (drop
        (select (result funcref)
-        (ref.null func)
+        (ref.null nofunc)
         (ref.func "$kitchen()sinker")
         (i32.const 1)
        )
       )
       (drop
        (ref.eq
-        (ref.null eq)
-        (ref.null eq)
+        (ref.null none)
+        (ref.null none)
        )
       )
       (drop
        (ref.is_func
-        (ref.null any)
+        (ref.null none)
        )
       )
       (drop
        (ref.is_data
-        (ref.null any)
+        (ref.null none)
        )
       )
       (drop
        (ref.is_i31
-        (ref.null any)
+        (ref.null none)
        )
       )
       (drop
        (ref.as_non_null
-        (ref.null any)
+        (ref.null none)
        )
       )
       (drop
        (ref.as_func
-        (ref.null any)
+        (ref.null none)
        )
       )
       (drop
        (ref.as_data
-        (ref.null any)
+        (ref.null none)
        )
       )
       (drop
        (ref.as_i31
-        (ref.null any)
+        (ref.null none)
+       )
+      )
+      (drop
+       (extern.internalize
+        (ref.null noextern)
+       )
+      )
+      (drop
+       (extern.externalize
+        (ref.null none)
        )
       )
       (try
@@ -2086,7 +2132,7 @@ BinaryenFeatureAll: 57343
        (pop funcref)
       )
       (drop
-       (pop anyref)
+       (pop externref)
       )
       (drop
        (pop i32 i64 f32 f64)
@@ -2118,6 +2164,288 @@ BinaryenFeatureAll: 57343
         )
        )
       )
+      (drop
+       (ref.test_static $[mut:i8]
+        (global.get $i8Array-global)
+       )
+      )
+      (drop
+       (ref.cast_static $[mut:i8]
+        (global.get $i8Array-global)
+       )
+      )
+      (drop
+       (struct.new_default ${mut:i32})
+      )
+      (drop
+       (struct.new ${mut:i32}
+        (i32.const 0)
+       )
+      )
+      (drop
+       (struct.get ${mut:i32} 0
+        (global.get $i32Struct-global)
+       )
+      )
+      (struct.set ${mut:i32} 0
+       (global.get $i32Struct-global)
+       (i32.const 0)
+      )
+      (drop
+       (array.new_default $[mut:i8]
+        (i32.const 3)
+       )
+      )
+      (drop
+       (array.new $[mut:i8]
+        (i32.const 42)
+        (i32.const 3)
+       )
+      )
+      (drop
+       (array.init_static $[mut:i8]
+        (i32.const 1)
+        (i32.const 2)
+        (i32.const 3)
+       )
+      )
+      (drop
+       (array.get_s $[mut:i8]
+        (global.get $i8Array-global)
+        (i32.const 0)
+       )
+      )
+      (array.set $[mut:i8]
+       (global.get $i8Array-global)
+       (i32.const 0)
+       (i32.const 42)
+      )
+      (drop
+       (array.len
+        (global.get $i8Array-global)
+       )
+      )
+      (array.copy $[mut:i8] $[mut:i8]
+       (global.get $i8Array-global)
+       (i32.const 0)
+       (global.get $i8Array-global)
+       (i32.const 1)
+       (i32.const 2)
+      )
+      (drop
+       (string.new_wtf8 utf8
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf8 wtf8
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf8 replace
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf16
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf8_array utf8
+        (global.get $i8Array-global)
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf8_array wtf8
+        (global.get $i8Array-global)
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf8_array replace
+        (global.get $i8Array-global)
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.new_wtf16_array
+        (global.get $i16Array-global)
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.const "hello world")
+      )
+      (drop
+       (string.measure_wtf8 utf8
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.measure_wtf8 wtf8
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.measure_wtf16
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.is_usv_sequence
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (stringview_wtf16.length
+        (string.as_wtf16
+         (global.get $string-global)
+        )
+       )
+      )
+      (drop
+       (string.encode_wtf8 utf8
+        (global.get $string-global)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.encode_wtf8 wtf8
+        (global.get $string-global)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.encode_wtf16
+        (global.get $string-global)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.encode_wtf8_array utf8
+        (global.get $string-global)
+        (global.get $i8Array-global)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.encode_wtf8_array wtf8
+        (global.get $string-global)
+        (global.get $i8Array-global)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.encode_wtf16_array
+        (global.get $string-global)
+        (global.get $i16Array-global)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (string.concat
+        (global.get $string-global)
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.eq
+        (global.get $string-global)
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.as_wtf8
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.as_wtf16
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (string.as_iter
+        (global.get $string-global)
+       )
+      )
+      (drop
+       (stringview_wtf8.advance
+        (string.as_wtf8
+         (global.get $string-global)
+        )
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (stringview_wtf16.get_codeunit
+        (string.as_wtf16
+         (global.get $string-global)
+        )
+        (i32.const 0)
+       )
+      )
+      (drop
+       (stringview_iter.next
+        (string.as_iter
+         (global.get $string-global)
+        )
+       )
+      )
+      (drop
+       (stringview_iter.advance
+        (string.as_iter
+         (global.get $string-global)
+        )
+        (i32.const 1)
+       )
+      )
+      (drop
+       (stringview_iter.rewind
+        (string.as_iter
+         (global.get $string-global)
+        )
+        (i32.const 1)
+       )
+      )
+      (drop
+       (stringview_wtf8.slice
+        (string.as_wtf8
+         (global.get $string-global)
+        )
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (stringview_wtf16.slice
+        (string.as_wtf16
+         (global.get $string-global)
+        )
+        (i32.const 0)
+        (i32.const 0)
+       )
+      )
+      (drop
+       (stringview_iter.slice
+        (string.as_iter
+         (global.get $string-global)
+        )
+        (i32.const 0)
+       )
+      )
       (nop)
       (unreachable)
      )
@@ -2700,3 +3028,26 @@ optimized:
   (i32.const 4)
  )
 )
+BinaryenTypeSystemEquirecursive: 0
+BinaryenTypeSystemNominal: 1
+BinaryenTypeSystemIsorecursive: 2
+TypeBuilderErrorReasonSelfSupertype: 0
+TypeBuilderErrorReasonInvalidSupertype: 1
+TypeBuilderErrorReasonForwardSupertypeReference: 2
+TypeBuilderErrorReasonForwardChildReference: 3
+module with recursive GC types:
+(module
+ (type $SomeArray (array_subtype (mut (ref null $SomeArray)) data))
+ (type $SomeStruct (struct_subtype (field $SomeField (mut (ref null $SomeStruct))) data))
+ (type $SomeSignature (func_subtype (param (ref null $SomeSignature) (ref null $SomeArray)) (result (ref null $SomeSignature)) func))
+ (type $none_=>_none (func_subtype func))
+ (type $SomeSubStruct (struct_subtype (field $SomeField (mut (ref null $SomeStruct))) (field $SomePackedField i8) $SomeStruct))
+ (func $test (type $none_=>_none)
+  (local $0 (ref null $SomeArray))
+  (local $1 (ref null $SomeStruct))
+  (local $2 (ref null $SomeSignature))
+  (local $3 eqref)
+  (local $4 (ref null $SomeSubStruct))
+  (nop)
+ )
+)
diff --git a/test/example/c-api-relooper-unreachable-if.cpp b/test/example/c-api-relooper-unreachable-if.cpp
index e8bfc43..1c57088 100644
--- a/test/example/c-api-relooper-unreachable-if.cpp
+++ b/test/example/c-api-relooper-unreachable-if.cpp
@@ -28,13 +28,21 @@ int main() {
                       segmentOffsets,
                       segmentSizes,
                       0,
-                      0);
+                      0,
+                      0,
+                      "0");
   }
   the_relooper = RelooperCreate(the_module);
   expressions[1] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32());
   expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[3] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[2], expressions[1], BinaryenTypeInt32());
+  expressions[3] = BinaryenStore(the_module,
+                                 4,
+                                 0,
+                                 0,
+                                 expressions[2],
+                                 expressions[1],
+                                 BinaryenTypeInt32(),
+                                 "0");
   expressions[4] = BinaryenReturn(the_module, expressions[0]);
   {
     BinaryenExpressionRef children[] = {expressions[3], expressions[4]};
@@ -43,8 +51,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[5]);
   expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[7] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[6]);
+  expressions[7] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[6], "0");
   expressions[8] = BinaryenLocalSet(the_module, 0, expressions[7]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[8]);
   RelooperAddBranch(
@@ -66,8 +74,14 @@ int main() {
   the_relooper = RelooperCreate(the_module);
   expressions[10] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32());
   expressions[11] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[12] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[11], expressions[10], BinaryenTypeInt32());
+  expressions[12] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[11],
+                                  expressions[10],
+                                  BinaryenTypeInt32(),
+                                  "0");
   expressions[13] = BinaryenReturn(the_module, expressions[0]);
   {
     BinaryenExpressionRef children[] = {expressions[12], expressions[13]};
@@ -76,8 +90,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[14]);
   expressions[15] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[16] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[15]);
+  expressions[16] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[15], "0");
   expressions[17] = BinaryenLocalSet(the_module, 0, expressions[16]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[17]);
   RelooperAddBranch(
@@ -115,8 +129,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[1], relooperBlocks[1], expressions[0], expressions[0]);
   expressions[21] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[22] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[21]);
+  expressions[22] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[21], "0");
   expressions[23] = BinaryenLocalSet(the_module, 0, expressions[22]);
   relooperBlocks[2] = RelooperAddBlock(the_relooper, expressions[23]);
   RelooperAddBranch(
@@ -139,8 +153,14 @@ int main() {
   the_relooper = RelooperCreate(the_module);
   expressions[25] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32());
   expressions[26] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[27] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[26], expressions[25], BinaryenTypeInt32());
+  expressions[27] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[26],
+                                  expressions[25],
+                                  BinaryenTypeInt32(),
+                                  "0");
   expressions[28] = BinaryenReturn(the_module, expressions[0]);
   {
     BinaryenExpressionRef children[] = {expressions[27], expressions[28]};
@@ -149,8 +169,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[29]);
   expressions[30] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[31] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[30]);
+  expressions[31] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[30], "0");
   expressions[32] = BinaryenLocalSet(the_module, 0, expressions[31]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[32]);
   RelooperAddBranch(
@@ -174,8 +194,14 @@ int main() {
   the_relooper = RelooperCreate(the_module);
   expressions[34] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32());
   expressions[35] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[36] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[35], expressions[34], BinaryenTypeInt32());
+  expressions[36] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[35],
+                                  expressions[34],
+                                  BinaryenTypeInt32(),
+                                  "0");
   expressions[37] = BinaryenReturn(the_module, expressions[0]);
   {
     BinaryenExpressionRef children[] = {expressions[36], expressions[37]};
@@ -184,8 +210,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[38]);
   expressions[39] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[40] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[39]);
+  expressions[40] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[39], "0");
   expressions[41] = BinaryenLocalSet(the_module, 0, expressions[40]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[41]);
   RelooperAddBranch(
@@ -232,8 +258,14 @@ int main() {
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[49]);
   expressions[50] = BinaryenLocalGet(the_module, 3, BinaryenTypeInt32());
   expressions[51] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[52] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[51], expressions[50], BinaryenTypeInt32());
+  expressions[52] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[51],
+                                  expressions[50],
+                                  BinaryenTypeInt32(),
+                                  "0");
   expressions[53] = BinaryenReturn(the_module, expressions[0]);
   {
     BinaryenExpressionRef children[] = {expressions[52], expressions[53]};
@@ -244,8 +276,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[1], expressions[0], expressions[0]);
   expressions[55] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[56] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[55]);
+  expressions[56] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[55], "0");
   expressions[57] = BinaryenLocalSet(the_module, 3, expressions[56]);
   relooperBlocks[2] = RelooperAddBlock(the_relooper, expressions[57]);
   RelooperAddBranch(
@@ -287,20 +319,38 @@ int main() {
     BinaryenBinary(the_module, 36, expressions[72], expressions[71]);
   expressions[74] = BinaryenUnary(the_module, 24, expressions[73]);
   expressions[75] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[76] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[75]);
+  expressions[76] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[75], "0");
   expressions[77] = BinaryenConst(the_module, BinaryenLiteralInt32(128));
   expressions[78] =
     BinaryenBinary(the_module, 1, expressions[76], expressions[77]);
   expressions[79] =
     BinaryenLocalTee(the_module, 3, expressions[78], BinaryenTypeInt32());
-  expressions[80] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[75], expressions[79], BinaryenTypeInt32());
+  expressions[80] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[75],
+                                  expressions[79],
+                                  BinaryenTypeInt32(),
+                                  "0");
   expressions[81] = BinaryenLocalGet(the_module, 3, BinaryenTypeInt32());
-  expressions[82] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[81], expressions[70], BinaryenTypeInt32());
-  expressions[83] = BinaryenStore(
-    the_module, 4, 4, 0, expressions[81], expressions[74], BinaryenTypeInt32());
+  expressions[82] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[81],
+                                  expressions[70],
+                                  BinaryenTypeInt32(),
+                                  "0");
+  expressions[83] = BinaryenStore(the_module,
+                                  4,
+                                  4,
+                                  0,
+                                  expressions[81],
+                                  expressions[74],
+                                  BinaryenTypeInt32(),
+                                  "0");
   {
     BinaryenExpressionRef children[] = {expressions[60],
                                         expressions[62],
@@ -313,8 +363,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[84]);
   expressions[85] = BinaryenLocalGet(the_module, 3, BinaryenTypeInt32());
-  expressions[86] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[85]);
+  expressions[86] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[85], "0");
   expressions[87] = BinaryenLocalSet(the_module, 1, expressions[86]);
   expressions[88] = BinaryenLocalGet(the_module, 1, BinaryenTypeInt32());
   expressions[89] = BinaryenLocalSet(the_module, 4, expressions[88]);
@@ -322,8 +372,14 @@ int main() {
   expressions[91] = BinaryenLocalSet(the_module, 5, expressions[90]);
   expressions[92] = BinaryenLocalGet(the_module, 6, BinaryenTypeInt32());
   expressions[93] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[94] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[93], expressions[92], BinaryenTypeInt32());
+  expressions[94] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[93],
+                                  expressions[92],
+                                  BinaryenTypeInt32(),
+                                  "0");
   expressions[95] = BinaryenLocalGet(the_module, 5, BinaryenTypeInt32());
   expressions[96] = BinaryenReturn(the_module, expressions[95]);
   {
@@ -337,8 +393,8 @@ int main() {
   }
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[97]);
   expressions[98] = BinaryenLocalGet(the_module, 3, BinaryenTypeInt32());
-  expressions[99] =
-    BinaryenLoad(the_module, 4, 0, 8, 0, BinaryenTypeInt32(), expressions[98]);
+  expressions[99] = BinaryenLoad(
+    the_module, 4, 0, 8, 0, BinaryenTypeInt32(), expressions[98], "0");
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[1], expressions[99], expressions[0]);
   expressions[100] = BinaryenUnreachable(the_module);
@@ -346,8 +402,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[2], expressions[0], expressions[0]);
   expressions[101] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[102] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[101]);
+  expressions[102] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[101], "0");
   expressions[103] = BinaryenLocalSet(the_module, 6, expressions[102]);
   relooperBlocks[3] = RelooperAddBlock(the_relooper, expressions[103]);
   RelooperAddBranch(
@@ -403,8 +459,8 @@ int main() {
     BinaryenBinary(the_module, 36, expressions[119], expressions[118]);
   expressions[121] = BinaryenUnary(the_module, 24, expressions[120]);
   expressions[122] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[123] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[122]);
+  expressions[123] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[122], "0");
   expressions[124] = BinaryenConst(the_module, BinaryenLiteralInt32(128));
   expressions[125] =
     BinaryenBinary(the_module, 1, expressions[123], expressions[124]);
@@ -416,7 +472,8 @@ int main() {
                                    0,
                                    expressions[122],
                                    expressions[126],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[128] = BinaryenLocalGet(the_module, 5, BinaryenTypeInt32());
   expressions[129] = BinaryenStore(the_module,
                                    4,
@@ -424,14 +481,16 @@ int main() {
                                    0,
                                    expressions[128],
                                    expressions[117],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[130] = BinaryenStore(the_module,
                                    4,
                                    4,
                                    0,
                                    expressions[128],
                                    expressions[121],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   {
     BinaryenExpressionRef children[] = {
       expressions[115], expressions[127], expressions[129], expressions[130]};
@@ -440,8 +499,8 @@ int main() {
   }
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[131]);
   expressions[132] = BinaryenLocalGet(the_module, 5, BinaryenTypeInt32());
-  expressions[133] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[132]);
+  expressions[133] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[132], "0");
   expressions[134] = BinaryenLocalSet(the_module, 3, expressions[133]);
   expressions[135] = BinaryenLocalGet(the_module, 3, BinaryenTypeInt32());
   expressions[136] = BinaryenLocalSet(the_module, 6, expressions[135]);
@@ -470,7 +529,8 @@ int main() {
                                    0,
                                    expressions[145],
                                    expressions[144],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[147] = BinaryenLocalGet(the_module, 8, BinaryenTypeInt32());
   expressions[148] = BinaryenReturn(the_module, expressions[147]);
   {
@@ -483,8 +543,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[1], expressions[0], expressions[0]);
   expressions[150] = BinaryenLocalGet(the_module, 5, BinaryenTypeInt32());
-  expressions[151] =
-    BinaryenLoad(the_module, 4, 0, 8, 0, BinaryenTypeInt32(), expressions[150]);
+  expressions[151] = BinaryenLoad(
+    the_module, 4, 0, 8, 0, BinaryenTypeInt32(), expressions[150], "0");
   RelooperAddBranch(
     relooperBlocks[1], relooperBlocks[2], expressions[151], expressions[0]);
   expressions[152] = BinaryenUnreachable(the_module);
@@ -494,8 +554,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[2], relooperBlocks[3], expressions[0], expressions[0]);
   expressions[153] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[154] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[153]);
+  expressions[154] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[153], "0");
   expressions[155] = BinaryenLocalSet(the_module, 9, expressions[154]);
   relooperBlocks[5] = RelooperAddBlock(the_relooper, expressions[155]);
   RelooperAddBranch(
@@ -538,7 +598,9 @@ int main() {
                       segmentOffsets,
                       segmentSizes,
                       0,
-                      0);
+                      0,
+                      0,
+                      "0");
   }
   expressions[157] = BinaryenConst(the_module, BinaryenLiteralInt32(65535));
   expressions[158] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
@@ -548,7 +610,8 @@ int main() {
                                    0,
                                    expressions[158],
                                    expressions[157],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[160] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
   expressions[161] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
   {
@@ -598,8 +661,8 @@ int main() {
     BinaryenBinary(the_module, 36, expressions[182], expressions[181]);
   expressions[184] = BinaryenUnary(the_module, 24, expressions[183]);
   expressions[185] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[186] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[185]);
+  expressions[186] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[185], "0");
   expressions[187] = BinaryenConst(the_module, BinaryenLiteralInt32(128));
   expressions[188] =
     BinaryenBinary(the_module, 1, expressions[186], expressions[187]);
@@ -611,7 +674,8 @@ int main() {
                                    0,
                                    expressions[185],
                                    expressions[189],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[191] = BinaryenLocalGet(the_module, 6, BinaryenTypeInt32());
   expressions[192] = BinaryenStore(the_module,
                                    4,
@@ -619,14 +683,16 @@ int main() {
                                    0,
                                    expressions[191],
                                    expressions[180],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[193] = BinaryenStore(the_module,
                                    4,
                                    4,
                                    0,
                                    expressions[191],
                                    expressions[184],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   {
     BinaryenExpressionRef children[] = {expressions[166],
                                         expressions[168],
@@ -641,8 +707,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[194]);
   expressions[195] = BinaryenLocalGet(the_module, 6, BinaryenTypeInt32());
-  expressions[196] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[195]);
+  expressions[196] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[195], "0");
   expressions[197] = BinaryenLocalSet(the_module, 7, expressions[196]);
   expressions[198] = BinaryenLocalGet(the_module, 8, BinaryenTypeInt32());
   expressions[199] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
@@ -652,7 +718,8 @@ int main() {
                                    0,
                                    expressions[199],
                                    expressions[198],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[201] = BinaryenLocalGet(the_module, 7, BinaryenTypeInt32());
   expressions[202] = BinaryenReturn(the_module, expressions[201]);
   {
@@ -663,8 +730,8 @@ int main() {
   }
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[203]);
   expressions[204] = BinaryenLocalGet(the_module, 6, BinaryenTypeInt32());
-  expressions[205] =
-    BinaryenLoad(the_module, 4, 0, 8, 0, BinaryenTypeInt32(), expressions[204]);
+  expressions[205] = BinaryenLoad(
+    the_module, 4, 0, 8, 0, BinaryenTypeInt32(), expressions[204], "0");
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[1], expressions[205], expressions[0]);
   expressions[206] = BinaryenUnreachable(the_module);
@@ -672,8 +739,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[2], expressions[0], expressions[0]);
   expressions[207] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[208] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[207]);
+  expressions[208] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[207], "0");
   expressions[209] = BinaryenLocalSet(the_module, 8, expressions[208]);
   relooperBlocks[3] = RelooperAddBlock(the_relooper, expressions[209]);
   RelooperAddBranch(
@@ -719,7 +786,8 @@ int main() {
                                    0,
                                    expressions[219],
                                    expressions[218],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[221] = BinaryenLocalGet(the_module, 3, BinaryenTypeInt32());
   expressions[222] = BinaryenReturn(the_module, expressions[221]);
   {
@@ -733,8 +801,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[223]);
   expressions[224] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[225] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[224]);
+  expressions[225] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[224], "0");
   expressions[226] = BinaryenLocalSet(the_module, 4, expressions[225]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[226]);
   RelooperAddBranch(
@@ -780,7 +848,8 @@ int main() {
                                    0,
                                    expressions[241],
                                    expressions[240],
-                                   BinaryenTypeInt32());
+                                   BinaryenTypeInt32(),
+                                   "0");
   expressions[243] = BinaryenLocalGet(the_module, 6, BinaryenTypeInt32());
   expressions[244] = BinaryenReturn(the_module, expressions[243]);
   {
@@ -796,8 +865,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[245]);
   expressions[246] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[247] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[246]);
+  expressions[247] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[246], "0");
   expressions[248] = BinaryenLocalSet(the_module, 7, expressions[247]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[248]);
   RelooperAddBranch(
@@ -848,7 +917,8 @@ int main() {
                                    0,
                                    expressions[263],
                                    expressions[262],
-                                   BinaryenTypeInt64());
+                                   BinaryenTypeInt64(),
+                                   "0");
   expressions[265] = BinaryenLocalGet(the_module, 6, BinaryenTypeInt32());
   expressions[266] = BinaryenReturn(the_module, expressions[265]);
   {
@@ -864,8 +934,8 @@ int main() {
   }
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[267]);
   expressions[268] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[269] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt64(), expressions[268]);
+  expressions[269] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt64(), expressions[268], "0");
   expressions[270] = BinaryenLocalSet(the_module, 7, expressions[269]);
   relooperBlocks[1] = RelooperAddBlock(the_relooper, expressions[270]);
   RelooperAddBranch(
diff --git a/test/example/c-api-unused-mem.cpp b/test/example/c-api-unused-mem.cpp
index 1231f56..a0970a8 100644
--- a/test/example/c-api-unused-mem.cpp
+++ b/test/example/c-api-unused-mem.cpp
@@ -29,7 +29,9 @@ int main() {
                       segmentOffsets,
                       segmentSizes,
                       0,
-                      0);
+                      0,
+                      0,
+                      "0");
   }
   the_relooper = RelooperCreate(the_module);
   {
@@ -40,8 +42,14 @@ int main() {
   relooperBlocks[0] = RelooperAddBlock(the_relooper, expressions[1]);
   expressions[2] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32());
   expressions[3] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[4] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[3], expressions[2], BinaryenTypeInt32());
+  expressions[4] = BinaryenStore(the_module,
+                                 4,
+                                 0,
+                                 0,
+                                 expressions[3],
+                                 expressions[2],
+                                 BinaryenTypeInt32(),
+                                 "0");
   expressions[5] = BinaryenReturn(the_module, expressions[0]);
   {
     BinaryenExpressionRef children[] = {expressions[4], expressions[5]};
@@ -52,8 +60,8 @@ int main() {
   RelooperAddBranch(
     relooperBlocks[0], relooperBlocks[1], expressions[0], expressions[0]);
   expressions[7] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[8] =
-    BinaryenLoad(the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[7]);
+  expressions[8] = BinaryenLoad(
+    the_module, 4, 0, 0, 0, BinaryenTypeInt32(), expressions[7], "0");
   expressions[9] = BinaryenLocalSet(the_module, 0, expressions[8]);
   relooperBlocks[2] = RelooperAddBlock(the_relooper, expressions[9]);
   RelooperAddBranch(
@@ -86,12 +94,20 @@ int main() {
                       segmentOffsets,
                       segmentSizes,
                       0,
-                      0);
+                      0,
+                      0,
+                      "0");
   }
   expressions[11] = BinaryenConst(the_module, BinaryenLiteralInt32(65535));
   expressions[12] = BinaryenConst(the_module, BinaryenLiteralInt32(0));
-  expressions[13] = BinaryenStore(
-    the_module, 4, 0, 0, expressions[12], expressions[11], BinaryenTypeInt32());
+  expressions[13] = BinaryenStore(the_module,
+                                  4,
+                                  0,
+                                  0,
+                                  expressions[12],
+                                  expressions[11],
+                                  BinaryenTypeInt32(),
+                                  "0");
   {
     BinaryenExpressionRef operands[] = {0};
     expressions[14] =
@@ -120,7 +136,7 @@ int main() {
     char buffer[1024];
     BinaryenSetDebugInfo(1);
     size_t size = BinaryenModuleWrite(the_module, buffer, 1024);
-    printf("%d\n", size);
+    printf("%zd\n", size);
     BinaryenModuleRef copy = BinaryenModuleRead(buffer, size);
     BinaryenModulePrint(copy);
     BinaryenModuleDispose(copy);
diff --git a/test/example/c-api-unused-mem.txt b/test/example/c-api-unused-mem.txt
index 0a5ce80..802e720 100644
--- a/test/example/c-api-unused-mem.txt
+++ b/test/example/c-api-unused-mem.txt
@@ -36,7 +36,7 @@
   (call $main)
  )
 )
-145
+133
 (module
  (type $none_=>_none (func))
  (memory $0 1024 1024)
@@ -53,20 +53,13 @@
      (i32.const 0)
     )
    )
-   (block $label$2
-    (br $label$1)
-   )
+   (br $label$1)
   )
-  (block $label$3
-   (block $label$4
-    (i32.store
-     (i32.const 0)
-     (local.get $0)
-    )
-    (return)
-   )
-   (unreachable)
+  (i32.store
+   (i32.const 0)
+   (local.get $0)
   )
+  (return)
  )
  (func $__wasm_start
   (i32.store
diff --git a/test/example/cpp-unit.cpp b/test/example/cpp-unit.cpp
index b945304..d2339f0 100644
--- a/test/example/cpp-unit.cpp
+++ b/test/example/cpp-unit.cpp
@@ -605,7 +605,30 @@ void test_effects() {
 
   // ArrayCopy can trap, reads arrays, and writes arrays (but not structs).
   {
+    Type arrayref = Type(HeapType(Array(Field(Type::i32, Mutable))), Nullable);
+    LocalGet dest;
+    dest.index = 0;
+    dest.type = arrayref;
+    LocalGet destIndex;
+    destIndex.index = 1;
+    destIndex.type = Type::i32;
+    LocalGet src;
+    src.index = 0;
+    src.type = arrayref;
+    LocalGet srcIndex;
+    srcIndex.index = 1;
+    srcIndex.type = Type::i32;
+    LocalGet length;
+    srcIndex.index = 2;
+    srcIndex.type = Type::i32;
     ArrayCopy arrayCopy(module.allocator);
+    arrayCopy.destRef = &dest;
+    arrayCopy.destIndex = &destIndex;
+    arrayCopy.srcRef = &src;
+    arrayCopy.srcIndex = &srcIndex;
+    arrayCopy.length = &length;
+    arrayCopy.finalize();
+
     EffectAnalyzer effects(options, module);
     effects.visit(&arrayCopy);
     assert_equal(effects.trap, true);
@@ -616,19 +639,6 @@ void test_effects() {
   }
 }
 
-void test_literals() {
-  // The i31 heap type may or may not be basic, depending on if it is nullable.
-  // Verify we handle both code paths.
-  {
-    Literal x(Type(HeapType::i31, Nullable));
-    std::cout << x << '\n';
-  }
-  {
-    Literal x(Type(HeapType::i31, NonNullable));
-    std::cout << x << '\n';
-  }
-}
-
 void test_field() {
   // Simple types
   assert_equal(Field(Type::i32, Immutable).getByteSize(), 4);
@@ -681,7 +691,6 @@ int main() {
   test_bits();
   test_cost();
   test_effects();
-  test_literals();
   test_field();
   test_queue();
 
diff --git a/test/example/cpp-unit.txt b/test/example/cpp-unit.txt
index cbe80e1..3582111 100644
--- a/test/example/cpp-unit.txt
+++ b/test/example/cpp-unit.txt
@@ -1,3 +1 @@
-i31ref(0)
-i31ref(0)
 Success
diff --git a/test/example/relooper-fuzz.c b/test/example/relooper-fuzz.c
index d953d9c..a648e61 100644
--- a/test/example/relooper-fuzz.c
+++ b/test/example/relooper-fuzz.c
@@ -25,7 +25,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 27)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -45,29 +46,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -89,7 +93,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -338,7 +344,8 @@ int main() {
                       0,
                       BinaryenConst(module, BinaryenLiteralInt32(8 + 4 * i)),
                       BinaryenConst(module, BinaryenLiteralInt32(decisions[i])),
-                      BinaryenTypeInt32());
+                      BinaryenTypeInt32(),
+                      "0");
     }
   }
   full[numDecisions] = body;
@@ -362,7 +369,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   assert(BinaryenModuleValidate(module));
 
diff --git a/test/example/relooper-fuzz1.c b/test/example/relooper-fuzz1.c
index 9e49fbc..a524922 100644
--- a/test/example/relooper-fuzz1.c
+++ b/test/example/relooper-fuzz1.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 30)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -337,7 +343,8 @@ int main() {
                       0,
                       BinaryenConst(module, BinaryenLiteralInt32(8 + 4 * i)),
                       BinaryenConst(module, BinaryenLiteralInt32(decisions[i])),
-                      BinaryenTypeInt32());
+                      BinaryenTypeInt32(),
+                      "0");
     }
   }
   full[numDecisions] = body;
@@ -359,7 +366,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   assert(BinaryenModuleValidate(module));
 
diff --git a/test/example/relooper-fuzz2.c b/test/example/relooper-fuzz2.c
index a48b86e..8d129b7 100644
--- a/test/example/relooper-fuzz2.c
+++ b/test/example/relooper-fuzz2.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 27)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -266,47 +272,49 @@ int main() {
     b0,
     b1,
     NULL,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranch(
     b1,
     b1,
     NULL,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   {
     BinaryenIndex values[] = {0,  2,  4,  6,  8,  10, 12, 14, 16, 18, 20,
@@ -333,9 +341,11 @@ int main() {
                        0,
                        0,
                        BinaryenTypeInt32(),
-                       BinaryenConst(module, BinaryenLiteralInt32(4))),
+                       BinaryenConst(module, BinaryenLiteralInt32(4)),
+                       "0"),
           BinaryenConst(module, BinaryenLiteralInt32(4 * 6))),
-        BinaryenTypeInt32()));
+        BinaryenTypeInt32(),
+        "0"));
   }
 
   RelooperAddBranchForSwitch(
@@ -343,48 +353,50 @@ int main() {
     b4,
     NULL,
     0,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranchForSwitch(
     b3,
     b6,
     NULL,
     0,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranch(
     b4,
@@ -397,71 +409,74 @@ int main() {
                      BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
                      BinaryenConst(module, BinaryenLiteralInt32(2))),
       BinaryenConst(module, BinaryenLiteralInt32(0))),
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranch(
     b4,
     b3,
     NULL,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranchForSwitch(
     b5,
     b1,
     NULL,
     0,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   {
     BinaryenIndex values[] = {0,  3,  6,  9,  12,  15,  18, 21, 24, 27,
@@ -488,9 +503,11 @@ int main() {
                        0,
                        0,
                        BinaryenTypeInt32(),
-                       BinaryenConst(module, BinaryenLiteralInt32(4))),
+                       BinaryenConst(module, BinaryenLiteralInt32(4)),
+                       "0"),
           BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
-        BinaryenTypeInt32()));
+        BinaryenTypeInt32(),
+        "0"));
   }
 
   {
@@ -519,9 +536,11 @@ int main() {
                        0,
                        0,
                        BinaryenTypeInt32(),
-                       BinaryenConst(module, BinaryenLiteralInt32(4))),
+                       BinaryenConst(module, BinaryenLiteralInt32(4)),
+                       "0"),
           BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
-        BinaryenTypeInt32()));
+        BinaryenTypeInt32(),
+        "0"));
   }
 
   RelooperAddBranchForSwitch(
@@ -529,24 +548,25 @@ int main() {
     b2,
     NULL,
     0,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranch(
     b7,
@@ -559,70 +579,73 @@ int main() {
                      BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
                      BinaryenConst(module, BinaryenLiteralInt32(2))),
       BinaryenConst(module, BinaryenLiteralInt32(0))),
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 1))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 1))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranch(
     b7,
     b1,
     NULL,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   RelooperAddBranch(
     b8,
     b8,
     NULL,
-    BinaryenStore(
-      module,
-      4,
-      0,
-      0,
-      BinaryenConst(module, BinaryenLiteralInt32(4)),
-      BinaryenBinary(
-        module,
-        BinaryenAddInt32(),
-        BinaryenLoad(module,
-                     4,
-                     0,
-                     0,
-                     0,
-                     BinaryenTypeInt32(),
-                     BinaryenConst(module, BinaryenLiteralInt32(4))),
-        BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
-      BinaryenTypeInt32()));
+    BinaryenStore(module,
+                  4,
+                  0,
+                  0,
+                  BinaryenConst(module, BinaryenLiteralInt32(4)),
+                  BinaryenBinary(
+                    module,
+                    BinaryenAddInt32(),
+                    BinaryenLoad(module,
+                                 4,
+                                 0,
+                                 0,
+                                 0,
+                                 BinaryenTypeInt32(),
+                                 BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                 "0"),
+                    BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
+                  BinaryenTypeInt32(),
+                  "0"));
 
   BinaryenExpressionRef body = RelooperRenderAndDispose(relooper, b0, 1);
 
@@ -644,7 +667,8 @@ int main() {
                       0,
                       BinaryenConst(module, BinaryenLiteralInt32(8 + 4 * i)),
                       BinaryenConst(module, BinaryenLiteralInt32(decisions[i])),
-                      BinaryenTypeInt32());
+                      BinaryenTypeInt32(),
+                      "0");
     }
   }
   full[numDecisions] = body;
@@ -666,7 +690,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/relooper-merge1.c b/test/example/relooper-merge1.c
index 491839f..4086de4 100644
--- a/test/example/relooper-merge1.c
+++ b/test/example/relooper-merge1.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 12)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -226,7 +232,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/relooper-merge2.c b/test/example/relooper-merge2.c
index ed5fc0f..d85f9d7 100644
--- a/test/example/relooper-merge2.c
+++ b/test/example/relooper-merge2.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 12)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -241,7 +247,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/relooper-merge3.c b/test/example/relooper-merge3.c
index 655b4d2..9347e0f 100644
--- a/test/example/relooper-merge3.c
+++ b/test/example/relooper-merge3.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 12)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -225,7 +231,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/relooper-merge4.c b/test/example/relooper-merge4.c
index 2e56244..b437345 100644
--- a/test/example/relooper-merge4.c
+++ b/test/example/relooper-merge4.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 12)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -225,7 +231,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/relooper-merge5.c b/test/example/relooper-merge5.c
index 41a36c1..79ee6ab 100644
--- a/test/example/relooper-merge5.c
+++ b/test/example/relooper-merge5.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 12)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -225,7 +231,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/relooper-merge6.c b/test/example/relooper-merge6.c
index 23e639c..774dd93 100644
--- a/test/example/relooper-merge6.c
+++ b/test/example/relooper-merge6.c
@@ -23,7 +23,8 @@ int main() {
                    0,
                    0,
                    BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4))),
+                   BinaryenConst(module, BinaryenLiteralInt32(4)),
+                   "0"),
       BinaryenConst(module, BinaryenLiteralInt32(4 * 12)) // jumps of 4 bytes
       ),
     BinaryenUnreachable(module),
@@ -43,29 +44,32 @@ int main() {
                                 0,
                                 0,
                                 BinaryenTypeInt32(),
-                                BinaryenConst(module, BinaryenLiteralInt32(4))),
+                                BinaryenConst(module, BinaryenLiteralInt32(4)),
+                                "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4))),
-    BinaryenTypeInt32());
+    BinaryenTypeInt32(),
+    "0");
 
   // optionally, print the return value
   BinaryenExpressionRef args[] = {BinaryenBinary(
     module,
     BinaryenSubInt32(),
     BinaryenConst(module, BinaryenLiteralInt32(0)),
-    BinaryenLoad(
-      module,
-      4,
-      0,
-      4,
-      0,
-      BinaryenTypeInt32(),
-      BinaryenLoad(module,
-                   4,
-                   0,
-                   0,
-                   0,
-                   BinaryenTypeInt32(),
-                   BinaryenConst(module, BinaryenLiteralInt32(4)))))};
+    BinaryenLoad(module,
+                 4,
+                 0,
+                 4,
+                 0,
+                 BinaryenTypeInt32(),
+                 BinaryenLoad(module,
+                              4,
+                              0,
+                              0,
+                              0,
+                              BinaryenTypeInt32(),
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0"))};
   BinaryenExpressionRef debugger;
   if (1)
     debugger = BinaryenCall(module, "print", args, 1, BinaryenTypeNone());
@@ -87,7 +91,9 @@ int main() {
                               0,
                               0,
                               BinaryenTypeInt32(),
-                              BinaryenConst(module, BinaryenLiteralInt32(4))));
+                              BinaryenConst(module, BinaryenLiteralInt32(4)),
+                              "0"),
+                 "0");
   BinaryenExpressionRef checkBodyList[] = {halter, incer, debugger, returner};
   BinaryenExpressionRef checkBody =
     BinaryenBlock(module,
@@ -228,7 +234,7 @@ int main() {
                             BinaryenTypeNone());
 
   // memory
-  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);
+  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0, 0, "0");
 
   // optionally, optimize
   if (0)
diff --git a/test/example/small_set.cpp b/test/example/small_set.cpp
index 48bcb6a..f1130c5 100644
--- a/test/example/small_set.cpp
+++ b/test/example/small_set.cpp
@@ -201,6 +201,60 @@ template<typename T> void testInternals() {
   }
 }
 
+// Iterate over an input object and check we have the same items, in the same
+// order, as we expect.
+template<typename T>
+void assertEqualOrdered(const T& t, const std::vector<int>& expected) {
+  size_t index = 0;
+  for (auto x : t) {
+    assert(index < expected.size());
+    assert(x == expected[index]);
+    index++;
+  }
+}
+
+template<typename T> void testOrdering() {
+  T t;
+
+  // Do some inserts at the end.
+  t.insert(1);
+  assertEqualOrdered(t, {1});
+
+  t.insert(2);
+  assertEqualOrdered(t, {1, 2});
+
+  t.insert(3);
+  assertEqualOrdered(t, {1, 2, 3});
+
+  // Erase the start.
+  t.erase(1);
+  assertEqualOrdered(t, {2, 3});
+
+  // Insert at the start.
+  t.insert(1);
+  assertEqualOrdered(t, {1, 2, 3});
+
+  // Erase the end.
+  t.erase(3);
+  assertEqualOrdered(t, {1, 2});
+
+  // Erase the end.
+  t.erase(2);
+  assertEqualOrdered(t, {1});
+
+  // Insert at the end.
+  t.insert(3);
+  assertEqualOrdered(t, {1, 3});
+
+  // Insert at the middle.
+  t.insert(2);
+  assertEqualOrdered(t, {1, 2, 3});
+
+  // Erase the middle.
+  t.erase(2);
+  assertEqualOrdered(t, {1, 3});
+}
+
 int main() {
   testAPI<SmallSet<int, 0>>();
   testAPI<SmallSet<int, 1>>();
@@ -217,5 +271,11 @@ int main() {
   testInternals<SmallSet<int, 1>>();
   testInternals<SmallUnorderedSet<int, 1>>();
 
+  testOrdering<SmallSet<int, 0>>();
+  testOrdering<SmallSet<int, 1>>();
+  testOrdering<SmallSet<int, 2>>();
+  testOrdering<SmallSet<int, 3>>();
+  testOrdering<SmallSet<int, 10>>();
+
   std::cout << "ok.\n";
 }
diff --git a/test/example/small_vector.cpp b/test/example/small_vector.cpp
index 29d9649..a1e394e 100644
--- a/test/example/small_vector.cpp
+++ b/test/example/small_vector.cpp
@@ -66,12 +66,47 @@ template<typename T> void test(size_t N) {
     t.reserve(t.capacity() + 100);
     assert(t.capacity() >= N + 100);
   }
+  {
+    // Test resizing.
+    T t;
+
+    assert(t.empty());
+    t.resize(1);
+    assert(t.size() == 1);
+    t.resize(2);
+    assert(t.size() == 2);
+    t.resize(3);
+    assert(t.size() == 3);
+    t.resize(6);
+    assert(t.size() == 6);
+
+    // Now go in reverse.
+    t.resize(6);
+    assert(t.size() == 6);
+    t.resize(3);
+    assert(t.size() == 3);
+    t.resize(2);
+    assert(t.size() == 2);
+    t.resize(1);
+    assert(t.size() == 1);
+
+    // Test a big leap from nothing (rather than gradual increase as before).
+    t.clear();
+    assert(t.empty());
+    t.resize(6);
+    assert(t.size() == 6);
+    t.resize(2);
+    assert(t.size() == 2);
+    t.clear();
+    assert(t.empty());
+  }
 }
 
 int main() {
   test<SmallVector<int, 0>>(0);
   test<SmallVector<int, 1>>(1);
   test<SmallVector<int, 2>>(2);
+  test<SmallVector<int, 3>>(3);
   test<SmallVector<int, 10>>(10);
   std::cout << "ok.\n";
 }
diff --git a/test/example/stack-utils.cpp b/test/example/stack-utils.cpp
index 8709396..f8824e5 100644
--- a/test/example/stack-utils.cpp
+++ b/test/example/stack-utils.cpp
@@ -250,6 +250,9 @@ void test_signature_composition() {
 }
 
 void test_signature_subtype() {
+  Type eqref = Type(HeapType::eq, Nullable);
+  Type anyref = Type(HeapType::any, Nullable);
+
   std::cout << ";; Test stack signature subtyping\n";
   // Differences in unreachability only
   {
@@ -260,15 +263,15 @@ void test_signature_subtype() {
   }
   // Covariance of results
   {
-    StackSignature a(Type::none, Type::funcref, StackSignature::Fixed);
-    StackSignature b(Type::none, Type::anyref, StackSignature::Fixed);
+    StackSignature a(Type::none, eqref, StackSignature::Fixed);
+    StackSignature b(Type::none, anyref, StackSignature::Fixed);
     assert(StackSignature::isSubType(a, b));
     assert(!StackSignature::isSubType(b, a));
   }
   // Contravariance of params
   {
-    StackSignature a(Type::anyref, Type::none, StackSignature::Fixed);
-    StackSignature b(Type::funcref, Type::none, StackSignature::Fixed);
+    StackSignature a(anyref, Type::none, StackSignature::Fixed);
+    StackSignature b(eqref, Type::none, StackSignature::Fixed);
     assert(StackSignature::isSubType(a, b));
     assert(!StackSignature::isSubType(b, a));
   }
@@ -355,6 +358,9 @@ void test_signature_subtype() {
 }
 
 void test_signature_lub() {
+  Type eqref = Type(HeapType::eq, Nullable);
+  Type anyref = Type(HeapType::any, Nullable);
+
   std::cout << ";; Test stack signature lub\n";
   {
     StackSignature a{Type::none, Type::none, StackSignature::Fixed};
@@ -392,30 +398,27 @@ void test_signature_lub() {
            (StackSignature{Type::i32, Type::i32, StackSignature::Polymorphic}));
   }
   {
-    StackSignature a{Type::none, Type::anyref, StackSignature::Polymorphic};
-    StackSignature b{Type::none, Type::funcref, StackSignature::Polymorphic};
+    StackSignature a{Type::none, anyref, StackSignature::Polymorphic};
+    StackSignature b{Type::none, eqref, StackSignature::Polymorphic};
     assert(StackSignature::haveLeastUpperBound(a, b));
-    assert(
-      StackSignature::getLeastUpperBound(a, b) ==
-      (StackSignature{Type::none, Type::anyref, StackSignature::Polymorphic}));
+    assert(StackSignature::getLeastUpperBound(a, b) ==
+           (StackSignature{Type::none, anyref, StackSignature::Polymorphic}));
   }
   {
-    StackSignature a{Type::anyref, Type::none, StackSignature::Polymorphic};
-    StackSignature b{Type::funcref, Type::none, StackSignature::Polymorphic};
+    StackSignature a{anyref, Type::none, StackSignature::Polymorphic};
+    StackSignature b{eqref, Type::none, StackSignature::Polymorphic};
     // assert(StackSignature::haveLeastUpperBound(a, b));
     // assert(StackSignature::getLeastUpperBound(a, b) ==
-    //        (StackSignature{Type::funcref, Type::none,
+    //        (StackSignature{eqref, Type::none,
     //        StackSignature::Polymorphic}));
   }
   {
-    StackSignature a{
-      {Type::i32, Type::funcref}, Type::funcref, StackSignature::Polymorphic};
-    StackSignature b{
-      Type::funcref, {Type::f32, Type::anyref}, StackSignature::Polymorphic};
+    StackSignature a{{Type::i32, eqref}, eqref, StackSignature::Polymorphic};
+    StackSignature b{eqref, {Type::f32, anyref}, StackSignature::Polymorphic};
     assert(StackSignature::haveLeastUpperBound(a, b));
     assert(StackSignature::getLeastUpperBound(a, b) ==
-           (StackSignature{{Type::i32, Type::funcref},
-                           {Type::f32, Type::anyref},
+           (StackSignature{{Type::i32, eqref},
+                           {Type::f32, anyref},
                            StackSignature::Polymorphic}));
   }
   // No LUB
diff --git a/test/example/type-builder-nominal.cpp b/test/example/type-builder-nominal.cpp
index 34571c4..66dccea 100644
--- a/test/example/type-builder-nominal.cpp
+++ b/test/example/type-builder-nominal.cpp
@@ -1,6 +1,7 @@
 #include <cassert>
 #include <iostream>
 
+#include "wasm-builder.h"
 #include "wasm-type-printing.h"
 #include "wasm-type.h"
 
@@ -11,7 +12,7 @@ void test_builder() {
   std::cout << ";; Test TypeBuilder\n";
 
   // (type $sig (func (param (ref $struct)) (result (ref $array) i32)))
-  // (type $struct (struct (field (ref null $array) (mut rtt 0 $array))))
+  // (type $struct (struct (field (ref null $array))))
   // (type $array (array (mut anyref)))
 
   TypeBuilder builder;
@@ -23,11 +24,10 @@ void test_builder() {
   Type refStruct = builder.getTempRefType(builder[1], NonNullable);
   Type refArray = builder.getTempRefType(builder[2], NonNullable);
   Type refNullArray = builder.getTempRefType(builder[2], Nullable);
-  Type rttArray = builder.getTempRttType(Rtt(0, builder[2]));
   Type refNullAny(HeapType::any, Nullable);
 
   Signature sig(refStruct, builder.getTempTupleType({refArray, Type::i32}));
-  Struct struct_({Field(refNullArray, Immutable), Field(rttArray, Mutable)});
+  Struct struct_({Field(refNullArray, Immutable)});
   Array array(Field(refNullAny, Mutable));
 
   {
@@ -40,7 +40,6 @@ void test_builder() {
     std::cout << "(ref $struct) => " << print(refStruct) << "\n";
     std::cout << "(ref $array) => " << print(refArray) << "\n";
     std::cout << "(ref null $array) => " << print(refNullArray) << "\n";
-    std::cout << "(rtt 0 $array) => " << print(rttArray) << "\n\n";
   }
 
   builder[0] = sig;
@@ -57,7 +56,6 @@ void test_builder() {
     std::cout << "(ref $struct) => " << print(refStruct) << "\n";
     std::cout << "(ref $array) => " << print(refArray) << "\n";
     std::cout << "(ref null $array) => " << print(refNullArray) << "\n";
-    std::cout << "(rtt 0 $array) => " << print(rttArray) << "\n\n";
   }
 
   std::vector<HeapType> built = *builder.build();
@@ -66,7 +64,6 @@ void test_builder() {
   Type newRefStruct = Type(built[1], NonNullable);
   Type newRefArray = Type(built[2], NonNullable);
   Type newRefNullArray = Type(built[2], Nullable);
-  Type newRttArray = Type(Rtt(0, built[2]));
 
   {
     IndexedTypeNameGenerator print(built);
@@ -78,7 +75,6 @@ void test_builder() {
     std::cout << "(ref $struct) => " << print(newRefStruct) << "\n";
     std::cout << "(ref $array) => " << print(newRefArray) << "\n";
     std::cout << "(ref null $array) => " << print(newRefNullArray) << "\n";
-    std::cout << "(rtt 0 $array) => " << print(newRttArray) << "\n\n";
   }
 }
 
@@ -122,21 +118,24 @@ void test_canonicalization() {
 void test_basic() {
   std::cout << ";; Test basic\n";
 
+  Type canonAnyref = Type(HeapType::any, Nullable);
+  Type canonI31ref = Type(HeapType::i31, NonNullable);
+
   TypeBuilder builder(6);
 
   Type anyref = builder.getTempRefType(builder[4], Nullable);
   Type i31ref = builder.getTempRefType(builder[5], NonNullable);
 
-  builder[0] = Signature(Type::anyref, Type::i31ref);
-  builder[1] = Signature(anyref, Type::i31ref);
-  builder[2] = Signature(Type::anyref, i31ref);
+  builder[0] = Signature(canonAnyref, canonI31ref);
+  builder[1] = Signature(anyref, canonI31ref);
+  builder[2] = Signature(canonAnyref, i31ref);
   builder[3] = Signature(anyref, i31ref);
   builder[4] = HeapType::any;
   builder[5] = HeapType::i31;
 
   std::vector<HeapType> built = *builder.build();
 
-  assert(built[0].getSignature() == Signature(Type::anyref, Type::i31ref));
+  assert(built[0].getSignature() == Signature(canonAnyref, canonI31ref));
   assert(built[1].getSignature() == built[0].getSignature());
   assert(built[2].getSignature() == built[1].getSignature());
   assert(built[3].getSignature() == built[2].getSignature());
@@ -149,16 +148,18 @@ void test_basic() {
 void test_signatures(bool warm) {
   std::cout << ";; Test canonical signatures\n";
 
+  Type canonAnyref = Type(HeapType::any, Nullable);
+  Type canonI31ref = Type(HeapType::i31, NonNullable);
+
   TypeBuilder builder(2);
   Type tempRef = builder.getTempRefType(builder[0], Nullable);
-  builder[0] = Signature(Type::i31ref, Type::anyref);
+  builder[0] = Signature(canonI31ref, canonAnyref);
   builder[1] = Signature(tempRef, tempRef);
   std::vector<HeapType> built = *builder.build();
 
-  HeapType small = Signature(Type::i31ref, Type::anyref);
-  HeapType big =
-    Signature(Type(Signature(Type::i31ref, Type::anyref), Nullable),
-              Type(Signature(Type::i31ref, Type::anyref), Nullable));
+  HeapType small = Signature(canonI31ref, canonAnyref);
+  HeapType big = Signature(Type(Signature(canonI31ref, canonAnyref), Nullable),
+                           Type(Signature(canonI31ref, canonAnyref), Nullable));
   if (warm) {
     assert(built[0] != small);
     assert(built[1] != big);
@@ -305,6 +306,9 @@ void test_recursive() {
 void test_subtypes() {
   std::cout << ";; Test subtyping\n";
 
+  Type anyref = Type(HeapType::any, Nullable);
+  Type eqref = Type(HeapType::eq, Nullable);
+
   auto LUB = [&](HeapType a, HeapType b) {
     Type refA = Type(a, Nullable);
     Type refB = Type(b, Nullable);
@@ -326,14 +330,13 @@ void test_subtypes() {
 
   {
     // Basic Types
-    for (auto other : {HeapType::func,
+    for (auto other : {HeapType::eq,
                        HeapType::any,
                        HeapType::eq,
                        HeapType::i31,
                        HeapType::data}) {
       assert(LUB(HeapType::any, other) == HeapType::any);
     }
-    assert(LUB(HeapType::eq, HeapType::func) == HeapType::any);
     assert(LUB(HeapType::i31, HeapType::data) == HeapType::eq);
   }
 
@@ -361,7 +364,7 @@ void test_subtypes() {
       Type structRef1 = builder.getTempRefType(builder[1], Nullable);
       builder[0] = Struct{};
       builder[1] = Struct{};
-      builder[2] = Signature(Type::none, Type::anyref);
+      builder[2] = Signature(Type::none, anyref);
       builder[3] = Signature(Type::none, structRef0);
       builder[4] = Signature(Type::none, structRef1);
       built = *builder.build();
@@ -393,12 +396,10 @@ void test_subtypes() {
       builder[0].subTypeOf(builder[1]);
       builder[2].subTypeOf(builder[3]);
       builder[4].subTypeOf(builder[5]);
-      builder[0] =
-        Struct({Field(Type::i32, Mutable), Field(Type::anyref, Mutable)});
-      builder[1] =
-        Struct({Field(Type::i32, Mutable), Field(Type::anyref, Mutable)});
-      builder[2] = Signature(Type::i32, Type::anyref);
-      builder[3] = Signature(Type::i32, Type::anyref);
+      builder[0] = Struct({Field(Type::i32, Mutable), Field(anyref, Mutable)});
+      builder[1] = Struct({Field(Type::i32, Mutable), Field(anyref, Mutable)});
+      builder[2] = Signature(Type::i32, anyref);
+      builder[3] = Signature(Type::i32, anyref);
       builder[4] = Array(Field(Type::i32, Mutable));
       builder[5] = Array(Field(Type::i32, Mutable));
       built = *builder.build();
@@ -427,8 +428,8 @@ void test_subtypes() {
     std::vector<HeapType> built;
     {
       TypeBuilder builder(2);
-      builder[0] = Struct({Field(Type::anyref, Immutable)});
-      builder[1] = Struct({Field(Type::funcref, Immutable)});
+      builder[0] = Struct({Field(anyref, Immutable)});
+      builder[1] = Struct({Field(eqref, Immutable)});
       builder[1].subTypeOf(builder[0]);
       built = *builder.build();
     }
diff --git a/test/example/type-builder-nominal.txt b/test/example/type-builder-nominal.txt
index 745f63d..8d146cc 100644
--- a/test/example/type-builder-nominal.txt
+++ b/test/example/type-builder-nominal.txt
@@ -7,28 +7,22 @@ $array => (; temp ;) (func_subtype func)
 (ref $struct) => (; temp ;) (ref $1)
 (ref $array) => (; temp ;) (ref $2)
 (ref null $array) => (; temp ;) (ref null $2)
-(rtt 0 $array) => (; temp ;) (rtt 0 $2)
-
 After setting heap types:
 $sig => (; temp ;) (func_subtype (param (; temp ;) (ref $1)) (result (; temp ;) (ref $2) i32) func)
-$struct => (; temp ;) (struct_subtype (field (; temp ;) (ref null $2) (mut (; temp ;) (rtt 0 $2))) data)
+$struct => (; temp ;) (struct_subtype (field (; temp ;) (ref null $2)) data)
 $array => (; temp ;) (array_subtype (mut anyref) data)
 (ref $sig) => (; temp ;) (ref $0)
 (ref $struct) => (; temp ;) (ref $1)
 (ref $array) => (; temp ;) (ref $2)
 (ref null $array) => (; temp ;) (ref null $2)
-(rtt 0 $array) => (; temp ;) (rtt 0 $2)
-
 After building types:
 $sig => (func_subtype (param (ref $1)) (result (ref $2) i32) func)
-$struct => (struct_subtype (field (ref null $2) (mut (rtt 0 $2))) data)
+$struct => (struct_subtype (field (ref null $2)) data)
 $array => (array_subtype (mut anyref) data)
 (ref $sig) => (ref $0)
 (ref $struct) => (ref $1)
 (ref $array) => (ref $2)
 (ref null $array) => (ref null $2)
-(rtt 0 $array) => (rtt 0 $2)
-
 ;; Test canonicalization
 ;; Test basic
 ;; Test canonical signatures
@@ -64,28 +58,22 @@ $array => (; temp ;) (func_subtype func)
 (ref $struct) => (; temp ;) (ref $1)
 (ref $array) => (; temp ;) (ref $2)
 (ref null $array) => (; temp ;) (ref null $2)
-(rtt 0 $array) => (; temp ;) (rtt 0 $2)
-
 After setting heap types:
 $sig => (; temp ;) (func_subtype (param (; temp ;) (ref $1)) (result (; temp ;) (ref $2) i32) func)
-$struct => (; temp ;) (struct_subtype (field (; temp ;) (ref null $2) (mut (; temp ;) (rtt 0 $2))) data)
+$struct => (; temp ;) (struct_subtype (field (; temp ;) (ref null $2)) data)
 $array => (; temp ;) (array_subtype (mut anyref) data)
 (ref $sig) => (; temp ;) (ref $0)
 (ref $struct) => (; temp ;) (ref $1)
 (ref $array) => (; temp ;) (ref $2)
 (ref null $array) => (; temp ;) (ref null $2)
-(rtt 0 $array) => (; temp ;) (rtt 0 $2)
-
 After building types:
 $sig => (func_subtype (param (ref $1)) (result (ref $2) i32) func)
-$struct => (struct_subtype (field (ref null $2) (mut (rtt 0 $2))) data)
+$struct => (struct_subtype (field (ref null $2)) data)
 $array => (array_subtype (mut anyref) data)
 (ref $sig) => (ref $0)
 (ref $struct) => (ref $1)
 (ref $array) => (ref $2)
 (ref null $array) => (ref null $2)
-(rtt 0 $array) => (rtt 0 $2)
-
 ;; Test canonicalization
 ;; Test basic
 ;; Test canonical signatures
diff --git a/test/example/type-builder.cpp b/test/example/type-builder.cpp
index ce7b748..7da2f8f 100644
--- a/test/example/type-builder.cpp
+++ b/test/example/type-builder.cpp
@@ -44,21 +44,24 @@ void test_canonicalization() {
 void test_basic() {
   std::cout << ";; Test basic\n";
 
+  Type canonAnyref = Type(HeapType::any, Nullable);
+  Type canonI31ref = Type(HeapType::i31, NonNullable);
+
   TypeBuilder builder(6);
 
   Type anyref = builder.getTempRefType(builder[4], Nullable);
   Type i31ref = builder.getTempRefType(builder[5], NonNullable);
 
-  builder[0] = Signature(Type::anyref, Type::i31ref);
-  builder[1] = Signature(anyref, Type::i31ref);
-  builder[2] = Signature(Type::anyref, i31ref);
+  builder[0] = Signature(canonAnyref, canonI31ref);
+  builder[1] = Signature(anyref, canonI31ref);
+  builder[2] = Signature(canonAnyref, i31ref);
   builder[3] = Signature(anyref, i31ref);
   builder[4] = HeapType::any;
   builder[5] = HeapType::i31;
 
   std::vector<HeapType> built = *builder.build();
 
-  assert(built[0] == Signature(Type::anyref, Type::i31ref));
+  assert(built[0] == Signature(canonAnyref, canonI31ref));
   assert(built[1] == built[0]);
   assert(built[2] == built[1]);
   assert(built[3] == built[2]);
@@ -210,8 +213,8 @@ void test_recursive() {
     std::cout << print(built[1]) << "\n\n";
     assert(built[0].getSignature().results.getHeapType() == built[0]);
     assert(built[1].getSignature().results.getHeapType() == built[0]);
-    assert(built[0].getSignature().params == Type::anyref);
-    assert(built[1].getSignature().params == Type::anyref);
+    assert(built[0].getSignature().params == Type(HeapType::any, Nullable));
+    assert(built[1].getSignature().params == Type(HeapType::any, Nullable));
     assert(built[0] == built[1]);
     assert(built[2] == HeapType::any);
   }
@@ -220,6 +223,13 @@ void test_recursive() {
 void test_lub() {
   std::cout << ";; Test LUBs\n";
 
+  Type ext = Type(HeapType::ext, Nullable);
+  Type func = Type(HeapType::func, Nullable);
+  Type any = Type(HeapType::any, Nullable);
+  Type eq = Type(HeapType::eq, Nullable);
+  Type i31 = Type(HeapType::i31, Nullable);
+  Type data = Type(HeapType::data, Nullable);
+
   auto LUB = [&](Type a, Type b) {
     Type lubAB = Type::getLeastUpperBound(a, b);
     Type lubBA = Type::getLeastUpperBound(b, a);
@@ -238,15 +248,12 @@ void test_lub() {
 
   {
     // Basic Types
-    for (auto other : {Type::funcref,
-                       Type::anyref,
-                       Type::eqref,
-                       Type::i31ref,
-                       Type::dataref}) {
-      assert(LUB(Type::anyref, other) == Type::anyref);
+    for (auto other : {any, eq, i31, data}) {
+      assert(LUB(any, other) == any);
+      assert(LUB(func, other) == Type::none);
+      assert(LUB(ext, other) == Type::none);
     }
-    assert(LUB(Type::eqref, Type::funcref) == Type::anyref);
-    assert(LUB(Type::i31ref, Type::dataref) == Type(HeapType::eq, NonNullable));
+    assert(LUB(i31, data) == eq);
   }
 
   {
@@ -260,22 +267,21 @@ void test_lub() {
 
   {
     // Funcref with specific signature
-    assert(LUB(Type::funcref, Type(Signature(), Nullable)) == Type::funcref);
+    assert(LUB(func, Type(Signature(), Nullable)) == func);
   }
 
   {
     // Incompatible signatures
-    Type a(Signature(Type::none, Type::anyref), Nullable);
-    Type b(Signature(Type::anyref, Type::none), Nullable);
-    assert(LUB(a, b) == Type::funcref);
+    Type a(Signature(Type::none, any), Nullable);
+    Type b(Signature(any, Type::none), Nullable);
+    assert(LUB(a, b) == Type(HeapType::func, Nullable));
   }
 
   {
     // Signatures incompatible in tuple size
-    Type a(Signature(Type::none, {Type::anyref, Type::anyref}), Nullable);
-    Type b(Signature(Type::none, {Type::anyref, Type::anyref, Type::anyref}),
-           Nullable);
-    assert(LUB(a, b) == Type::funcref);
+    Type a(Signature(Type::none, {any, any}), Nullable);
+    Type b(Signature(Type::none, {any, any, any}), Nullable);
+    assert(LUB(a, b) == Type(HeapType::func, Nullable));
   }
 
   // {
@@ -303,24 +309,24 @@ void test_lub() {
 
   {
     // Mutable fields are invariant
-    Type a(Array(Field(Type::eqref, Mutable)), Nullable);
-    Type b(Array(Field(Type::funcref, Mutable)), Nullable);
-    assert(LUB(a, b) == Type(HeapType::data, Nullable));
+    Type a(Array(Field(eq, Mutable)), Nullable);
+    Type b(Array(Field(func, Mutable)), Nullable);
+    assert(LUB(a, b) == data);
   }
 
   {
     // Immutable fields are covariant
-    Type a(Array(Field(Type::eqref, Immutable)), Nullable);
-    Type b(Array(Field(Type::funcref, Immutable)), Nullable);
-    Type lub(Array(Field(Type::anyref, Immutable)), Nullable);
+    Type a(Array(Field(data, Immutable)), Nullable);
+    Type b(Array(Field(i31, Immutable)), Nullable);
+    Type lub(Array(Field(eq, Immutable)), Nullable);
     assert(LUB(a, b) == lub);
   }
 
   {
     // Depth subtyping
-    Type a(Struct({Field(Type::eqref, Immutable)}), Nullable);
-    Type b(Struct({Field(Type::funcref, Immutable)}), Nullable);
-    Type lub(Struct({Field(Type::anyref, Immutable)}), Nullable);
+    Type a(Struct({Field(data, Immutable)}), Nullable);
+    Type b(Struct({Field(i31, Immutable)}), Nullable);
+    Type lub(Struct({Field(eq, Immutable)}), Nullable);
     assert(LUB(a, b) == lub);
   }
 
@@ -344,32 +350,29 @@ void test_lub() {
 
   {
     // Width and depth subtyping with different suffixes
-    Type a(Struct({Field(Type::eqref, Immutable), Field(Type::i64, Immutable)}),
+    Type a(Struct({Field(data, Immutable), Field(Type::i64, Immutable)}),
            Nullable);
-    Type b(
-      Struct({Field(Type::funcref, Immutable), Field(Type::f32, Immutable)}),
-      Nullable);
-    Type lub(Struct({Field(Type::anyref, Immutable)}), Nullable);
+    Type b(Struct({Field(i31, Immutable), Field(Type::f32, Immutable)}),
+           Nullable);
+    Type lub(Struct({Field(eq, Immutable)}), Nullable);
     assert(LUB(a, b) == lub);
   }
 
   {
     // No common prefix
-    Type a(
-      Struct({Field(Type::i32, Immutable), Field(Type::anyref, Immutable)}),
-      Nullable);
-    Type b(
-      Struct({Field(Type::f32, Immutable), Field(Type::anyref, Immutable)}),
-      Nullable);
+    Type a(Struct({Field(Type::i32, Immutable), Field(any, Immutable)}),
+           Nullable);
+    Type b(Struct({Field(Type::f32, Immutable), Field(any, Immutable)}),
+           Nullable);
     Type lub(Struct(), Nullable);
     assert(LUB(a, b) == lub);
   }
 
   {
     // Nested structs
-    Type innerA(Struct({Field(Type::eqref, Immutable)}), Nullable);
-    Type innerB(Struct({Field(Type::funcref, Immutable)}), Nullable);
-    Type innerLub(Struct({Field(Type::anyref, Immutable)}), Nullable);
+    Type innerA(Struct({Field(data, Immutable)}), Nullable);
+    Type innerB(Struct({Field(i31, Immutable)}), Nullable);
+    Type innerLub(Struct({Field(eq, Immutable)}), Nullable);
     Type a(Struct({Field(innerA, Immutable)}), Nullable);
     Type b(Struct({Field(innerB, Immutable)}), Nullable);
     Type lub(Struct({Field(innerLub, Immutable)}), Nullable);
@@ -381,57 +384,20 @@ void test_lub() {
     TypeBuilder builder(2);
     Type tempA = builder.getTempRefType(builder[0], Nullable);
     Type tempB = builder.getTempRefType(builder[1], Nullable);
-    builder[0] =
-      Struct({Field(tempB, Immutable), Field(Type::eqref, Immutable)});
-    builder[1] =
-      Struct({Field(tempA, Immutable), Field(Type::funcref, Immutable)});
+    builder[0] = Struct({Field(tempB, Immutable), Field(data, Immutable)});
+    builder[1] = Struct({Field(tempA, Immutable), Field(i31, Immutable)});
     auto built = *builder.build();
     Type a(built[0], Nullable);
     Type b(built[1], Nullable);
 
     TypeBuilder lubBuilder(1);
     Type tempLub = builder.getTempRefType(lubBuilder[0], Nullable);
-    lubBuilder[0] =
-      Struct({Field(tempLub, Immutable), Field(Type::anyref, Immutable)});
+    lubBuilder[0] = Struct({Field(tempLub, Immutable), Field(eq, Immutable)});
     built = *lubBuilder.build();
     Type lub(built[0], Nullable);
 
     assert(LUB(a, b) == lub);
   }
-
-  {
-    // Incompatible Rtts
-    Type a{Rtt(HeapType::eq)};
-    Type b{Rtt(HeapType::func)};
-    assert(LUB(a, b) == Type::none);
-  }
-
-  {
-    // Rtts with matching depth
-    Type a(Rtt(42, HeapType::any));
-    assert(LUB(a, a) == a);
-  }
-
-  {
-    // Rtts with mismatched depth
-    Type a(Rtt(42, HeapType::any));
-    Type b(Rtt(50, HeapType::any));
-    Type lub{Rtt(HeapType::any)};
-    assert(LUB(a, b) == lub);
-  }
-
-  {
-    // Rtts with and without depth
-    Type a(Rtt(42, HeapType::any));
-    Type b{Rtt(HeapType::any)};
-    assert(LUB(a, b) == b);
-  }
-
-  {
-    // Rtts without depth
-    Type a{Rtt(HeapType::any)};
-    assert(LUB(a, a) == a);
-  }
 }
 
 int main() {
diff --git a/test/example/typeinfo.cpp b/test/example/typeinfo.cpp
index ed5637b..ff20753 100644
--- a/test/example/typeinfo.cpp
+++ b/test/example/typeinfo.cpp
@@ -8,7 +8,6 @@ using namespace wasm;
 void test_compound() {
   {
     HeapType func(HeapType::func);
-    assert(Type(func, Nullable).getID() == Type::funcref);
     assert(Type(func, NonNullable).getID() == Type(func, NonNullable).getID());
     assert(Type(func, NonNullable).getID() != Type(func, Nullable).getID());
     HeapType sameFunc(HeapType::func);
@@ -16,7 +15,6 @@ void test_compound() {
            Type(sameFunc, NonNullable).getID());
 
     HeapType any(HeapType::any);
-    assert(Type(any, Nullable).getID() == Type::anyref);
     assert(Type(any, NonNullable).getID() == Type(any, NonNullable).getID());
     assert(Type(any, NonNullable).getID() != Type(any, Nullable).getID());
     HeapType sameAny(HeapType::any);
@@ -24,14 +22,12 @@ void test_compound() {
            Type(sameAny, NonNullable).getID());
 
     HeapType eq(HeapType::eq);
-    // assert(Type(eq, Nullable).getID() == Type::eqref);
     assert(Type(eq, NonNullable).getID() == Type(eq, NonNullable).getID());
     assert(Type(eq, NonNullable).getID() != Type(eq, Nullable).getID());
     HeapType sameEq(HeapType::eq);
     assert(Type(eq, NonNullable).getID() == Type(sameEq, NonNullable).getID());
 
     HeapType i31(HeapType::i31);
-    // assert(Type(i31, NonNullable).getID() == Type::i31ref);
     assert(Type(i31, NonNullable).getID() == Type(i31, NonNullable).getID());
     assert(Type(i31, NonNullable).getID() != Type(i31, Nullable).getID());
     HeapType sameI31(HeapType::i31);
@@ -92,36 +88,9 @@ void test_compound() {
     Tuple sameTuple({Type::i32, Type::f64});
     assert(Type(tuple).getID() == Type(sameTuple).getID());
 
-    Tuple otherTuple({Type::f64, Type::anyref});
+    Tuple otherTuple({Type::f64, Type::i64});
     assert(Type(tuple).getID() != Type(otherTuple).getID());
   }
-  {
-    Rtt rtt(0, HeapType::func);
-    assert(Type(rtt).getID() == Type(rtt).getID());
-
-    Rtt sameRtt(0, HeapType::func);
-    assert(rtt == sameRtt);
-    assert(Type(rtt).getID() == Type(sameRtt).getID());
-
-    Rtt otherDepthRtt(1, HeapType::func);
-    assert(rtt != otherDepthRtt);
-    assert(Type(rtt).getID() != Type(otherDepthRtt).getID());
-
-    Rtt otherHeapTypeRtt(0, HeapType::any);
-    assert(rtt != otherHeapTypeRtt);
-    assert(Type(rtt).getID() != Type(otherHeapTypeRtt).getID());
-
-    Rtt structRtt(0, Struct{});
-    assert(Type(structRtt).getID() == Type(structRtt).getID());
-
-    Rtt sameStructRtt(0, Struct{});
-    assert(structRtt == sameStructRtt);
-    assert(Type(structRtt).getID() == Type(sameStructRtt).getID());
-
-    Rtt otherStructRtt(0, Struct({{Type::i32, Immutable}}));
-    assert(structRtt != otherStructRtt);
-    assert(Type(structRtt).getID() != Type(otherStructRtt).getID());
-  }
 }
 
 void test_printing() {
@@ -165,7 +134,6 @@ void test_printing() {
       {Type::i64, Immutable},
       {Type::f32, Mutable},
       {Type::f64, Mutable},
-      {Type::anyref, Immutable},
     });
     std::cout << struct_ << "\n";
     std::cout << Type(struct_, NonNullable) << "\n";
@@ -177,7 +145,7 @@ void test_printing() {
     std::cout << array << "\n";
     std::cout << Type(array, NonNullable) << "\n";
     std::cout << Type(array, Nullable) << "\n";
-    Array arrayMut({Type::anyref, Mutable});
+    Array arrayMut({Type::i64, Mutable});
     std::cout << arrayMut << "\n";
     std::cout << Type(arrayMut, NonNullable) << "\n";
     std::cout << Type(arrayMut, Nullable) << "\n";
@@ -190,31 +158,10 @@ void test_printing() {
     Tuple tuple({
       Type::i32,
       Type::f64,
-      Type::anyref,
     });
     std::cout << tuple << "\n";
     std::cout << Type(tuple) << "\n";
   }
-  {
-    std::cout << "\n;; Rtt\n";
-    std::cout << Rtt(0, HeapType::func) << "\n";
-    std::cout << Type(Rtt(0, HeapType::func)) << "\n";
-    std::cout << Rtt(2, HeapType::any) << "\n";
-    std::cout << Type(Rtt(2, HeapType::any)) << "\n";
-    std::cout << Rtt(3, HeapType::eq) << "\n";
-    std::cout << Type(Rtt(3, HeapType::eq)) << "\n";
-    std::cout << Rtt(4, HeapType::i31) << "\n";
-    std::cout << Type(Rtt(4, HeapType::i31)) << "\n";
-    Rtt signatureRtt(6, Signature(Type::none, Type::none));
-    std::cout << signatureRtt << "\n";
-    std::cout << Type(signatureRtt) << "\n";
-    Rtt structRtt(7, Struct{});
-    std::cout << structRtt << "\n";
-    std::cout << Type(structRtt) << "\n";
-    Rtt arrayRtt(8, Array({Type::i32, Immutable}));
-    std::cout << arrayRtt << "\n";
-    std::cout << Type(arrayRtt) << "\n";
-  }
   {
     std::cout << "\n;; Signature of references (param/result)\n";
     Signature signature(Type(Struct{}, Nullable),
diff --git a/test/example/typeinfo.txt b/test/example/typeinfo.txt
index 924fcb3..7015e6c 100644
--- a/test/example/typeinfo.txt
+++ b/test/example/typeinfo.txt
@@ -9,8 +9,8 @@ eq
 eqref
 (ref eq)
 i31
-(ref null i31)
 i31ref
+(ref i31)
 (func)
 (struct)
 (array i32)
@@ -27,7 +27,7 @@ i31ref
 (struct)
 (ref $struct.0)
 (ref null $struct.0)
-(struct (field i32 i64 (mut f32) (mut f64) anyref))
+(struct (field i32 i64 (mut f32) (mut f64)))
 (ref $struct.0)
 (ref null $struct.0)
 
@@ -35,31 +35,15 @@ i31ref
 (array i32)
 (ref $array.0)
 (ref null $array.0)
-(array (mut anyref))
+(array (mut i64))
 (ref $array.0)
 (ref null $array.0)
 
 ;; Tuple
 ()
 none
-(i32 f64 anyref)
-(i32 f64 anyref)
-
-;; Rtt
-(rtt 0 func)
-(rtt 0 func)
-(rtt 2 any)
-(rtt 2 any)
-(rtt 3 eq)
-(rtt 3 eq)
-(rtt 4 i31)
-(rtt 4 i31)
-(rtt 6 $func.0)
-(rtt 6 $func.0)
-(rtt 7 $struct.0)
-(rtt 7 $struct.0)
-(rtt 8 $array.0)
-(rtt 8 $array.0)
+(i32 f64)
+(i32 f64)
 
 ;; Signature of references (param/result)
 (func (param (ref null $struct.0)) (result (ref $array.0)))
diff --git a/test/exception-handling.wast b/test/exception-handling.wast
index 1db3566..d37cc32 100644
--- a/test/exception-handling.wast
+++ b/test/exception-handling.wast
@@ -2,7 +2,7 @@
   (tag $e-i32 (param i32))
   (tag $e-i64 (param i64))
   (tag $e-i32-i64 (param i32 i64))
-  (tag $e-anyref (param anyref))
+  (tag $e-eqref (param (ref null eq)))
   (tag $e-empty)
 
   (func $foo)
@@ -330,9 +330,9 @@
 
     (try
       (do)
-      (catch $e-anyref
+      (catch $e-eqref
         (drop
-          (pop funcref) ;; pop can be subtype
+          (pop anyref) ;; pop can be supertype
         )
       )
     )
@@ -352,4 +352,17 @@
       )
     )
   )
+
+  ;; When 'delegate' is next to a nested block, make sure its delegate argument
+  ;; is parsed correctly.
+  (func $nested-block-and-try
+    (block $l0
+      (block $l1)
+      (try
+        (do)
+        (delegate 1) ;; to caller
+      )
+    )
+    (nop)
+  )
 )
diff --git a/test/exception-handling.wast.from-wast b/test/exception-handling.wast.from-wast
index c53f602..b5926ca 100644
--- a/test/exception-handling.wast.from-wast
+++ b/test/exception-handling.wast.from-wast
@@ -3,11 +3,11 @@
  (type $i32_=>_none (func (param i32)))
  (type $i64_=>_none (func (param i64)))
  (type $i32_i64_=>_none (func (param i32 i64)))
- (type $anyref_=>_none (func (param anyref)))
+ (type $eqref_=>_none (func (param eqref)))
  (tag $e-i32 (param i32))
  (tag $e-i64 (param i64))
  (tag $e-i32-i64 (param i32 i64))
- (tag $e-anyref (param anyref))
+ (tag $e-eqref (param eqref))
  (tag $e-empty (param))
  (func $foo
   (nop)
@@ -372,9 +372,9 @@
    (do
     (nop)
    )
-   (catch $e-anyref
+   (catch $e-eqref
     (drop
-     (pop funcref)
+     (pop anyref)
     )
    )
   )
@@ -393,4 +393,17 @@
    )
   )
  )
+ (func $nested-block-and-try
+  (block $l0
+   (block $l1
+   )
+   (try $try
+    (do
+     (nop)
+    )
+    (delegate 1)
+   )
+  )
+  (nop)
+ )
 )
diff --git a/test/exception-handling.wast.fromBinary b/test/exception-handling.wast.fromBinary
index 29231a9..73a4ae9 100644
--- a/test/exception-handling.wast.fromBinary
+++ b/test/exception-handling.wast.fromBinary
@@ -3,12 +3,12 @@
  (type $i32_=>_none (func (param i32)))
  (type $i64_=>_none (func (param i64)))
  (type $i32_i64_=>_none (func (param i32 i64)))
- (type $anyref_=>_none (func (param anyref)))
- (tag $tag$0 (param i32))
- (tag $tag$1 (param i64))
- (tag $tag$2 (param i32 i64))
- (tag $tag$3 (param anyref))
- (tag $tag$4 (param))
+ (type $eqref_=>_none (func (param eqref)))
+ (tag $e-i32 (param i32))
+ (tag $e-i64 (param i64))
+ (tag $e-i32-i64 (param i32 i64))
+ (tag $e-eqref (param eqref))
+ (tag $e-empty (param))
  (func $foo
   (nop)
  )
@@ -23,11 +23,11 @@
   (local $4 i32)
   (try $label$3
    (do
-    (throw $tag$0
+    (throw $e-i32
      (i32.const 0)
     )
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
@@ -35,12 +35,12 @@
   )
   (try $label$6
    (do
-    (throw $tag$2
+    (throw $e-i32-i64
      (i32.const 0)
      (i64.const 0)
     )
    )
-   (catch $tag$2
+   (catch $e-i32-i64
     (local.set $2
      (pop i32 i64)
     )
@@ -77,7 +77,7 @@
     (do
      (br $label$7)
     )
-    (catch $tag$0
+    (catch $e-i32
      (drop
       (pop i32)
      )
@@ -89,7 +89,7 @@
    (do
     (nop)
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
@@ -100,7 +100,7 @@
     (call $foo)
     (call $bar)
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
@@ -110,16 +110,16 @@
   )
   (try $label$19
    (do
-    (throw $tag$0
+    (throw $e-i32
      (i32.const 0)
     )
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
    )
-   (catch $tag$1
+   (catch $e-i64
     (drop
      (pop i64)
     )
@@ -127,7 +127,7 @@
   )
   (try $label$22
    (do
-    (throw $tag$0
+    (throw $e-i32
      (i32.const 0)
     )
    )
@@ -137,16 +137,16 @@
   )
   (try $label$25
    (do
-    (throw $tag$0
+    (throw $e-i32
      (i32.const 0)
     )
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
    )
-   (catch $tag$1
+   (catch $e-i64
     (drop
      (pop i64)
     )
@@ -160,11 +160,11 @@
    (do
     (try $label$29
      (do
-      (throw $tag$0
+      (throw $e-i32
        (i32.const 0)
       )
      )
-     (catch $tag$0
+     (catch $e-i32
       (drop
        (pop i32)
       )
@@ -174,7 +174,7 @@
      )
     )
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
@@ -182,11 +182,11 @@
    (catch_all
     (try $label$33
      (do
-      (throw $tag$0
+      (throw $e-i32
        (i32.const 0)
       )
      )
-     (catch $tag$0
+     (catch $e-i32
       (drop
        (pop i32)
       )
@@ -199,7 +199,7 @@
   )
   (try $label$37
    (do
-    (throw $tag$0
+    (throw $e-i32
      (i32.const 0)
     )
    )
@@ -271,7 +271,7 @@
    (do
     (nop)
    )
-   (catch $tag$4
+   (catch $e-empty
     (nop)
    )
   )
@@ -281,7 +281,7 @@
    (do
     (call $foo)
    )
-   (catch $tag$0
+   (catch $e-i32
     (drop
      (pop i32)
     )
@@ -296,7 +296,7 @@
     (do
      (call $foo)
     )
-    (catch $tag$0
+    (catch $e-i32
      (drop
       (pop i32)
      )
@@ -316,7 +316,7 @@
      (do
       (call $foo)
      )
-     (catch $tag$0
+     (catch $e-i32
       (drop
        (pop i32)
       )
@@ -337,7 +337,7 @@
      (do
       (call $foo)
      )
-     (catch $tag$0
+     (catch $e-i32
       (drop
        (pop i32)
       )
@@ -387,8 +387,8 @@
    (do
     (nop)
    )
-   (catch $tag$0
-    (throw $tag$0
+   (catch $e-i32
+    (throw $e-i32
      (if (result i32)
       (pop i32)
       (i32.const 0)
@@ -401,9 +401,9 @@
    (do
     (nop)
    )
-   (catch $tag$3
+   (catch $e-eqref
     (drop
-     (pop anyref)
+     (pop eqref)
     )
    )
   )
@@ -414,7 +414,7 @@
     (block $label$1
      (try $label$4
       (do
-       (throw $tag$0
+       (throw $e-i32
         (i32.const 0)
        )
       )
@@ -424,5 +424,18 @@
    )
   )
  )
+ (func $nested-block-and-try
+  (block $label$1
+   (block $label$2
+   )
+   (try $label$5
+    (do
+     (nop)
+    )
+    (delegate 1)
+   )
+  )
+  (nop)
+ )
 )
 
diff --git a/test/exception-handling.wast.fromBinary.noDebugInfo b/test/exception-handling.wast.fromBinary.noDebugInfo
index 16ab587..c3f2a83 100644
--- a/test/exception-handling.wast.fromBinary.noDebugInfo
+++ b/test/exception-handling.wast.fromBinary.noDebugInfo
@@ -3,11 +3,11 @@
  (type $i32_=>_none (func (param i32)))
  (type $i64_=>_none (func (param i64)))
  (type $i32_i64_=>_none (func (param i32 i64)))
- (type $anyref_=>_none (func (param anyref)))
+ (type $eqref_=>_none (func (param eqref)))
  (tag $tag$0 (param i32))
  (tag $tag$1 (param i64))
  (tag $tag$2 (param i32 i64))
- (tag $tag$3 (param anyref))
+ (tag $tag$3 (param eqref))
  (tag $tag$4 (param))
  (func $0
   (nop)
@@ -403,7 +403,7 @@
    )
    (catch $tag$3
     (drop
-     (pop anyref)
+     (pop eqref)
     )
    )
   )
@@ -424,5 +424,18 @@
    )
   )
  )
+ (func $7
+  (block $label$1
+   (block $label$2
+   )
+   (try $label$5
+    (do
+     (nop)
+    )
+    (delegate 1)
+   )
+  )
+  (nop)
+ )
 )
 
diff --git a/test/gc.wast.from-wast b/test/gc.wast.from-wast
index 91cd83e..a518cef 100644
--- a/test/gc.wast.from-wast
+++ b/test/gc.wast.from-wast
@@ -1,12 +1,12 @@
 (module
  (type $i31ref_dataref_=>_none (func (param i31ref dataref)))
- (type $ref?|i31|_i31ref_ref?|data|_dataref_=>_none (func (param (ref null i31) i31ref (ref null data) dataref)))
- (global $global_anyref (mut anyref) (ref.null any))
- (global $global_eqref (mut eqref) (ref.null eq))
+ (type $i31ref_ref|i31|_dataref_ref|data|_=>_none (func (param i31ref (ref i31) dataref (ref data))))
+ (global $global_anyref (mut anyref) (ref.null none))
+ (global $global_eqref (mut eqref) (ref.null none))
  (global $global_i31ref (mut i31ref) (i31.new
   (i32.const 0)
  ))
- (global $global_anyref2 (mut anyref) (ref.null eq))
+ (global $global_anyref2 (mut anyref) (ref.null none))
  (global $global_anyref3 (mut anyref) (i31.new
   (i32.const 0)
  ))
@@ -24,7 +24,7 @@
    (global.get $global_anyref)
   )
   (local.set $local_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (local.set $local_eqref
    (local.get $local_eqref)
@@ -33,7 +33,7 @@
    (global.get $global_eqref)
   )
   (local.set $local_eqref
-   (ref.null eq)
+   (ref.null none)
   )
   (local.set $local_i31ref
    (local.get $local_i31ref)
@@ -53,7 +53,7 @@
    (global.get $global_eqref)
   )
   (local.set $local_anyref
-   (ref.null eq)
+   (ref.null none)
   )
   (local.set $local_anyref
    (local.get $local_i31ref)
@@ -84,7 +84,7 @@
    (global.get $global_anyref)
   )
   (global.set $global_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (global.set $global_eqref
    (local.get $local_eqref)
@@ -93,7 +93,7 @@
    (global.get $global_eqref)
   )
   (global.set $global_eqref
-   (ref.null eq)
+   (ref.null none)
   )
   (global.set $global_i31ref
    (local.get $local_i31ref)
@@ -113,7 +113,7 @@
    (global.get $global_eqref)
   )
   (global.set $global_anyref
-   (ref.null eq)
+   (ref.null none)
   )
   (global.set $global_anyref
    (local.get $local_i31ref)
@@ -148,7 +148,7 @@
    )
   )
  )
- (func $test-variants (param $local_i31refnull (ref null i31)) (param $local_i31refnonnull i31ref) (param $local_datarefnull (ref null data)) (param $local_datarefnonnull dataref)
+ (func $test-variants (param $local_i31refnull i31ref) (param $local_i31refnonnull (ref i31)) (param $local_datarefnull dataref) (param $local_datarefnonnull (ref data))
   (nop)
  )
 )
diff --git a/test/gc.wast.fromBinary b/test/gc.wast.fromBinary
index 3794a01..72c1508 100644
--- a/test/gc.wast.fromBinary
+++ b/test/gc.wast.fromBinary
@@ -1,12 +1,12 @@
 (module
  (type $i31ref_dataref_=>_none (func (param i31ref dataref)))
- (type $ref?|i31|_i31ref_ref?|data|_dataref_=>_none (func (param (ref null i31) i31ref (ref null data) dataref)))
- (global $global_anyref (mut anyref) (ref.null any))
- (global $global_eqref (mut eqref) (ref.null eq))
+ (type $i31ref_ref|i31|_dataref_ref|data|_=>_none (func (param i31ref (ref i31) dataref (ref data))))
+ (global $global_anyref (mut anyref) (ref.null none))
+ (global $global_eqref (mut eqref) (ref.null none))
  (global $global_i31ref (mut i31ref) (i31.new
   (i32.const 0)
  ))
- (global $global_anyref2 (mut anyref) (ref.null eq))
+ (global $global_anyref2 (mut anyref) (ref.null none))
  (global $global_anyref3 (mut anyref) (i31.new
   (i32.const 0)
  ))
@@ -24,7 +24,7 @@
    (global.get $global_anyref)
   )
   (local.set $local_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (local.set $local_eqref
    (local.get $local_eqref)
@@ -33,7 +33,7 @@
    (global.get $global_eqref)
   )
   (local.set $local_eqref
-   (ref.null eq)
+   (ref.null none)
   )
   (local.set $local_i31ref
    (local.get $local_i31ref)
@@ -53,7 +53,7 @@
    (global.get $global_eqref)
   )
   (local.set $local_anyref
-   (ref.null eq)
+   (ref.null none)
   )
   (local.set $local_anyref
    (local.get $local_i31ref)
@@ -84,7 +84,7 @@
    (global.get $global_anyref)
   )
   (global.set $global_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (global.set $global_eqref
    (local.get $local_eqref)
@@ -93,7 +93,7 @@
    (global.get $global_eqref)
   )
   (global.set $global_eqref
-   (ref.null eq)
+   (ref.null none)
   )
   (global.set $global_i31ref
    (local.get $local_i31ref)
@@ -113,7 +113,7 @@
    (global.get $global_eqref)
   )
   (global.set $global_anyref
-   (ref.null eq)
+   (ref.null none)
   )
   (global.set $global_anyref
    (local.get $local_i31ref)
@@ -148,7 +148,7 @@
    )
   )
  )
- (func $test-variants (param $local_i31refnull (ref null i31)) (param $local_i31refnonnull i31ref) (param $local_datarefnull (ref null data)) (param $local_datarefnonnull dataref)
+ (func $test-variants (param $local_i31refnull i31ref) (param $local_i31refnonnull (ref i31)) (param $local_datarefnull dataref) (param $local_datarefnonnull (ref data))
   (nop)
  )
 )
diff --git a/test/gc.wast.fromBinary.noDebugInfo b/test/gc.wast.fromBinary.noDebugInfo
index a523e58..6143768 100644
--- a/test/gc.wast.fromBinary.noDebugInfo
+++ b/test/gc.wast.fromBinary.noDebugInfo
@@ -1,12 +1,12 @@
 (module
  (type $i31ref_dataref_=>_none (func (param i31ref dataref)))
- (type $ref?|i31|_i31ref_ref?|data|_dataref_=>_none (func (param (ref null i31) i31ref (ref null data) dataref)))
- (global $global$0 (mut anyref) (ref.null any))
- (global $global$1 (mut eqref) (ref.null eq))
+ (type $i31ref_ref|i31|_dataref_ref|data|_=>_none (func (param i31ref (ref i31) dataref (ref data))))
+ (global $global$0 (mut anyref) (ref.null none))
+ (global $global$1 (mut eqref) (ref.null none))
  (global $global$2 (mut i31ref) (i31.new
   (i32.const 0)
  ))
- (global $global$3 (mut anyref) (ref.null eq))
+ (global $global$3 (mut anyref) (ref.null none))
  (global $global$4 (mut anyref) (i31.new
   (i32.const 0)
  ))
@@ -24,7 +24,7 @@
    (global.get $global$0)
   )
   (local.set $3
-   (ref.null any)
+   (ref.null none)
   )
   (local.set $4
    (local.get $4)
@@ -33,7 +33,7 @@
    (global.get $global$1)
   )
   (local.set $4
-   (ref.null eq)
+   (ref.null none)
   )
   (local.set $0
    (local.get $0)
@@ -53,7 +53,7 @@
    (global.get $global$1)
   )
   (local.set $3
-   (ref.null eq)
+   (ref.null none)
   )
   (local.set $3
    (local.get $0)
@@ -84,7 +84,7 @@
    (global.get $global$0)
   )
   (global.set $global$0
-   (ref.null any)
+   (ref.null none)
   )
   (global.set $global$1
    (local.get $4)
@@ -93,7 +93,7 @@
    (global.get $global$1)
   )
   (global.set $global$1
-   (ref.null eq)
+   (ref.null none)
   )
   (global.set $global$2
    (local.get $0)
@@ -113,7 +113,7 @@
    (global.get $global$1)
   )
   (global.set $global$0
-   (ref.null eq)
+   (ref.null none)
   )
   (global.set $global$0
    (local.get $0)
@@ -148,7 +148,7 @@
    )
   )
  )
- (func $1 (param $0 (ref null i31)) (param $1 i31ref) (param $2 (ref null data)) (param $3 dataref)
+ (func $1 (param $0 i31ref) (param $1 (ref i31)) (param $2 dataref) (param $3 (ref data))
   (nop)
  )
 )
diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt
index c58827a..548f01f 100644
--- a/test/gtest/CMakeLists.txt
+++ b/test/gtest/CMakeLists.txt
@@ -1,7 +1,10 @@
 include_directories(../../third_party/googletest/googletest/include)
+include_directories(../../src/wasm)
 
 set(unittest_SOURCES
+  possible-contents.cpp
   type-builder.cpp
+  wat-lexer.cpp
 )
 
 binaryen_add_executable(binaryen-unittests "${unittest_SOURCES}")
diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp
new file mode 100644
index 0000000..ea2a0c0
--- /dev/null
+++ b/test/gtest/possible-contents.cpp
@@ -0,0 +1,934 @@
+#include "ir/possible-contents.h"
+#include "ir/subtypes.h"
+#include "wasm-s-parser.h"
+#include "wasm.h"
+#include "gtest/gtest.h"
+
+using namespace wasm;
+
+// Asserts a == b, in any order.
+template<typename T> void assertEqualSymmetric(const T& a, const T& b) {
+  EXPECT_EQ(a, b);
+  EXPECT_EQ(b, a);
+  EXPECT_PRED2([](const T& a, const T& b) { return !(a != b); }, a, b);
+  EXPECT_PRED2([](const T& a, const T& b) { return !(b != a); }, a, b);
+}
+
+// Asserts a != b, in any order.
+template<typename T> void assertNotEqualSymmetric(const T& a, const T& b) {
+  EXPECT_NE(a, b);
+  EXPECT_NE(b, a);
+  EXPECT_PRED2([](const T& a, const T& b) { return !(a == b); }, a, b);
+  EXPECT_PRED2([](const T& a, const T& b) { return !(b == a); }, a, b);
+}
+
+// Asserts a combined with b (in any order) is equal to c.
+template<typename T>
+void assertCombination(const T& a, const T& b, const T& c) {
+  T temp1 = PossibleContents::combine(a, b);
+  assertEqualSymmetric(temp1, c);
+  // Also check the type, as nulls will compare equal even if their types
+  // differ. We want to make sure even the types are identical.
+  assertEqualSymmetric(temp1.getType(), c.getType());
+
+  T temp2 = PossibleContents::combine(b, a);
+  assertEqualSymmetric(temp2, c);
+  assertEqualSymmetric(temp2.getType(), c.getType());
+
+  // Verify the shorthand API works like the static one.
+  T temp3 = a;
+  temp3.combine(b);
+  assertEqualSymmetric(temp3, temp1);
+
+  T temp4 = b;
+  temp4.combine(a);
+  assertEqualSymmetric(temp4, temp2);
+}
+
+// Parse a module from text and return it.
+static std::unique_ptr<Module> parse(std::string module) {
+  auto wasm = std::make_unique<Module>();
+  wasm->features = FeatureSet::All;
+  try {
+    SExpressionParser parser(&module.front());
+    Element& root = *parser.root;
+    SExpressionWasmBuilder builder(*wasm, *root[0], IRProfile::Normal);
+  } catch (ParseException& p) {
+    p.dump(std::cerr);
+    Fatal() << "error in parsing wasm text";
+  }
+  return wasm;
+};
+
+// We want to declare a bunch of globals that are used in various tests. Doing
+// so in the global scope is risky, as their constructors use types, and the
+// type system itself uses global constructors. Instead, we use a fixture for
+// that.
+class PossibleContentsTest : public testing::Test {
+protected:
+  void SetUp() override {
+    // Use nominal typing to test struct types.
+    wasm::setTypeSystem(TypeSystem::Nominal);
+  }
+
+  Type anyref = Type(HeapType::any, Nullable);
+  Type funcref = Type(HeapType::func, Nullable);
+  Type i31ref = Type(HeapType::i31, Nullable);
+  Type dataref = Type(HeapType::data, Nullable);
+
+  PossibleContents none = PossibleContents::none();
+
+  PossibleContents i32Zero = PossibleContents::literal(Literal(int32_t(0)));
+  PossibleContents i32One = PossibleContents::literal(Literal(int32_t(1)));
+  PossibleContents f64One = PossibleContents::literal(Literal(double(1)));
+  PossibleContents anyNull =
+    PossibleContents::literal(Literal::makeNull(HeapType::any));
+  PossibleContents funcNull =
+    PossibleContents::literal(Literal::makeNull(HeapType::func));
+  PossibleContents i31Null =
+    PossibleContents::literal(Literal::makeNull(HeapType::i31));
+
+  PossibleContents i32Global1 =
+    PossibleContents::global("i32Global1", Type::i32);
+  PossibleContents i32Global2 =
+    PossibleContents::global("i32Global2", Type::i32);
+  PossibleContents f64Global = PossibleContents::global("f64Global", Type::f64);
+  PossibleContents anyGlobal = PossibleContents::global("anyGlobal", anyref);
+  PossibleContents funcGlobal = PossibleContents::global("funcGlobal", funcref);
+  PossibleContents nonNullFuncGlobal =
+    PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable));
+
+  PossibleContents nonNullFunc = PossibleContents::literal(
+    Literal("func", Signature(Type::none, Type::none)));
+
+  PossibleContents exactI32 = PossibleContents::exactType(Type::i32);
+  PossibleContents exactAnyref = PossibleContents::exactType(anyref);
+  PossibleContents exactFuncref = PossibleContents::exactType(funcref);
+  PossibleContents exactDataref = PossibleContents::exactType(dataref);
+  PossibleContents exactI31ref = PossibleContents::exactType(i31ref);
+  PossibleContents exactNonNullAnyref =
+    PossibleContents::exactType(Type(HeapType::any, NonNullable));
+  PossibleContents exactNonNullFuncref =
+    PossibleContents::exactType(Type(HeapType::func, NonNullable));
+  PossibleContents exactNonNullI31ref =
+    PossibleContents::exactType(Type(HeapType::i31, NonNullable));
+
+  PossibleContents exactFuncSignatureType = PossibleContents::exactType(
+    Type(Signature(Type::none, Type::none), Nullable));
+  PossibleContents exactNonNullFuncSignatureType = PossibleContents::exactType(
+    Type(Signature(Type::none, Type::none), NonNullable));
+
+  PossibleContents many = PossibleContents::many();
+
+  PossibleContents coneAnyref = PossibleContents::fullConeType(anyref);
+  PossibleContents coneFuncref = PossibleContents::fullConeType(funcref);
+  PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1);
+};
+
+TEST_F(PossibleContentsTest, TestComparisons) {
+  assertEqualSymmetric(none, none);
+  assertNotEqualSymmetric(none, i32Zero);
+  assertNotEqualSymmetric(none, i32Global1);
+  assertNotEqualSymmetric(none, exactI32);
+  assertNotEqualSymmetric(none, many);
+
+  assertEqualSymmetric(i32Zero, i32Zero);
+  assertNotEqualSymmetric(i32Zero, i32One);
+  assertNotEqualSymmetric(i32Zero, f64One);
+  assertNotEqualSymmetric(i32Zero, i32Global1);
+  assertNotEqualSymmetric(i32Zero, exactI32);
+  assertNotEqualSymmetric(i32Zero, many);
+
+  assertEqualSymmetric(i32Global1, i32Global1);
+  assertNotEqualSymmetric(i32Global1, i32Global2);
+  assertNotEqualSymmetric(i32Global1, exactI32);
+  assertNotEqualSymmetric(i32Global1, many);
+
+  assertEqualSymmetric(exactI32, exactI32);
+  assertNotEqualSymmetric(exactI32, exactAnyref);
+  assertNotEqualSymmetric(exactI32, many);
+
+  assertEqualSymmetric(many, many);
+
+  // Nulls
+
+  assertNotEqualSymmetric(i32Zero, anyNull);
+  assertNotEqualSymmetric(anyNull, funcNull);
+  assertEqualSymmetric(anyNull, anyNull);
+
+  assertEqualSymmetric(exactNonNullAnyref, exactNonNullAnyref);
+  assertNotEqualSymmetric(exactNonNullAnyref, exactAnyref);
+}
+
+TEST_F(PossibleContentsTest, TestHash) {
+  // Hashes should be deterministic.
+  EXPECT_EQ(none.hash(), none.hash());
+  EXPECT_EQ(many.hash(), many.hash());
+
+  // Hashes should be different. (In theory hash collisions could appear here,
+  // but if such simple things collide and the test fails then we should really
+  // rethink our hash functions!)
+  EXPECT_NE(none.hash(), many.hash());
+  EXPECT_NE(none.hash(), i32Zero.hash());
+  EXPECT_NE(none.hash(), i32One.hash());
+  EXPECT_NE(none.hash(), anyGlobal.hash());
+  EXPECT_NE(none.hash(), funcGlobal.hash());
+  EXPECT_NE(none.hash(), exactAnyref.hash());
+  EXPECT_NE(none.hash(), exactFuncSignatureType.hash());
+  EXPECT_NE(none.hash(), coneAnyref.hash());
+  EXPECT_NE(none.hash(), coneFuncref.hash());
+  EXPECT_NE(none.hash(), coneFuncref1.hash());
+
+  EXPECT_NE(i32Zero.hash(), i32One.hash());
+  EXPECT_NE(anyGlobal.hash(), funcGlobal.hash());
+  EXPECT_NE(exactAnyref.hash(), exactFuncSignatureType.hash());
+  EXPECT_NE(coneAnyref.hash(), coneFuncref.hash());
+  EXPECT_NE(coneAnyref.hash(), coneFuncref1.hash());
+  EXPECT_NE(coneFuncref.hash(), coneFuncref1.hash());
+}
+
+TEST_F(PossibleContentsTest, TestCombinations) {
+  // None with anything else becomes the other thing.
+  assertCombination(none, none, none);
+  assertCombination(none, i32Zero, i32Zero);
+  assertCombination(none, i32Global1, i32Global1);
+  assertCombination(none, exactI32, exactI32);
+  assertCombination(none, many, many);
+
+  // i32(0) will become Many, unless the value or the type is identical.
+  assertCombination(i32Zero, i32Zero, i32Zero);
+  assertCombination(i32Zero, i32One, exactI32);
+  assertCombination(i32Zero, f64One, many);
+  assertCombination(i32Zero, i32Global1, exactI32);
+  assertCombination(i32Zero, f64Global, many);
+  assertCombination(i32Zero, exactI32, exactI32);
+  assertCombination(i32Zero, exactAnyref, many);
+  assertCombination(i32Zero, many, many);
+
+  assertCombination(i32Global1, i32Global1, i32Global1);
+  assertCombination(i32Global1, i32Global2, exactI32);
+  assertCombination(i32Global1, f64Global, many);
+  assertCombination(i32Global1, exactI32, exactI32);
+  assertCombination(i32Global1, exactAnyref, many);
+  assertCombination(i32Global1, many, many);
+
+  assertCombination(exactI32, exactI32, exactI32);
+  assertCombination(exactI32, exactAnyref, many);
+  assertCombination(exactI32, many, many);
+
+  assertCombination(many, many, many);
+
+  // Exact references: An exact reference only stays exact when combined with
+  // the same heap type (nullability may be added, but nothing else). Otherwise
+  // we go to a cone type or to many.
+  assertCombination(exactFuncref, exactAnyref, many);
+  assertCombination(exactFuncref, anyGlobal, many);
+  assertCombination(exactFuncref, nonNullFunc, coneFuncref1);
+  assertCombination(exactFuncref, exactFuncref, exactFuncref);
+  assertCombination(exactFuncref, exactNonNullFuncref, exactFuncref);
+
+  // Nulls.
+
+  assertCombination(anyNull, i32Zero, many);
+  assertCombination(anyNull, anyNull, anyNull);
+  assertCombination(anyNull, exactAnyref, exactAnyref);
+
+  // Two nulls go to the lub.
+  assertCombination(anyNull, i31Null, anyNull);
+
+  // Incompatible nulls go to Many.
+  assertCombination(anyNull, funcNull, many);
+
+  assertCombination(exactNonNullAnyref, exactNonNullAnyref, exactNonNullAnyref);
+
+  // If one is a null and the other is not, it makes the one that is not a
+  // null be a nullable type - but keeps the heap type of the other (since the
+  // type of the null does not matter, all nulls compare equal).
+  assertCombination(anyNull, exactNonNullAnyref, exactAnyref);
+  assertCombination(anyNull, exactNonNullI31ref, exactI31ref);
+
+  // Funcrefs
+
+  // A function reference + a null becomes an exact type (of that sig), plus
+  // nullability.
+  assertCombination(nonNullFunc, funcNull, exactFuncSignatureType);
+  assertCombination(exactFuncSignatureType, funcNull, exactFuncSignatureType);
+  assertCombination(
+    exactNonNullFuncSignatureType, funcNull, exactFuncSignatureType);
+  assertCombination(
+    nonNullFunc, exactFuncSignatureType, exactFuncSignatureType);
+  assertCombination(
+    nonNullFunc, exactNonNullFuncSignatureType, exactNonNullFuncSignatureType);
+  assertCombination(nonNullFunc, exactI32, many);
+
+  // Globals vs nulls. The result is either the global or a null, so all we can
+  // say is that it is something of the global's type, or a null: a cone.
+
+  assertCombination(anyGlobal, anyNull, coneAnyref);
+  assertCombination(anyGlobal, i31Null, coneAnyref);
+}
+
+TEST_F(PossibleContentsTest, TestOracleMinimal) {
+  // A minimal test of the public API of PossibleTypesOracle. See the lit test
+  // for coverage of all the internals (using lit makes the result more
+  // fuzzable).
+  auto wasm = parse(R"(
+    (module
+      (global $null (ref null any) (ref.null any))
+      (global $something i32 (i32.const 42))
+    )
+  )");
+  ContentOracle oracle(*wasm);
+
+  // This will be a null constant.
+  EXPECT_TRUE(oracle.getContents(GlobalLocation{"null"}).isNull());
+
+  // This will be 42.
+  EXPECT_EQ(oracle.getContents(GlobalLocation{"something"}).getLiteral(),
+            Literal(int32_t(42)));
+}
+
+// Asserts a and b have an intersection (or do not), and checks both orderings.
+void assertHaveIntersection(PossibleContents a, PossibleContents b) {
+  EXPECT_TRUE(PossibleContents::haveIntersection(a, b));
+  EXPECT_TRUE(PossibleContents::haveIntersection(b, a));
+#if BINARYEN_TEST_DEBUG
+  if (!PossibleContents::haveIntersection(a, b) ||
+      !PossibleContents::haveIntersection(b, a)) {
+    std::cout << "\nFailure: no intersection:\n" << a << '\n' << b << '\n';
+    abort();
+  }
+#endif
+}
+void assertLackIntersection(PossibleContents a, PossibleContents b) {
+  EXPECT_FALSE(PossibleContents::haveIntersection(a, b));
+  EXPECT_FALSE(PossibleContents::haveIntersection(b, a));
+}
+
+TEST_F(PossibleContentsTest, TestIntersection) {
+  // None has no contents, so nothing to intersect.
+  assertLackIntersection(none, none);
+  assertLackIntersection(none, i32Zero);
+  assertLackIntersection(none, many);
+
+  // Many intersects with anything (but none).
+  assertHaveIntersection(many, many);
+  assertHaveIntersection(many, i32Zero);
+
+  // Different exact types cannot intersect.
+  assertLackIntersection(exactI32, exactAnyref);
+  assertLackIntersection(i32Zero, exactAnyref);
+
+  // But nullable ones can - the null can be the intersection, if they are not
+  // in separate hierarchies.
+  assertHaveIntersection(exactFuncSignatureType, funcNull);
+
+  assertLackIntersection(exactFuncSignatureType, exactAnyref);
+  assertLackIntersection(anyNull, funcNull);
+
+  // Identical types might.
+  assertHaveIntersection(exactI32, exactI32);
+  assertHaveIntersection(i32Zero, i32Zero);
+  assertHaveIntersection(exactFuncSignatureType, exactFuncSignatureType);
+  assertHaveIntersection(i32Zero, i32One); // TODO: this could be inferred false
+
+  // Exact types only differing by nullability can intersect (not on the null,
+  // but on something else).
+  assertHaveIntersection(exactAnyref, exactNonNullAnyref);
+
+  // Due to subtyping, an intersection might exist.
+  assertHaveIntersection(funcGlobal, funcGlobal);
+  assertHaveIntersection(funcGlobal, exactFuncSignatureType);
+  assertHaveIntersection(nonNullFuncGlobal, exactFuncSignatureType);
+  assertHaveIntersection(funcGlobal, exactNonNullFuncSignatureType);
+  assertHaveIntersection(nonNullFuncGlobal, exactNonNullFuncSignatureType);
+
+  // Separate hierarchies.
+  assertLackIntersection(funcGlobal, anyGlobal);
+}
+
+TEST_F(PossibleContentsTest, TestIntersectWithCombinations) {
+  // Whenever we combine C = A + B, both A and B must intersect with C. This
+  // helper function gets a set of things and checks that property on them. It
+  // returns the set of all contents it ever observed (see below for how we use
+  // that).
+  auto doTest = [](std::unordered_set<PossibleContents> set) {
+    std::vector<PossibleContents> vec(set.begin(), set.end());
+
+    // Find the maximum depths for the normalized cone tests later down.
+    std::unordered_set<HeapType> heapTypes;
+    for (auto& contents : set) {
+      auto type = contents.getType();
+      if (type.isRef()) {
+        auto heapType = type.getHeapType();
+        if (!heapType.isBasic()) {
+          heapTypes.insert(heapType);
+        }
+      }
+    }
+    std::vector<HeapType> heapTypesVec(heapTypes.begin(), heapTypes.end());
+    SubTypes subTypes(heapTypesVec);
+    auto maxDepths = subTypes.getMaxDepths();
+
+    // Go over all permutations up to a certain size (this quickly becomes
+    // extremely slow, obviously, so keep this low).
+    size_t max = 3;
+
+    auto n = set.size();
+
+    // |indexes| contains the indexes of the items in vec for the current
+    // permutation.
+    std::vector<size_t> indexes(max);
+    std::fill(indexes.begin(), indexes.end(), 0);
+    while (1) {
+      // Test the current permutation: Combine all the relevant things, and then
+      // check they all have an intersection.
+      PossibleContents combination;
+      for (auto index : indexes) {
+        combination.combine(vec[index]);
+      }
+      // Note the combination in the set.
+      set.insert(combination);
+#if BINARYEN_TEST_DEBUG
+      for (auto index : indexes) {
+        std::cout << index << ' ';
+        combination.combine(vec[index]);
+      }
+      std::cout << '\n';
+#endif
+      for (auto index : indexes) {
+        auto item = vec[index];
+        if (item.isNone()) {
+          assertLackIntersection(combination, item);
+          continue;
+        }
+#if BINARYEN_TEST_DEBUG
+        if (!PossibleContents::haveIntersection(combination, item)) {
+          std::cout << "\nFailure: no expected intersection. Indexes:\n";
+          for (auto index : indexes) {
+            std::cout << index << "\n  ";
+            vec[index].dump(std::cout);
+            std::cout << '\n';
+          }
+          std::cout << "combo:\n";
+          combination.dump(std::cout);
+          std::cout << "\ncompared item (index " << index << "):\n";
+          item.dump(std::cout);
+          std::cout << '\n';
+          abort();
+        }
+#endif
+        assertHaveIntersection(combination, item);
+
+        auto type = combination.getType();
+        if (type.isRef()) {
+          // If we normalize the combination's depth, the item must still have
+          // an intersection. That is, normalization must not have a bug that
+          // results in cones that are too shallow.
+          auto normalizedDepth = maxDepths[type.getHeapType()];
+          auto normalizedCone =
+            PossibleContents::coneType(type, normalizedDepth);
+          assertHaveIntersection(normalizedCone, item);
+        }
+
+        // Test intersectWithFullCone() method, which is supported with a full
+        // cone type. In that case we can test that the intersection of A with
+        // A + B is simply A.
+        if (combination.isFullConeType()) {
+          auto intersection = item;
+          intersection.intersectWithFullCone(combination);
+          EXPECT_EQ(intersection, item);
+#if BINARYEN_TEST_DEBUG
+          if (intersection != item) {
+            std::cout << "\nFailure: wrong intersection.\n";
+            std::cout << "item: " << item << '\n';
+            std::cout << "combination: " << combination << '\n';
+            std::cout << "intersection: " << intersection << '\n';
+            abort();
+          }
+#endif
+
+          // The intersection is contained in each of the things we intersected
+          // (but we can only compare to the full cone, as the API is restricted
+          // to that).
+          EXPECT_TRUE(
+            PossibleContents::isSubContents(intersection, combination));
+        }
+      }
+
+      // Move to the next permutation.
+      size_t i = 0;
+      while (1) {
+        indexes[i]++;
+        if (indexes[i] == n) {
+          // Overflow.
+          indexes[i] = 0;
+          i++;
+          if (i == max) {
+            // All done.
+            return set;
+          }
+        } else {
+          break;
+        }
+      }
+    }
+
+    WASM_UNREACHABLE("loop above returns manually");
+  };
+
+  // Start from an initial set of the hardcoded contents we have in our test
+  // fixture.
+  std::unordered_set<PossibleContents> initial = {none,
+                                                  f64One,
+                                                  anyNull,
+                                                  funcNull,
+                                                  i31Null,
+                                                  i32Global1,
+                                                  i32Global2,
+                                                  f64Global,
+                                                  anyGlobal,
+                                                  funcGlobal,
+                                                  nonNullFuncGlobal,
+                                                  nonNullFunc,
+                                                  exactI32,
+                                                  exactAnyref,
+                                                  exactFuncref,
+                                                  exactDataref,
+                                                  exactI31ref,
+                                                  exactNonNullAnyref,
+                                                  exactNonNullFuncref,
+                                                  exactNonNullI31ref,
+                                                  exactFuncSignatureType,
+                                                  exactNonNullFuncSignatureType,
+                                                  many,
+                                                  coneAnyref,
+                                                  coneFuncref,
+                                                  coneFuncref1};
+
+  // Add some additional interesting types.
+  auto structType =
+    Type(HeapType(Struct({Field(Type::i32, Immutable)})), NonNullable);
+  initial.insert(PossibleContents::coneType(structType, 0));
+  auto arrayType =
+    Type(HeapType(Array(Field(Type::i32, Immutable))), NonNullable);
+  initial.insert(PossibleContents::coneType(arrayType, 0));
+
+  // After testing on the initial contents, also test using anything new that
+  // showed up while combining them.
+  auto subsequent = doTest(initial);
+  while (subsequent.size() > initial.size()) {
+    initial = subsequent;
+    subsequent = doTest(subsequent);
+  }
+}
+
+void assertIntersection(PossibleContents a,
+                        PossibleContents b,
+                        PossibleContents result) {
+  auto intersection = a;
+  intersection.intersectWithFullCone(b);
+  EXPECT_EQ(intersection, result);
+
+  EXPECT_EQ(PossibleContents::haveIntersection(a, b), !result.isNone());
+}
+
+TEST_F(PossibleContentsTest, TestStructCones) {
+  /*
+        A       E
+       / \
+      B   C
+           \
+            D
+  */
+  TypeBuilder builder(5);
+  builder.setHeapType(0, Struct(FieldList{}));
+  builder.setHeapType(1, Struct(FieldList{}));
+  builder.setHeapType(2, Struct(FieldList{}));
+  builder.setHeapType(3, Struct(FieldList{}));
+  builder.setHeapType(4, Struct(FieldList{}));
+  builder.setSubType(1, builder.getTempHeapType(0));
+  builder.setSubType(2, builder.getTempHeapType(0));
+  builder.setSubType(3, builder.getTempHeapType(2));
+  auto result = builder.build();
+  ASSERT_TRUE(result);
+  auto types = *result;
+  auto A = types[0];
+  auto B = types[1];
+  auto C = types[2];
+  auto D = types[3];
+  auto E = types[4];
+  ASSERT_TRUE(B.getSuperType() == A);
+  ASSERT_TRUE(C.getSuperType() == A);
+  ASSERT_TRUE(D.getSuperType() == C);
+
+  auto nullA = Type(A, Nullable);
+  auto nullB = Type(B, Nullable);
+  auto nullC = Type(C, Nullable);
+  auto nullD = Type(D, Nullable);
+  auto nullE = Type(E, Nullable);
+
+  auto exactA = PossibleContents::exactType(nullA);
+  auto exactB = PossibleContents::exactType(nullB);
+  auto exactC = PossibleContents::exactType(nullC);
+  auto exactD = PossibleContents::exactType(nullD);
+  auto exactE = PossibleContents::exactType(nullE);
+
+  auto nnA = Type(A, NonNullable);
+  auto nnB = Type(B, NonNullable);
+  auto nnC = Type(C, NonNullable);
+  auto nnD = Type(D, NonNullable);
+  auto nnE = Type(E, NonNullable);
+
+  auto nnExactA = PossibleContents::exactType(nnA);
+  auto nnExactB = PossibleContents::exactType(nnB);
+  auto nnExactC = PossibleContents::exactType(nnC);
+  auto nnExactD = PossibleContents::exactType(nnD);
+  auto nnExactE = PossibleContents::exactType(nnE);
+
+  assertCombination(exactA, exactA, exactA);
+  assertCombination(exactA, exactA, PossibleContents::coneType(nullA, 0));
+  assertCombination(exactA, exactB, PossibleContents::coneType(nullA, 1));
+  assertCombination(exactA, exactC, PossibleContents::coneType(nullA, 1));
+  assertCombination(exactA, exactD, PossibleContents::coneType(nullA, 2));
+  assertCombination(exactA, exactE, PossibleContents::coneType(dataref, 1));
+  assertCombination(
+    exactA, exactDataref, PossibleContents::coneType(dataref, 1));
+
+  assertCombination(exactB, exactB, exactB);
+  assertCombination(exactB, exactC, PossibleContents::coneType(nullA, 1));
+  assertCombination(exactB, exactD, PossibleContents::coneType(nullA, 2));
+  assertCombination(exactB, exactE, PossibleContents::coneType(dataref, 2));
+  assertCombination(
+    exactB, exactDataref, PossibleContents::coneType(dataref, 2));
+
+  assertCombination(exactC, exactC, exactC);
+  assertCombination(exactC, exactD, PossibleContents::coneType(nullC, 1));
+  assertCombination(exactC, exactE, PossibleContents::coneType(dataref, 2));
+  assertCombination(
+    exactC, exactDataref, PossibleContents::coneType(dataref, 2));
+
+  assertCombination(exactD, exactD, exactD);
+  assertCombination(exactD, exactE, PossibleContents::coneType(dataref, 3));
+  assertCombination(
+    exactD, exactDataref, PossibleContents::coneType(dataref, 3));
+
+  assertCombination(exactE, exactE, exactE);
+  assertCombination(
+    exactE, exactDataref, PossibleContents::coneType(dataref, 1));
+
+  assertCombination(exactDataref, exactDataref, exactDataref);
+
+  assertCombination(
+    exactDataref, exactAnyref, PossibleContents::coneType(anyref, 2));
+
+  // Combinations of cones.
+  assertCombination(PossibleContents::coneType(nullA, 5),
+                    PossibleContents::coneType(nullA, 7),
+                    PossibleContents::coneType(nullA, 7));
+
+  // Increment the cone of D as we go here, until it matters.
+  assertCombination(PossibleContents::coneType(nullA, 5),
+                    PossibleContents::coneType(nullD, 2),
+                    PossibleContents::coneType(nullA, 5));
+  assertCombination(PossibleContents::coneType(nullA, 5),
+                    PossibleContents::coneType(nullD, 3),
+                    PossibleContents::coneType(nullA, 5));
+  assertCombination(PossibleContents::coneType(nullA, 5),
+                    PossibleContents::coneType(nullD, 4),
+                    PossibleContents::coneType(nullA, 6));
+
+  assertCombination(PossibleContents::coneType(nullA, 5),
+                    PossibleContents::coneType(nullE, 7),
+                    PossibleContents::coneType(dataref, 8));
+
+  assertCombination(PossibleContents::coneType(nullB, 4),
+                    PossibleContents::coneType(dataref, 1),
+                    PossibleContents::coneType(dataref, 6));
+
+  // Combinations of cones and exact types.
+  assertCombination(exactA,
+                    PossibleContents::coneType(nullA, 3),
+                    PossibleContents::coneType(nullA, 3));
+  assertCombination(exactA,
+                    PossibleContents::coneType(nullD, 3),
+                    PossibleContents::coneType(nullA, 5));
+  assertCombination(exactD,
+                    PossibleContents::coneType(nullA, 3),
+                    PossibleContents::coneType(nullA, 3));
+  assertCombination(exactA,
+                    PossibleContents::coneType(nullE, 2),
+                    PossibleContents::coneType(dataref, 3));
+
+  assertCombination(exactA,
+                    PossibleContents::coneType(dataref, 1),
+                    PossibleContents::coneType(dataref, 1));
+  assertCombination(exactA,
+                    PossibleContents::coneType(dataref, 2),
+                    PossibleContents::coneType(dataref, 2));
+
+  assertCombination(exactDataref,
+                    PossibleContents::coneType(nullB, 3),
+                    PossibleContents::coneType(dataref, 5));
+
+  // Full cones.
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    exactA,
+                    PossibleContents::fullConeType(nullA));
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    PossibleContents::coneType(nullA, 2),
+                    PossibleContents::fullConeType(nullA));
+
+  // All full cones with A remain full cones, except for E.
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    PossibleContents::fullConeType(nullA),
+                    PossibleContents::fullConeType(nullA));
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    PossibleContents::fullConeType(nullB),
+                    PossibleContents::fullConeType(nullA));
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    PossibleContents::fullConeType(nullC),
+                    PossibleContents::fullConeType(nullA));
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    PossibleContents::fullConeType(nullD),
+                    PossibleContents::fullConeType(nullA));
+  assertCombination(PossibleContents::fullConeType(nullA),
+                    PossibleContents::fullConeType(nullE),
+                    PossibleContents::fullConeType(dataref));
+
+  // Intersections. Test with non-nullable types to avoid the null being a
+  // possible intersection.
+  assertHaveIntersection(nnExactA, nnExactA);
+  assertLackIntersection(nnExactA, nnExactB);
+  assertLackIntersection(nnExactA, nnExactC);
+  assertLackIntersection(nnExactA, nnExactD);
+  assertLackIntersection(nnExactA, nnExactE);
+
+  assertHaveIntersection(PossibleContents::coneType(nnA, 1), nnExactB);
+  assertHaveIntersection(PossibleContents::coneType(nnA, 1), nnExactC);
+  assertHaveIntersection(PossibleContents::coneType(nnA, 2), nnExactD);
+
+  assertLackIntersection(PossibleContents::coneType(nnA, 1), nnExactD);
+  assertLackIntersection(PossibleContents::coneType(nnA, 1), nnExactE);
+  assertLackIntersection(PossibleContents::coneType(nnA, 2), nnExactE);
+
+  assertHaveIntersection(PossibleContents::coneType(nnA, 1),
+                         PossibleContents::coneType(nnC, 100));
+  assertLackIntersection(PossibleContents::coneType(nnA, 1),
+                         PossibleContents::coneType(nnD, 100));
+
+  // Neither is a subtype of the other, but nulls are possible, so a null can be
+  // the intersection.
+  assertHaveIntersection(PossibleContents::fullConeType(nullA),
+                         PossibleContents::fullConeType(nullE));
+
+  // Without null on one side, we cannot intersect.
+  assertLackIntersection(PossibleContents::fullConeType(nnA),
+                         PossibleContents::fullConeType(nullE));
+
+  // Computing intersections is supported with a full cone type.
+  assertIntersection(none, PossibleContents::fullConeType(nnA), none);
+  assertIntersection(many,
+                     PossibleContents::fullConeType(nnA),
+                     PossibleContents::fullConeType(nnA));
+  assertIntersection(many,
+                     PossibleContents::fullConeType(nullA),
+                     PossibleContents::fullConeType(nullA));
+
+  assertIntersection(exactA, PossibleContents::fullConeType(nullA), exactA);
+  assertIntersection(nnExactA, PossibleContents::fullConeType(nullA), nnExactA);
+  assertIntersection(exactA, PossibleContents::fullConeType(nnA), nnExactA);
+
+  assertIntersection(exactB, PossibleContents::fullConeType(nullA), exactB);
+  assertIntersection(nnExactB, PossibleContents::fullConeType(nullA), nnExactB);
+  assertIntersection(exactB, PossibleContents::fullConeType(nnA), nnExactB);
+
+  auto literalNullA = PossibleContents::literal(Literal::makeNull(A));
+
+  assertIntersection(
+    literalNullA, PossibleContents::fullConeType(nullA), literalNullA);
+  assertIntersection(literalNullA, PossibleContents::fullConeType(nnA), none);
+
+  assertIntersection(
+    literalNullA, PossibleContents::fullConeType(nullB), literalNullA);
+  assertIntersection(literalNullA, PossibleContents::fullConeType(nnB), none);
+
+  assertIntersection(
+    literalNullA, PossibleContents::fullConeType(nullE), literalNullA);
+  assertIntersection(literalNullA, PossibleContents::fullConeType(nnE), none);
+
+  assertIntersection(exactA,
+                     PossibleContents::fullConeType(nullB),
+                     PossibleContents::literal(Literal::makeNull(B)));
+  assertIntersection(nnExactA, PossibleContents::fullConeType(nullB), none);
+  assertIntersection(exactA, PossibleContents::fullConeType(nnB), none);
+
+  // A and E have no intersection, so the only possibility is a null, and that
+  // null must be the bottom type.
+  assertIntersection(
+    exactA,
+    PossibleContents::fullConeType(nullE),
+    PossibleContents::literal(Literal::makeNull(HeapType::none)));
+
+  assertIntersection(PossibleContents::coneType(nnA, 1),
+                     PossibleContents::fullConeType(nnB),
+                     nnExactB);
+  assertIntersection(PossibleContents::coneType(nnB, 1),
+                     PossibleContents::fullConeType(nnA),
+                     PossibleContents::coneType(nnB, 1));
+  assertIntersection(PossibleContents::coneType(nnD, 2),
+                     PossibleContents::fullConeType(nnA),
+                     PossibleContents::coneType(nnD, 2));
+  assertIntersection(PossibleContents::coneType(nnA, 5),
+                     PossibleContents::fullConeType(nnD),
+                     PossibleContents::coneType(nnD, 3));
+
+  assertIntersection(PossibleContents::coneType(nnA, 1),
+                     PossibleContents::fullConeType(nnD),
+                     none);
+
+  // Globals stay as globals if their type is in the cone. Otherwise, they lose
+  // the global info and we compute a normal cone intersection on them. The
+  // same for literals.
+  assertIntersection(
+    funcGlobal, PossibleContents::fullConeType(funcref), funcGlobal);
+
+  auto signature = Type(Signature(Type::none, Type::none), Nullable);
+  assertIntersection(
+    nonNullFunc, PossibleContents::fullConeType(signature), nonNullFunc);
+  assertIntersection(funcGlobal,
+                     PossibleContents::fullConeType(signature),
+                     PossibleContents::fullConeType(signature));
+
+  // Incompatible hierarchies have no intersection.
+  assertIntersection(
+    literalNullA, PossibleContents::fullConeType(funcref), none);
+
+  // Subcontents. This API only supports the case where one of the inputs is a
+  // full cone type.
+  // First, compare exact types to such a cone.
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    exactA, PossibleContents::fullConeType(nullA)));
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    nnExactA, PossibleContents::fullConeType(nnA)));
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    nnExactA, PossibleContents::fullConeType(nullA)));
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    nnExactD, PossibleContents::fullConeType(nullA)));
+
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    exactA, PossibleContents::fullConeType(nnA)));
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    exactA, PossibleContents::fullConeType(nullB)));
+
+  // Next, compare cones.
+  EXPECT_TRUE(
+    PossibleContents::isSubContents(PossibleContents::fullConeType(nullA),
+                                    PossibleContents::fullConeType(nullA)));
+  EXPECT_TRUE(
+    PossibleContents::isSubContents(PossibleContents::fullConeType(nnA),
+                                    PossibleContents::fullConeType(nullA)));
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nnA)));
+  EXPECT_TRUE(
+    PossibleContents::isSubContents(PossibleContents::fullConeType(nullD),
+                                    PossibleContents::fullConeType(nullA)));
+
+  EXPECT_FALSE(
+    PossibleContents::isSubContents(PossibleContents::fullConeType(nullA),
+                                    PossibleContents::fullConeType(nnA)));
+  EXPECT_FALSE(
+    PossibleContents::isSubContents(PossibleContents::fullConeType(nullA),
+                                    PossibleContents::fullConeType(nullD)));
+
+  // Trivial values.
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    PossibleContents::none(), PossibleContents::fullConeType(nullA)));
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    PossibleContents::many(), PossibleContents::fullConeType(nullA)));
+
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    anyNull, PossibleContents::fullConeType(nullA)));
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    anyNull, PossibleContents::fullConeType(nnA)));
+
+  // Tests cases with a full cone only on the left. Such a cone is only a sub-
+  // contents of Many.
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nullA), exactA));
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nullA), nnExactA));
+
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nullA), PossibleContents::none()));
+  EXPECT_TRUE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nullA), PossibleContents::many()));
+
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nullA), anyNull));
+  EXPECT_FALSE(PossibleContents::isSubContents(
+    PossibleContents::fullConeType(nnA), anyNull));
+}
+
+TEST_F(PossibleContentsTest, TestOracleManyTypes) {
+  // Test for a node with many possible types. The pass limits how many it
+  // notices to not use excessive memory, so even though 4 are possible here,
+  // we'll just report that more than one is possible, a cone of data.
+  auto wasm = parse(R"(
+    (module
+      (type $A (struct_subtype (field i32) data))
+      (type $B (struct_subtype (field i64) data))
+      (type $C (struct_subtype (field f32) data))
+      (type $D (struct_subtype (field f64) data))
+      (func $foo (result (ref any))
+        (select (result (ref any))
+          (select (result (ref any))
+            (struct.new $A)
+            (struct.new $B)
+            (i32.const 0)
+          )
+          (select (result (ref any))
+            (struct.new $C)
+            (struct.new $D)
+            (i32.const 0)
+          )
+          (i32.const 0)
+        )
+      )
+    )
+  )");
+  ContentOracle oracle(*wasm);
+  // The body's contents must be a cone of data with depth 1.
+  auto bodyContents =
+    oracle.getContents(ResultLocation{wasm->getFunction("foo"), 0});
+  ASSERT_TRUE(bodyContents.isConeType());
+  EXPECT_EQ(bodyContents.getType().getHeapType(), HeapType::data);
+  EXPECT_EQ(bodyContents.getCone().depth, Index(1));
+}
+
+TEST_F(PossibleContentsTest, TestOracleNoFullCones) {
+  // PossibleContents should be normalized, so we never have full cones (depth
+  // infinity).
+  auto wasm = parse(R"(
+    (module
+      (type $A (struct_subtype (field i32) data))
+      (type $B (struct_subtype (field i32) $A))
+      (type $C (struct_subtype (field i32) $B))
+      (func $foo (export "foo")
+        ;; Note we must declare $C so that $B and $C have uses and are not
+        ;; removed automatically from consideration.
+        (param $a (ref $A)) (param $c (ref $C))
+        (result (ref any))
+        (local.get $a)
+      )
+    )
+  )");
+  ContentOracle oracle(*wasm);
+  // The function is exported, and all we know about the parameter $a is that it
+  // is some subtype of $A. This is normalized to depth 2 because that is the
+  // actual depth of subtypes.
+  auto bodyContents =
+    oracle.getContents(ResultLocation{wasm->getFunction("foo"), 0});
+  ASSERT_TRUE(bodyContents.isConeType());
+  EXPECT_EQ(bodyContents.getCone().depth, Index(2));
+}
diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp
index 42b3885..8ba2a39 100644
--- a/test/gtest/type-builder.cpp
+++ b/test/gtest/type-builder.cpp
@@ -1,3 +1,4 @@
+#include "ir/subtypes.h"
 #include "type-test.h"
 #include "wasm-type-printing.h"
 #include "wasm-type.h"
@@ -10,6 +11,8 @@ TEST_F(TypeTest, TypeBuilderGrowth) {
   EXPECT_EQ(builder.size(), 0u);
   builder.grow(3);
   EXPECT_EQ(builder.size(), 3u);
+  builder.grow(0);
+  EXPECT_EQ(builder.size(), 3u);
 }
 
 TEST_F(TypeTest, TypeIterator) {
@@ -22,12 +25,12 @@ TEST_F(TypeTest, TypeIterator) {
 
   EXPECT_EQ(none.size(), 0u);
   EXPECT_EQ(none.begin(), none.end());
-  EXPECT_EQ(none.end() - none.begin(), 0u);
+  EXPECT_EQ(none.end() - none.begin(), 0);
   EXPECT_EQ(none.begin() + 0, none.end());
 
   EXPECT_EQ(i32.size(), 1u);
   EXPECT_NE(i32.begin(), i32.end());
-  EXPECT_EQ(i32.end() - i32.begin(), 1u);
+  EXPECT_EQ(i32.end() - i32.begin(), 1);
 
   EXPECT_EQ(*i32.begin(), i32);
   EXPECT_EQ(i32[0], i32);
@@ -53,7 +56,7 @@ TEST_F(TypeTest, TypeIterator) {
 
   EXPECT_EQ(tuple.size(), 4u);
   EXPECT_NE(tuple.begin(), tuple.end());
-  EXPECT_EQ(tuple.end() - tuple.begin(), 4u);
+  EXPECT_EQ(tuple.end() - tuple.begin(), 4);
 
   EXPECT_EQ(*tuple.begin(), i32);
   EXPECT_EQ(*(tuple.begin() + 1), i64);
@@ -117,7 +120,7 @@ TEST_F(TypeTest, IndexedTypePrinter) {
 
 TEST_F(EquirecursiveTest, Basics) {
   // (type $sig (func (param (ref $struct)) (result (ref $array) i32)))
-  // (type $struct (struct (field (ref null $array) (mut rtt 0 $array))))
+  // (type $struct (struct (field (ref null $array))))
   // (type $array (array (mut anyref)))
   TypeBuilder builder(3);
   ASSERT_EQ(builder.size(), size_t{3});
@@ -126,11 +129,10 @@ TEST_F(EquirecursiveTest, Basics) {
   Type refStruct = builder.getTempRefType(builder[1], NonNullable);
   Type refArray = builder.getTempRefType(builder[2], NonNullable);
   Type refNullArray = builder.getTempRefType(builder[2], Nullable);
-  Type rttArray = builder.getTempRttType(Rtt(0, builder[2]));
   Type refNullAny(HeapType::any, Nullable);
 
   Signature sig(refStruct, builder.getTempTupleType({refArray, Type::i32}));
-  Struct struct_({Field(refNullArray, Immutable), Field(rttArray, Mutable)});
+  Struct struct_({Field(refNullArray, Immutable)});
   Array array(Field(refNullAny, Mutable));
 
   builder[0] = sig;
@@ -152,13 +154,10 @@ TEST_F(EquirecursiveTest, Basics) {
   Type newRefStruct = Type(built[1], NonNullable);
   Type newRefArray = Type(built[2], NonNullable);
   Type newRefNullArray = Type(built[2], Nullable);
-  Type newRttArray = Type(Rtt(0, built[2]));
 
   EXPECT_EQ(built[0].getSignature(),
             Signature(newRefStruct, {newRefArray, Type::i32}));
-  EXPECT_EQ(
-    built[1].getStruct(),
-    Struct({Field(newRefNullArray, Immutable), Field(newRttArray, Mutable)}));
+  EXPECT_EQ(built[1].getStruct(), Struct({Field(newRefNullArray, Immutable)}));
   EXPECT_EQ(built[2].getArray(), Array(Field(refNullAny, Mutable)));
 
   // The built types should be different from the temporary types.
@@ -166,7 +165,6 @@ TEST_F(EquirecursiveTest, Basics) {
   EXPECT_NE(newRefStruct, refStruct);
   EXPECT_NE(newRefArray, refArray);
   EXPECT_NE(newRefNullArray, refNullArray);
-  EXPECT_NE(newRttArray, rttArray);
 }
 
 static void testDirectSelfSupertype() {
@@ -364,7 +362,7 @@ TEST_F(IsorecursiveTest, CanonicalizeUses) {
   EXPECT_NE(built[4], built[6]);
 }
 
-TEST_F(IsorecursiveTest, DISABLED_CanonicalizeSelfReferences) {
+TEST_F(IsorecursiveTest, CanonicalizeSelfReferences) {
   TypeBuilder builder(5);
   // Single self-reference
   builder[0] = makeStruct(builder, {0});
@@ -478,11 +476,13 @@ static void testCanonicalizeBasicTypes() {
   Type anyref = builder.getTempRefType(builder[0], Nullable);
   Type anyrefs = builder.getTempTupleType({anyref, anyref});
 
+  Type anyrefCanon = Type(HeapType::any, Nullable);
+
   builder[0] = HeapType::any;
   builder[1] = Struct({Field(anyref, Immutable)});
-  builder[2] = Struct({Field(Type::anyref, Immutable)});
+  builder[2] = Struct({Field(anyrefCanon, Immutable)});
   builder[3] = Signature(anyrefs, Type::none);
-  builder[4] = Signature({Type::anyref, Type::anyref}, Type::none);
+  builder[4] = Signature({anyrefCanon, anyrefCanon}, Type::none);
 
   auto result = builder.build();
   ASSERT_TRUE(result);
@@ -498,3 +498,485 @@ TEST_F(EquirecursiveTest, CanonicalizeBasicTypes) {
 TEST_F(IsorecursiveTest, CanonicalizeBasicTypes) {
   testCanonicalizeBasicTypes();
 }
+
+TEST_F(IsorecursiveTest, TestBasicTypeRelations) {
+  HeapType ext = HeapType::ext;
+  HeapType func = HeapType::func;
+  HeapType any = HeapType::any;
+  HeapType eq = HeapType::eq;
+  HeapType i31 = HeapType::i31;
+  HeapType data = HeapType::data;
+  HeapType array = HeapType::array;
+  HeapType string = HeapType::string;
+  HeapType stringview_wtf8 = HeapType::stringview_wtf8;
+  HeapType stringview_wtf16 = HeapType::stringview_wtf16;
+  HeapType stringview_iter = HeapType::stringview_iter;
+  HeapType none = HeapType::none;
+  HeapType noext = HeapType::noext;
+  HeapType nofunc = HeapType::nofunc;
+  HeapType defFunc = Signature();
+  HeapType defStruct = Struct();
+  HeapType defArray = Array(Field(Type::i32, Immutable));
+
+  auto assertLUB = [](HeapType a, HeapType b, std::optional<HeapType> lub) {
+    auto lub1 = HeapType::getLeastUpperBound(a, b);
+    auto lub2 = HeapType::getLeastUpperBound(b, a);
+    EXPECT_EQ(lub, lub1);
+    EXPECT_EQ(lub1, lub2);
+    if (a == b) {
+      EXPECT_TRUE(HeapType::isSubType(a, b));
+      EXPECT_TRUE(HeapType::isSubType(b, a));
+      EXPECT_EQ(a.getBottom(), b.getBottom());
+    } else if (lub && *lub == b) {
+      EXPECT_TRUE(HeapType::isSubType(a, b));
+      EXPECT_FALSE(HeapType::isSubType(b, a));
+      EXPECT_EQ(a.getBottom(), b.getBottom());
+    } else if (lub && *lub == a) {
+      EXPECT_FALSE(HeapType::isSubType(a, b));
+      EXPECT_TRUE(HeapType::isSubType(b, a));
+      EXPECT_EQ(a.getBottom(), b.getBottom());
+    } else if (lub) {
+      EXPECT_FALSE(HeapType::isSubType(a, b));
+      EXPECT_FALSE(HeapType::isSubType(b, a));
+      EXPECT_EQ(a.getBottom(), b.getBottom());
+    } else {
+      EXPECT_FALSE(HeapType::isSubType(a, b));
+      EXPECT_FALSE(HeapType::isSubType(b, a));
+      EXPECT_NE(a.getBottom(), b.getBottom());
+    }
+  };
+
+  assertLUB(ext, ext, ext);
+  assertLUB(ext, func, {});
+  assertLUB(ext, any, {});
+  assertLUB(ext, eq, {});
+  assertLUB(ext, i31, {});
+  assertLUB(ext, data, {});
+  assertLUB(ext, array, {});
+  assertLUB(ext, string, {});
+  assertLUB(ext, stringview_wtf8, {});
+  assertLUB(ext, stringview_wtf16, {});
+  assertLUB(ext, stringview_iter, {});
+  assertLUB(ext, none, {});
+  assertLUB(ext, noext, ext);
+  assertLUB(ext, nofunc, {});
+  assertLUB(ext, defFunc, {});
+  assertLUB(ext, defStruct, {});
+  assertLUB(ext, defArray, {});
+
+  assertLUB(func, func, func);
+  assertLUB(func, any, {});
+  assertLUB(func, eq, {});
+  assertLUB(func, i31, {});
+  assertLUB(func, data, {});
+  assertLUB(func, array, {});
+  assertLUB(func, string, {});
+  assertLUB(func, stringview_wtf8, {});
+  assertLUB(func, stringview_wtf16, {});
+  assertLUB(func, stringview_iter, {});
+  assertLUB(func, none, {});
+  assertLUB(func, noext, {});
+  assertLUB(func, nofunc, func);
+  assertLUB(func, defFunc, func);
+  assertLUB(func, defStruct, {});
+  assertLUB(func, defArray, {});
+
+  assertLUB(any, any, any);
+  assertLUB(any, eq, any);
+  assertLUB(any, i31, any);
+  assertLUB(any, data, any);
+  assertLUB(any, array, any);
+  assertLUB(any, string, any);
+  assertLUB(any, stringview_wtf8, any);
+  assertLUB(any, stringview_wtf16, any);
+  assertLUB(any, stringview_iter, any);
+  assertLUB(any, none, any);
+  assertLUB(any, noext, {});
+  assertLUB(any, nofunc, {});
+  assertLUB(any, defFunc, {});
+  assertLUB(any, defStruct, any);
+  assertLUB(any, defArray, any);
+
+  assertLUB(eq, eq, eq);
+  assertLUB(eq, i31, eq);
+  assertLUB(eq, data, eq);
+  assertLUB(eq, array, eq);
+  assertLUB(eq, string, any);
+  assertLUB(eq, stringview_wtf8, any);
+  assertLUB(eq, stringview_wtf16, any);
+  assertLUB(eq, stringview_iter, any);
+  assertLUB(eq, none, eq);
+  assertLUB(eq, noext, {});
+  assertLUB(eq, nofunc, {});
+  assertLUB(eq, defFunc, {});
+  assertLUB(eq, defStruct, eq);
+  assertLUB(eq, defArray, eq);
+
+  assertLUB(i31, i31, i31);
+  assertLUB(i31, data, eq);
+  assertLUB(i31, array, eq);
+  assertLUB(i31, string, any);
+  assertLUB(i31, stringview_wtf8, any);
+  assertLUB(i31, stringview_wtf16, any);
+  assertLUB(i31, stringview_iter, any);
+  assertLUB(i31, none, i31);
+  assertLUB(i31, noext, {});
+  assertLUB(i31, nofunc, {});
+  assertLUB(i31, defFunc, {});
+  assertLUB(i31, defStruct, eq);
+  assertLUB(i31, defArray, eq);
+
+  assertLUB(data, data, data);
+  assertLUB(data, array, data);
+  assertLUB(data, string, any);
+  assertLUB(data, stringview_wtf8, any);
+  assertLUB(data, stringview_wtf16, any);
+  assertLUB(data, stringview_iter, any);
+  assertLUB(data, none, data);
+  assertLUB(data, noext, {});
+  assertLUB(data, nofunc, {});
+  assertLUB(data, defFunc, {});
+  assertLUB(data, defStruct, data);
+  assertLUB(data, defArray, data);
+
+  assertLUB(array, array, array);
+  assertLUB(array, string, any);
+  assertLUB(array, stringview_wtf8, any);
+  assertLUB(array, stringview_wtf16, any);
+  assertLUB(array, stringview_iter, any);
+  assertLUB(array, none, array);
+  assertLUB(array, noext, {});
+  assertLUB(array, nofunc, {});
+  assertLUB(array, defFunc, {});
+  assertLUB(array, defStruct, data);
+  assertLUB(array, defArray, array);
+
+  assertLUB(string, string, string);
+  assertLUB(string, stringview_wtf8, any);
+  assertLUB(string, stringview_wtf16, any);
+  assertLUB(string, stringview_iter, any);
+  assertLUB(string, none, string);
+  assertLUB(string, noext, {});
+  assertLUB(string, nofunc, {});
+  assertLUB(string, defFunc, {});
+  assertLUB(string, defStruct, any);
+  assertLUB(string, defArray, any);
+
+  assertLUB(stringview_wtf8, stringview_wtf8, stringview_wtf8);
+  assertLUB(stringview_wtf8, stringview_wtf16, any);
+  assertLUB(stringview_wtf8, stringview_iter, any);
+  assertLUB(stringview_wtf8, none, stringview_wtf8);
+  assertLUB(stringview_wtf8, noext, {});
+  assertLUB(stringview_wtf8, nofunc, {});
+  assertLUB(stringview_wtf8, defFunc, {});
+  assertLUB(stringview_wtf8, defStruct, any);
+  assertLUB(stringview_wtf8, defArray, any);
+
+  assertLUB(stringview_wtf16, stringview_wtf16, stringview_wtf16);
+  assertLUB(stringview_wtf16, stringview_iter, any);
+  assertLUB(stringview_wtf16, none, stringview_wtf16);
+  assertLUB(stringview_wtf16, noext, {});
+  assertLUB(stringview_wtf16, nofunc, {});
+  assertLUB(stringview_wtf16, defFunc, {});
+  assertLUB(stringview_wtf16, defStruct, any);
+  assertLUB(stringview_wtf16, defArray, any);
+
+  assertLUB(stringview_iter, stringview_iter, stringview_iter);
+  assertLUB(stringview_iter, none, stringview_iter);
+  assertLUB(stringview_iter, noext, {});
+  assertLUB(stringview_iter, nofunc, {});
+  assertLUB(stringview_iter, defFunc, {});
+  assertLUB(stringview_iter, defStruct, any);
+  assertLUB(stringview_iter, defArray, any);
+
+  assertLUB(none, none, none);
+  assertLUB(none, noext, {});
+  assertLUB(none, nofunc, {});
+  assertLUB(none, defFunc, {});
+  assertLUB(none, defStruct, defStruct);
+  assertLUB(none, defArray, defArray);
+
+  assertLUB(noext, noext, noext);
+  assertLUB(noext, nofunc, {});
+  assertLUB(noext, defFunc, {});
+  assertLUB(noext, defStruct, {});
+  assertLUB(noext, defArray, {});
+
+  assertLUB(nofunc, nofunc, nofunc);
+  assertLUB(nofunc, defFunc, defFunc);
+  assertLUB(nofunc, defStruct, {});
+  assertLUB(nofunc, defArray, {});
+
+  assertLUB(defFunc, defFunc, defFunc);
+  assertLUB(defFunc, defStruct, {});
+  assertLUB(defFunc, defArray, {});
+
+  assertLUB(defStruct, defStruct, defStruct);
+  assertLUB(defStruct, defArray, data);
+
+  assertLUB(defArray, defArray, defArray);
+}
+
+// Test SubTypes utility code.
+TEST_F(NominalTest, TestSubTypes) {
+  Type anyref = Type(HeapType::any, Nullable);
+  Type eqref = Type(HeapType::eq, Nullable);
+
+  // Build type types, the second of which is a subtype.
+  TypeBuilder builder(2);
+  builder[0] = Struct({Field(anyref, Immutable)});
+  builder[1] = Struct({Field(eqref, Immutable)});
+  builder[1].subTypeOf(builder[0]);
+
+  auto result = builder.build();
+  ASSERT_TRUE(result);
+  auto built = *result;
+
+  // Build a tiny wasm module that uses the types, so that we can test the
+  // SubTypes utility code.
+  Module wasm;
+  Builder wasmBuilder(wasm);
+  wasm.addFunction(wasmBuilder.makeFunction(
+    "func",
+    Signature(Type::none, Type::none),
+    {Type(built[0], Nullable), Type(built[1], Nullable)},
+    wasmBuilder.makeNop()));
+  SubTypes subTypes(wasm);
+  auto subTypes0 = subTypes.getStrictSubTypes(built[0]);
+  EXPECT_TRUE(subTypes0.size() == 1 && subTypes0[0] == built[1]);
+  auto subTypes0Inclusive = subTypes.getAllSubTypes(built[0]);
+  EXPECT_TRUE(subTypes0Inclusive.size() == 2 &&
+              subTypes0Inclusive[0] == built[1] &&
+              subTypes0Inclusive[1] == built[0]);
+  auto subTypes1 = subTypes.getStrictSubTypes(built[1]);
+  EXPECT_EQ(subTypes1.size(), 0u);
+}
+
+// Test reuse of a previously built type as supertype.
+TEST_F(NominalTest, TestExistingSuperType) {
+  // Build an initial type A
+  Type A;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Struct();
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A = Type(built[0], Nullable);
+  }
+
+  // Build a type B <: A using a new builder
+  Type B;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Struct();
+    builder.setSubType(0, A.getHeapType());
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    B = Type(built[0], Nullable);
+  }
+
+  // Test that B <: A where A is the initial type A
+  auto superOfB = B.getHeapType().getSuperType();
+  ASSERT_TRUE(superOfB);
+  EXPECT_EQ(*superOfB, A.getHeapType());
+  EXPECT_NE(B.getHeapType(), A.getHeapType());
+}
+
+// Test reuse of a previously built type as supertype, where in isorecursive
+// mode canonicalization is performed.
+TEST_F(IsorecursiveTest, TestExistingSuperType) {
+  // Build an initial type A1
+  Type A1;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Struct();
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A1 = Type(built[0], Nullable);
+  }
+
+  // Build a separate type A2 identical to A1
+  Type A2;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Struct();
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A2 = Type(built[0], Nullable);
+  }
+
+  // Build a type B1 <: A1 using a new builder
+  Type B1;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Struct();
+    builder.setSubType(0, A1.getHeapType());
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    B1 = Type(built[0], Nullable);
+  }
+
+  // Build a type B2 <: A2 using a new builder
+  Type B2;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Struct();
+    builder.setSubType(0, A2.getHeapType());
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    B2 = Type(built[0], Nullable);
+  }
+
+  // Test that A1 == A2 and B1 == B2
+  EXPECT_EQ(A1.getHeapType(), A2.getHeapType());
+  EXPECT_EQ(B1.getHeapType(), B2.getHeapType());
+}
+
+// Test .getMaxDepths() helper.
+TEST_F(NominalTest, TestMaxStructDepths) {
+  /*
+      A
+      |
+      B
+  */
+  HeapType A, B;
+  {
+    TypeBuilder builder(2);
+    builder[0] = Struct();
+    builder[1] = Struct();
+    builder.setSubType(1, builder.getTempHeapType(0));
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A = built[0];
+    B = built[1];
+  }
+
+  SubTypes subTypes({A, B});
+  auto maxDepths = subTypes.getMaxDepths();
+
+  EXPECT_EQ(maxDepths[B], Index(0));
+  EXPECT_EQ(maxDepths[A], Index(1));
+  EXPECT_EQ(maxDepths[HeapType::data], Index(2));
+  EXPECT_EQ(maxDepths[HeapType::eq], Index(3));
+}
+
+TEST_F(NominalTest, TestMaxArrayDepths) {
+  HeapType A;
+  {
+    TypeBuilder builder(1);
+    builder[0] = Array(Field(Type::i32, Immutable));
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A = built[0];
+  }
+
+  SubTypes subTypes({A});
+  auto maxDepths = subTypes.getMaxDepths();
+
+  EXPECT_EQ(maxDepths[A], Index(0));
+  EXPECT_EQ(maxDepths[HeapType::array], Index(1));
+  EXPECT_EQ(maxDepths[HeapType::data], Index(2));
+  EXPECT_EQ(maxDepths[HeapType::eq], Index(3));
+}
+
+// Test .depth() helper.
+TEST_F(NominalTest, TestDepth) {
+  HeapType A, B, C;
+  {
+    TypeBuilder builder(3);
+    builder[0] = Struct();
+    builder[1] = Struct();
+    builder[2] = Array(Field(Type::i32, Immutable));
+    builder.setSubType(1, builder.getTempHeapType(0));
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A = built[0];
+    B = built[1];
+    C = built[2];
+  }
+
+  // any :> eq :> data :> array :> specific array types
+  EXPECT_EQ(HeapType(HeapType::any).getDepth(), 0U);
+  EXPECT_EQ(HeapType(HeapType::eq).getDepth(), 1U);
+  EXPECT_EQ(HeapType(HeapType::data).getDepth(), 2U);
+  EXPECT_EQ(HeapType(HeapType::array).getDepth(), 3U);
+  EXPECT_EQ(A.getDepth(), 3U);
+  EXPECT_EQ(B.getDepth(), 4U);
+  EXPECT_EQ(C.getDepth(), 4U);
+
+  // Signature types are subtypes of func.
+  EXPECT_EQ(HeapType(HeapType::func).getDepth(), 0U);
+  EXPECT_EQ(HeapType(Signature(Type::none, Type::none)).getDepth(), 1U);
+
+  EXPECT_EQ(HeapType(HeapType::ext).getDepth(), 0U);
+
+  EXPECT_EQ(HeapType(HeapType::i31).getDepth(), 2U);
+  EXPECT_EQ(HeapType(HeapType::string).getDepth(), 2U);
+  EXPECT_EQ(HeapType(HeapType::stringview_wtf8).getDepth(), 2U);
+  EXPECT_EQ(HeapType(HeapType::stringview_wtf16).getDepth(), 2U);
+  EXPECT_EQ(HeapType(HeapType::stringview_iter).getDepth(), 2U);
+
+  EXPECT_EQ(HeapType(HeapType::none).getDepth(), size_t(-1));
+  EXPECT_EQ(HeapType(HeapType::nofunc).getDepth(), size_t(-1));
+  EXPECT_EQ(HeapType(HeapType::noext).getDepth(), size_t(-1));
+}
+
+// Test .iterSubTypes() helper.
+TEST_F(NominalTest, TestIterSubTypes) {
+  /*
+        A
+       / \
+      B   C
+           \
+            D
+  */
+  HeapType A, B, C, D;
+  {
+    TypeBuilder builder(4);
+    builder[0] = Struct();
+    builder[1] = Struct();
+    builder[2] = Struct();
+    builder[3] = Struct();
+    builder.setSubType(1, builder.getTempHeapType(0));
+    builder.setSubType(2, builder.getTempHeapType(0));
+    builder.setSubType(3, builder.getTempHeapType(2));
+    auto result = builder.build();
+    ASSERT_TRUE(result);
+    auto built = *result;
+    A = built[0];
+    B = built[1];
+    C = built[2];
+    D = built[3];
+  }
+
+  SubTypes subTypes({A, B, C, D});
+
+  // Helper for comparing sets of types + their corresponding depth.
+  using TypeDepths = std::unordered_set<std::pair<HeapType, Index>>;
+
+  auto getSubTypes = [&](HeapType type, Index depth) {
+    TypeDepths ret;
+    subTypes.iterSubTypes(type, depth, [&](HeapType subType, Index depth) {
+      ret.insert({subType, depth});
+    });
+    return ret;
+  };
+
+  EXPECT_EQ(getSubTypes(A, 0), TypeDepths({{A, 0}}));
+  EXPECT_EQ(getSubTypes(A, 1), TypeDepths({{A, 0}, {B, 1}, {C, 1}}));
+  EXPECT_EQ(getSubTypes(A, 2), TypeDepths({{A, 0}, {B, 1}, {C, 1}, {D, 2}}));
+  EXPECT_EQ(getSubTypes(A, 3), TypeDepths({{A, 0}, {B, 1}, {C, 1}, {D, 2}}));
+
+  EXPECT_EQ(getSubTypes(C, 0), TypeDepths({{C, 0}}));
+  EXPECT_EQ(getSubTypes(C, 1), TypeDepths({{C, 0}, {D, 1}}));
+  EXPECT_EQ(getSubTypes(C, 2), TypeDepths({{C, 0}, {D, 1}}));
+}
diff --git a/test/gtest/wat-lexer.cpp b/test/gtest/wat-lexer.cpp
new file mode 100644
index 0000000..0820f45
--- /dev/null
+++ b/test/gtest/wat-lexer.cpp
@@ -0,0 +1,1517 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmath>
+
+#include "wat-lexer.h"
+#include "gtest/gtest.h"
+
+using namespace wasm::WATParser;
+using namespace std::string_view_literals;
+
+TEST(LexerTest, LexWhitespace) {
+  Token one{"1"sv, IntTok{1, NoSign}};
+  Token two{"2"sv, IntTok{2, NoSign}};
+  Token three{"3"sv, IntTok{3, NoSign}};
+  Token four{"4"sv, IntTok{4, NoSign}};
+  Token five{"5"sv, IntTok{5, NoSign}};
+
+  Lexer lexer(" 1\t2\n3\r4 \n\n\t 5 "sv);
+
+  auto it = lexer.begin();
+  ASSERT_NE(it, lexer.end());
+  Token t1 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t2 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t3 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t4 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t5 = *it++;
+  EXPECT_EQ(it, lexer.end());
+
+  EXPECT_EQ(t1, one);
+  EXPECT_EQ(t2, two);
+  EXPECT_EQ(t3, three);
+  EXPECT_EQ(t4, four);
+  EXPECT_EQ(t5, five);
+
+  EXPECT_EQ(lexer.position(t1), (TextPos{1, 1}));
+  EXPECT_EQ(lexer.position(t2), (TextPos{1, 3}));
+  EXPECT_EQ(lexer.position(t3), (TextPos{2, 0}));
+  EXPECT_EQ(lexer.position(t4), (TextPos{2, 2}));
+  EXPECT_EQ(lexer.position(t5), (TextPos{4, 2}));
+}
+
+TEST(LexerTest, LexLineComment) {
+  Token one{"1"sv, IntTok{1, NoSign}};
+  Token six{"6"sv, IntTok{6, NoSign}};
+
+  Lexer lexer("1;; whee! 2 3\t4\r5\n6"sv);
+
+  auto it = lexer.begin();
+  Token t1 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t2 = *it++;
+  EXPECT_EQ(it, lexer.end());
+
+  EXPECT_EQ(t1, one);
+  EXPECT_EQ(t2, six);
+
+  EXPECT_EQ(lexer.position(t1), (TextPos{1, 0}));
+  EXPECT_EQ(lexer.position(t2), (TextPos{2, 0}));
+}
+
+TEST(LexerTest, LexBlockComment) {
+  Token one{"1"sv, IntTok{1, NoSign}};
+  Token six{"6"sv, IntTok{6, NoSign}};
+
+  Lexer lexer("1(; whoo! 2\n (; \n3\n ;) 4 (;) 5 ;) \n;)6"sv);
+
+  auto it = lexer.begin();
+  Token t1 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t2 = *it++;
+  EXPECT_EQ(it, lexer.end());
+
+  EXPECT_EQ(t1, one);
+  EXPECT_EQ(t2, six);
+
+  EXPECT_EQ(lexer.position(t1), (TextPos{1, 0}));
+  EXPECT_EQ(lexer.position(t2), (TextPos{5, 2}));
+}
+
+TEST(LexerTest, LexParens) {
+  Token left{"("sv, LParenTok{}};
+  Token right{")"sv, RParenTok{}};
+
+  Lexer lexer("(())"sv);
+
+  auto it = lexer.begin();
+  ASSERT_NE(it, lexer.end());
+  Token t1 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t2 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t3 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t4 = *it++;
+  EXPECT_EQ(it, lexer.end());
+
+  EXPECT_EQ(t1, left);
+  EXPECT_EQ(t2, left);
+  EXPECT_EQ(t3, right);
+  EXPECT_EQ(t4, right);
+  EXPECT_TRUE(left.isLParen());
+  EXPECT_TRUE(right.isRParen());
+}
+
+TEST(LexerTest, LexInt) {
+  {
+    Lexer lexer("0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0"sv, IntTok{0, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0"sv, IntTok{0, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0"sv, IntTok{0, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"1"sv, IntTok{1, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+1"sv, IntTok{1, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-1"sv, IntTok{-1ull, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0010"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0010"sv, IntTok{10, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0010"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0010"sv, IntTok{10, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0010"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0010"sv, IntTok{-10ull, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("9999"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"9999"sv, IntTok{9999, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+9999"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+9999"sv, IntTok{9999, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-9999"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-9999"sv, IntTok{-9999ull, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("12_34"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"12_34"sv, IntTok{1234, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("1_2_3_4"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"1_2_3_4"sv, IntTok{1234, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("_1234"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("1234_"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("12__34"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("12cd56"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("18446744073709551615"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"18446744073709551615"sv, IntTok{-1ull, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    // 64-bit unsigned overflow!
+    Lexer lexer("18446744073709551616"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"18446744073709551616"sv,
+                   FloatTok{{}, 18446744073709551616.}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+9223372036854775807"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+9223372036854775807"sv, IntTok{INT64_MAX, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+9223372036854775808"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+9223372036854775808"sv,
+                   IntTok{uint64_t(INT64_MAX) + 1, Pos}};
+    ;
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-9223372036854775808"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-9223372036854775808"sv, IntTok{uint64_t(INT64_MIN), Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-9223372036854775809"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-9223372036854775809"sv,
+                   IntTok{uint64_t(INT64_MIN) - 1, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+}
+
+TEST(LexerTest, LexHexInt) {
+  {
+    Lexer lexer("0x0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x0"sv, IntTok{0, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0x0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0x0"sv, IntTok{0, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0x0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0x0"sv, IntTok{0, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x1"sv, IntTok{1, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0x1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0x1"sv, IntTok{1, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0x1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0x1"sv, IntTok{-1ull, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x0010"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x0010"sv, IntTok{16, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0x0010"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0x0010"sv, IntTok{16, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0x0010"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0x0010"sv, IntTok{-16ull, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0xabcdef"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0xabcdef"sv, IntTok{0xabcdef, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0xABCDEF"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0xABCDEF"sv, IntTok{0xabcdef, Pos}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0xAbCdEf"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0xAbCdEf"sv, IntTok{-0xabcdefull, Neg}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x12_34"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x12_34"sv, IntTok{0x1234, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x1_2_3_4"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x1_2_3_4"sv, IntTok{0x1234, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("_0x1234"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x_1234"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x1234_"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x12__34"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0xg"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x120x34"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+}
+
+TEST(LexerTest, ClassifyInt) {
+  {
+    Lexer lexer("0"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    ASSERT_TRUE(lexer->getU32());
+    ASSERT_TRUE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0ull);
+    EXPECT_EQ(*lexer->getS64(), 0ll);
+    EXPECT_EQ(*lexer->getI64(), 0ull);
+    EXPECT_EQ(*lexer->getU32(), 0u);
+    EXPECT_EQ(*lexer->getS32(), 0);
+    EXPECT_EQ(*lexer->getI32(), 0u);
+    EXPECT_EQ(*lexer->getF64(), 0.0);
+    EXPECT_EQ(*lexer->getF32(), 0.0);
+    EXPECT_FALSE(std::signbit(*lexer->getF64()));
+    EXPECT_FALSE(std::signbit(*lexer->getF32()));
+  }
+  {
+    Lexer lexer("+0"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    ASSERT_TRUE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0ll);
+    EXPECT_EQ(*lexer->getI64(), 0ull);
+    EXPECT_EQ(*lexer->getS32(), 0);
+    EXPECT_EQ(*lexer->getI32(), 0u);
+    EXPECT_EQ(*lexer->getF64(), 0.0);
+    EXPECT_EQ(*lexer->getF32(), 0.0);
+    EXPECT_FALSE(std::signbit(*lexer->getF64()));
+    EXPECT_FALSE(std::signbit(*lexer->getF32()));
+  }
+  {
+    Lexer lexer("-0"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    ASSERT_TRUE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0ll);
+    EXPECT_EQ(*lexer->getI64(), 0ull);
+    EXPECT_EQ(*lexer->getS32(), 0);
+    EXPECT_EQ(*lexer->getI32(), 0u);
+    EXPECT_EQ(*lexer->getF64(), -0.0);
+    EXPECT_EQ(*lexer->getF32(), -0.0);
+    ASSERT_TRUE(std::signbit(*lexer->getF64()));
+    ASSERT_TRUE(std::signbit(*lexer->getF32()));
+  }
+  {
+    Lexer lexer("0x7fff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    ASSERT_TRUE(lexer->getU32());
+    ASSERT_TRUE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0x7fffffffull);
+    EXPECT_EQ(*lexer->getS64(), 0x7fffffffll);
+    EXPECT_EQ(*lexer->getI64(), 0x7fffffffull);
+    EXPECT_EQ(*lexer->getU32(), 0x7fffffffu);
+    EXPECT_EQ(*lexer->getS32(), 0x7fffffff);
+    EXPECT_EQ(*lexer->getI32(), 0x7fffffffu);
+    EXPECT_EQ(*lexer->getF64(), 0x7fffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x7fffffff.p0f);
+  }
+  {
+    Lexer lexer("0x8000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    ASSERT_TRUE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0x80000000ull);
+    EXPECT_EQ(*lexer->getS64(), 0x80000000ll);
+    EXPECT_EQ(*lexer->getI64(), 0x80000000ull);
+    EXPECT_EQ(*lexer->getU32(), 0x80000000u);
+    EXPECT_EQ(*lexer->getI32(), 0x80000000u);
+    EXPECT_EQ(*lexer->getF64(), 0x80000000.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x80000000.p0f);
+  }
+  {
+    Lexer lexer("+0x7fff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    ASSERT_TRUE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0x7fffffffll);
+    EXPECT_EQ(*lexer->getI64(), 0x7fffffffull);
+    EXPECT_EQ(*lexer->getS32(), 0x7fffffff);
+    EXPECT_EQ(*lexer->getI32(), 0x7fffffffu);
+    EXPECT_EQ(*lexer->getF64(), 0x7fffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x7fffffff.p0f);
+  }
+  {
+    Lexer lexer("+0x8000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0x80000000ll);
+    EXPECT_EQ(*lexer->getI64(), 0x80000000ull);
+    EXPECT_EQ(*lexer->getF64(), 0x80000000.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x80000000.p0f);
+  }
+  {
+    Lexer lexer("-0x8000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    ASSERT_TRUE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), -0x80000000ll);
+    EXPECT_EQ(*lexer->getI64(), -0x80000000ull);
+    EXPECT_EQ(*lexer->getS32(), -0x7fffffffll - 1);
+    EXPECT_EQ(*lexer->getI32(), -0x80000000u);
+    EXPECT_EQ(*lexer->getF64(), -0x80000000.p0);
+    EXPECT_EQ(*lexer->getF32(), -0x80000000.p0f);
+  }
+  {
+    Lexer lexer("-0x8000_0001"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), -0x80000001ll);
+    EXPECT_EQ(*lexer->getI64(), -0x80000001ull);
+    EXPECT_EQ(*lexer->getF64(), -0x80000001.p0);
+    EXPECT_EQ(*lexer->getF32(), -0x80000001.p0f);
+  }
+  {
+    Lexer lexer("0xffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    ASSERT_TRUE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    ASSERT_TRUE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0xffffffffull);
+    EXPECT_EQ(*lexer->getS64(), 0xffffffffll);
+    EXPECT_EQ(*lexer->getI64(), 0xffffffffull);
+    EXPECT_EQ(*lexer->getU32(), 0xffffffffu);
+    EXPECT_EQ(*lexer->getI32(), 0xffffffffu);
+    EXPECT_EQ(*lexer->getF64(), 0xffffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0xffffffff.p0f);
+  }
+  {
+    Lexer lexer("0x1_0000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0x100000000ull);
+    EXPECT_EQ(*lexer->getS64(), 0x100000000ll);
+    EXPECT_EQ(*lexer->getI64(), 0x100000000ull);
+    EXPECT_EQ(*lexer->getF64(), 0x100000000.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x100000000.p0f);
+  }
+  {
+    Lexer lexer("+0xffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0xffffffffll);
+    EXPECT_EQ(*lexer->getI64(), 0xffffffffull);
+    EXPECT_EQ(*lexer->getF64(), 0xffffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0xffffffff.p0f);
+  }
+  {
+    Lexer lexer("+0x1_0000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0x100000000ll);
+    EXPECT_EQ(*lexer->getI64(), 0x100000000ull);
+    EXPECT_EQ(*lexer->getF64(), 0x100000000.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x100000000.p0f);
+  }
+  {
+    Lexer lexer("0x7fff_ffff_ffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0x7fffffffffffffffull);
+    EXPECT_EQ(*lexer->getS64(), 0x7fffffffffffffffll);
+    EXPECT_EQ(*lexer->getI64(), 0x7fffffffffffffffull);
+    EXPECT_EQ(*lexer->getF64(), 0x7fffffffffffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x7fffffffffffffff.p0f);
+  }
+  {
+    Lexer lexer("+0x7fff_ffff_ffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), 0x7fffffffffffffffll);
+    EXPECT_EQ(*lexer->getI64(), 0x7fffffffffffffffull);
+    EXPECT_EQ(*lexer->getF64(), 0x7fffffffffffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0x7fffffffffffffff.p0f);
+  }
+  {
+    Lexer lexer("-0x8000_0000_0000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    ASSERT_TRUE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getS64(), -0x7fffffffffffffffll - 1);
+    EXPECT_EQ(*lexer->getI64(), -0x8000000000000000ull);
+    EXPECT_EQ(*lexer->getF64(), -0x8000000000000000.p0);
+    EXPECT_EQ(*lexer->getF32(), -0x8000000000000000.p0f);
+  }
+  {
+    Lexer lexer("0xffff_ffff_ffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getU64());
+    EXPECT_FALSE(lexer->getS64());
+    ASSERT_TRUE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getU64(), 0xffffffffffffffffull);
+    EXPECT_EQ(*lexer->getI64(), 0xffffffffffffffffull);
+    EXPECT_EQ(*lexer->getF64(), 0xffffffffffffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0xffffffffffffffff.p0f);
+  }
+  {
+    Lexer lexer("+0xffff_ffff_ffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+
+    EXPECT_FALSE(lexer->getU64());
+    EXPECT_FALSE(lexer->getS64());
+    EXPECT_FALSE(lexer->getI64());
+    EXPECT_FALSE(lexer->getU32());
+    EXPECT_FALSE(lexer->getS32());
+    EXPECT_FALSE(lexer->getI32());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+
+    EXPECT_EQ(*lexer->getF64(), 0xffffffffffffffff.p0);
+    EXPECT_EQ(*lexer->getF32(), 0xffffffffffffffff.p0f);
+  }
+}
+
+TEST(LexerTest, LexFloat) {
+  {
+    Lexer lexer("42"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42"sv, IntTok{42, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42."sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42."sv, FloatTok{{}, 42.}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.5"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.5"sv, FloatTok{{}, 42.5}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42e0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42e0"sv, FloatTok{{}, 42e0}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.e1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.e1"sv, FloatTok{{}, 42.e1}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42E1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42E1"sv, FloatTok{{}, 42E1}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42e+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42e+2"sv, FloatTok{{}, 42e+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.E-02"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.E-02"sv, FloatTok{{}, 42.E-02}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.0e0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.0e0"sv, FloatTok{{}, 42.0e0}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.0E1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.0E1"sv, FloatTok{{}, 42.0E1}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.0e+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.0e+2"sv, FloatTok{{}, 42.0e+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("42.0E-2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"42.0E-2"sv, FloatTok{{}, 42.0E-2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+42.0e+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+42.0e+2"sv, FloatTok{{}, +42.0e+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-42.0e+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-42.0e+2"sv, FloatTok{{}, -42.0e+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("4_2.0_0e+0_2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"4_2.0_0e+0_2"sv, FloatTok{{}, 42.00e+02}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42.junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42.0junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42.Ejunk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42.e-junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42.e-10junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("+"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42e"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42eABC"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42e0xABC"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("+-42"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("-+42"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42e+-0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42e-+0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42p0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("42P0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+}
+
+TEST(LexerTest, LexHexFloat) {
+  {
+    Lexer lexer("0x4B"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B"sv, IntTok{0x4B, NoSign}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B."sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B."sv, FloatTok{{}, 0x4Bp0}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.5"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.5"sv, FloatTok{{}, 0x4B.5p0}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4Bp0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4Bp0"sv, FloatTok{{}, 0x4Bp0}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.p1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.p1"sv, FloatTok{{}, 0x4B.p1}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4BP1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4BP1"sv, FloatTok{{}, 0x4BP1}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4Bp+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4Bp+2"sv, FloatTok{{}, 0x4Bp+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.P-02"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.P-02"sv, FloatTok{{}, 0x4B.P-02}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.0p0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.0p0"sv, FloatTok{{}, 0x4B.0p0}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.0P1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.0P1"sv, FloatTok{{}, 0x4B.0P1}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.0p+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.0p+2"sv, FloatTok{{}, 0x4B.0p+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4B.0P-2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4B.0P-2"sv, FloatTok{{}, 0x4B.0P-2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+0x4B.0p+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+0x4B.0p+2"sv, FloatTok{{}, +0x4B.0p+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-0x4B.0p+2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-0x4B.0p+2"sv, FloatTok{{}, -0x4B.0p+2}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4_2.0_0p+0_2"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"0x4_2.0_0p+0_2"sv, FloatTok{{}, 0x42.00p+02}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("0x4Bjunk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.0junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.Pjunk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.p-junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.p-10junk"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("+0x"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4Bp"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4BpABC"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4Bp0xABC"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x+0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("+-0x4B"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("-+0x4B"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4Bp+-0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4Bp-+0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.e+0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("0x4B.E-0"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+}
+
+TEST(LexerTest, LexInfinity) {
+  {
+    Lexer lexer("inf"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"inf"sv, FloatTok{{}, INFINITY}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+inf"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+inf"sv, FloatTok{{}, INFINITY}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-inf"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-inf"sv, FloatTok{{}, -INFINITY}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("infjunk"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"infjunk"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("Inf"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("INF"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("infinity"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"infinity"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+}
+
+TEST(LexerTest, LexNan) {
+  {
+    Lexer lexer("nan"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan"sv, FloatTok{{}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+nan"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+nan"sv, FloatTok{{}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-nan"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-nan"sv, FloatTok{{}, -NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0x01"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0x01"sv, FloatTok{{1}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("+nan:0x01"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"+nan:0x01"sv, FloatTok{{1}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("-nan:0x01"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"-nan:0x01"sv, FloatTok{{1}, -NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0x1234"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0x1234"sv, FloatTok{{0x1234}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0xf_ffff_ffff_ffff"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0xf_ffff_ffff_ffff"sv,
+                   FloatTok{{0xfffffffffffff}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nanjunk"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nanjunk", KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0x"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0x"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0xjunk"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0xjunk"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:-0x1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:-0x1"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:+0x1"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:+0x1"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0x0"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0x0"sv, FloatTok{{0}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0x10_0000_0000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0x10_0000_0000_0000"sv,
+                   FloatTok{{0x10000000000000}, NAN}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("nan:0x1_0000_0000_0000_0000"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"nan:0x1_0000_0000_0000_0000"sv, KeywordTok{}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("NAN"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("NaN"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+}
+
+TEST(LexerTest, ClassifyFloat) {
+  constexpr int signif64 = 52;
+  constexpr int signif32 = 23;
+  constexpr uint64_t payloadMask64 = (1ull << signif64) - 1;
+  constexpr uint32_t payloadMask32 = (1u << signif32) - 1;
+  constexpr uint64_t dnanDefault = 1ull << (signif64 - 1);
+  constexpr uint32_t fnanDefault = 1u << (signif32 - 1);
+  {
+    Lexer lexer("340282346638528859811704183484516925440."sv);
+    ASSERT_FALSE(lexer.empty());
+    ASSERT_TRUE(lexer->getF64());
+    EXPECT_TRUE(lexer->getF32());
+    EXPECT_EQ(*lexer->getF64(), FLT_MAX);
+    EXPECT_EQ(*lexer->getF32(), FLT_MAX);
+  }
+  {
+    Lexer lexer("17976931348623157081452742373170435679807056752584499659891747"
+                "68031572607800285387605895586327668781715404589535143824642343"
+                "21326889464182768467546703537516986049910576551282076245490090"
+                "38932894407586850845513394230458323690322294816580855933212334"
+                "8274797826204144723168738177180919299881250404026184124858368"
+                "."sv);
+    ASSERT_FALSE(lexer.empty());
+    ASSERT_TRUE(lexer->getF64());
+    ASSERT_TRUE(lexer->getF32());
+    EXPECT_EQ(*lexer->getF64(), DBL_MAX);
+    EXPECT_EQ(*lexer->getF32(), INFINITY);
+  }
+  {
+    Lexer lexer("nan");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getF64());
+    double d = *lexer->getF64();
+    EXPECT_TRUE(std::isnan(d));
+    EXPECT_FALSE(std::signbit(d));
+    uint64_t dbits;
+    memcpy(&dbits, &d, sizeof(dbits));
+    EXPECT_EQ(dbits & payloadMask64, dnanDefault);
+
+    ASSERT_TRUE(lexer->getF32());
+    float f = *lexer->getF32();
+    EXPECT_TRUE(std::isnan(f));
+    EXPECT_FALSE(std::signbit(f));
+    uint32_t fbits;
+    memcpy(&fbits, &f, sizeof(fbits));
+    EXPECT_EQ(fbits & payloadMask32, fnanDefault);
+  }
+  {
+    Lexer lexer("-nan");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getF64());
+    double d = *lexer->getF64();
+    EXPECT_TRUE(std::isnan(d));
+    EXPECT_TRUE(std::signbit(d));
+    uint64_t dbits;
+    memcpy(&dbits, &d, sizeof(dbits));
+    EXPECT_EQ(dbits & payloadMask64, dnanDefault);
+
+    ASSERT_TRUE(lexer->getF32());
+    float f = *lexer->getF32();
+    EXPECT_TRUE(std::isnan(f));
+    EXPECT_TRUE(std::signbit(f));
+    uint32_t fbits;
+    memcpy(&fbits, &f, sizeof(fbits));
+    EXPECT_EQ(fbits & payloadMask32, fnanDefault);
+  }
+  {
+    Lexer lexer("+nan");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getF64());
+    double d = *lexer->getF64();
+    EXPECT_TRUE(std::isnan(d));
+    EXPECT_FALSE(std::signbit(d));
+    uint64_t dbits;
+    memcpy(&dbits, &d, sizeof(dbits));
+    EXPECT_EQ(dbits & payloadMask64, dnanDefault);
+
+    ASSERT_TRUE(lexer->getF32());
+    float f = *lexer->getF32();
+    EXPECT_TRUE(std::isnan(f));
+    EXPECT_FALSE(std::signbit(f));
+    uint32_t fbits;
+    memcpy(&fbits, &f, sizeof(fbits));
+    EXPECT_EQ(fbits & payloadMask32, fnanDefault);
+  }
+  {
+    Lexer lexer("nan:0x1234");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getF64());
+    double d = *lexer->getF64();
+    EXPECT_TRUE(std::isnan(d));
+    uint64_t dbits;
+    memcpy(&dbits, &d, sizeof(dbits));
+    EXPECT_EQ(dbits & payloadMask64, 0x1234ull);
+
+    ASSERT_TRUE(lexer->getF32());
+    float f = *lexer->getF32();
+    EXPECT_TRUE(std::isnan(f));
+    uint32_t fbits;
+    memcpy(&fbits, &f, sizeof(fbits));
+    EXPECT_EQ(fbits & payloadMask32, 0x1234u);
+  }
+  {
+    Lexer lexer("nan:0x7FFFFF");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getF64());
+    double d = *lexer->getF64();
+    EXPECT_TRUE(std::isnan(d));
+    uint64_t dbits;
+    memcpy(&dbits, &d, sizeof(dbits));
+    EXPECT_EQ(dbits & payloadMask64, 0x7fffffull);
+
+    ASSERT_TRUE(lexer->getF32());
+    float f = *lexer->getF32();
+    EXPECT_TRUE(std::isnan(f));
+    uint32_t fbits;
+    memcpy(&fbits, &f, sizeof(fbits));
+    EXPECT_EQ(fbits & payloadMask32, 0x7fffffu);
+  }
+  {
+    Lexer lexer("nan:0x800000");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_TRUE(lexer->getF64());
+    double d = *lexer->getF64();
+    EXPECT_TRUE(std::isnan(d));
+    uint64_t dbits;
+    memcpy(&dbits, &d, sizeof(dbits));
+    EXPECT_EQ(dbits & payloadMask64, 0x800000ull);
+
+    ASSERT_FALSE(lexer->getF32());
+  }
+  {
+    Lexer lexer("nan:0x0");
+    ASSERT_FALSE(lexer.empty());
+
+    ASSERT_FALSE(lexer->getF64());
+    ASSERT_FALSE(lexer->getF32());
+  }
+}
+
+TEST(LexerTest, LexIdent) {
+  {
+    Lexer lexer("$09azAZ!#$%&'*+-./:<=>?@\\^_`|~"sv);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{"$09azAZ!#$%&'*+-./:<=>?@\\^_`|~"sv, IdTok{}};
+    EXPECT_EQ(*lexer, expected);
+    EXPECT_TRUE(lexer->getID());
+    EXPECT_EQ(*lexer->getID(), "09azAZ!#$%&'*+-./:<=>?@\\^_`|~"sv);
+  }
+  {
+    Lexer lexer("$[]{}"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("$abc[]"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("$"sv);
+    EXPECT_TRUE(lexer.empty());
+  }
+}
+
+TEST(LexerTest, LexString) {
+  {
+    auto pangram = "\"The quick brown fox jumps over the lazy dog\""sv;
+    Lexer lexer(pangram);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{pangram, StringTok{{}}};
+    EXPECT_EQ(*lexer, expected);
+    EXPECT_TRUE(lexer->getString());
+    EXPECT_EQ(*lexer->getString(),
+              "The quick brown fox jumps over the lazy dog"sv);
+  }
+  {
+    auto chars = "\"`~!@#$%^&*()_-+0123456789|,.<>/?;:'\""sv;
+    Lexer lexer(chars);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{chars, StringTok{{}}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    auto escapes = "\"_\\t_\\n_\\r_\\\\_\\\"_\\'_\""sv;
+    Lexer lexer(escapes);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{escapes, StringTok{{"_\t_\n_\r_\\_\"_'_"}}};
+    EXPECT_EQ(*lexer, expected);
+    EXPECT_TRUE(lexer->getString());
+    EXPECT_EQ(*lexer->getString(), "_\t_\n_\r_\\_\"_'_"sv);
+  }
+  {
+    auto escapes = "\"_\\00_\\07_\\20_\\5A_\\7F_\\ff_\\ffff_\""sv;
+    Lexer lexer(escapes);
+    ASSERT_FALSE(lexer.empty());
+    std::string escaped{"_\0_\7_ _Z_\x7f_\xff_\xff"
+                        "ff_"sv};
+    Token expected{escapes, StringTok{{escaped}}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    // _$_£_€_𐍈_
+    auto unicode = "\"_\\u{24}_\\u{00a3}_\\u{20AC}_\\u{10348}_\""sv;
+    Lexer lexer(unicode);
+    ASSERT_FALSE(lexer.empty());
+    std::string escaped{"_$_\xC2\xA3_\xE2\x82\xAC_\xF0\x90\x8D\x88_"};
+    Token expected{unicode, StringTok{{escaped}}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    // _$_£_€_𐍈_
+    auto unicode = "\"_$_\xC2\xA3_\xE2\x82\xAC_\xF0\x90\x8D\x88_\""sv;
+    Lexer lexer(unicode);
+    ASSERT_FALSE(lexer.empty());
+    Token expected{unicode, StringTok{{}}};
+    EXPECT_EQ(*lexer, expected);
+  }
+  {
+    Lexer lexer("\"unterminated"sv);
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"unescaped nul\0\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"unescaped U+19\x19\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"unescaped U+7f\x7f\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"\\ stray backslash\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"short \\f hex escape\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"bad hex \\gg\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"empty unicode \\u{}\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"not unicode \\u{abcdefg}\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"extra chars \\u{123(}\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"unpaired surrogate unicode crimes \\u{d800}\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"more surrogate unicode crimes \\u{dfff}\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+  {
+    Lexer lexer("\"too big \\u{110000}\"");
+    ASSERT_TRUE(lexer.empty());
+  }
+}
+
+TEST(LexerTest, LexKeywords) {
+  Token module{"module"sv, KeywordTok{}};
+  Token type{"type"sv, KeywordTok{}};
+  Token func{"func"sv, KeywordTok{}};
+  Token import{"import"sv, KeywordTok{}};
+  Token reserved{"rEsErVeD"sv, KeywordTok{}};
+
+  Lexer lexer("module type func import rEsErVeD");
+
+  auto it = lexer.begin();
+  ASSERT_NE(it, lexer.end());
+  Token t1 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t2 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t3 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t4 = *it++;
+  ASSERT_NE(it, lexer.end());
+  Token t5 = *it++;
+  EXPECT_EQ(it, lexer.end());
+
+  EXPECT_EQ(t1, module);
+  EXPECT_EQ(t2, type);
+  EXPECT_EQ(t3, func);
+  EXPECT_EQ(t4, import);
+  EXPECT_EQ(t5, reserved);
+
+  EXPECT_TRUE(t1.getKeyword());
+  EXPECT_EQ(*t1.getKeyword(), "module"sv);
+}
diff --git a/test/heap-types.wast b/test/heap-types.wast
index 3c0a5a0..35e866e 100644
--- a/test/heap-types.wast
+++ b/test/heap-types.wast
@@ -29,25 +29,18 @@
   (type $bytes (array (mut i8)))
   (type $words (array (mut i32)))
 
-  ;; RTT
   (type $parent (struct))
   (type $child (struct i32))
   (type $grandchild (struct i32 i64))
-  (global $rttparent (rtt 0 $parent) (rtt.canon $parent))
-  (global $rttchild (rtt 1 $child) (rtt.sub $child (global.get $rttparent)))
-  (global $rttgrandchild (rtt 2 $grandchild) (rtt.sub $grandchild (global.get $rttchild)))
-  (global $rttfreshgrandchild (rtt 2 $grandchild) (rtt.fresh_sub $grandchild (global.get $rttchild)))
 
   (type $nested-child-struct (struct (field (mut (ref $child)))))
   (type $nested-child-array (array (mut (ref $child))))
 
   (global $struct.new-in-global (ref $struct.A)
-    (struct.new_default_with_rtt $struct.A
-      (rtt.canon $struct.A)
-    )
+    (struct.new_default $struct.A)
   )
 
-  (func $structs (param $x (ref $struct.A)) (result (ref $struct.B))
+  (func $structs (param $x (ref $struct.A)) (param $struct.A.prime (ref null $struct.A.prime)) (param $grandchild (ref null $grandchild)) (param $struct.C (ref null $struct.C)) (param $nested-child-struct (ref null $nested-child-struct)) (result (ref $struct.B))
     (local $tA (ref null $struct.A))
     (local $tB (ref null $struct.B))
     (local $tc (ref null $struct.C))
@@ -69,7 +62,7 @@
       (struct.get $struct.A $named (local.get $x))
     )
     (drop
-      (struct.get $struct.A.prime $othername (ref.null $struct.A.prime))
+      (struct.get $struct.A.prime $othername (local.get $struct.A.prime))
     )
     (drop
       (struct.get_u $struct.B 0 (local.get $tB))
@@ -79,10 +72,7 @@
     )
     ;; immutable fields allow subtyping.
     (drop
-      (struct.get $child 0 (ref.null $grandchild))
-    )
-    (drop
-      (ref.null $struct.A)
+      (struct.get $child 0 (local.get $grandchild))
     )
     (drop
       (block (result (ref null $struct.A))
@@ -109,47 +99,42 @@
       )
     )
     (struct.set $struct.C 0
-      (ref.null $struct.C)
+      (local.get $struct.C)
       (f32.const 100)
     )
     ;; values may be subtypes
     (struct.set $nested-child-struct 0
-      (ref.null $nested-child-struct)
+      (local.get $nested-child-struct)
       (ref.as_non_null
-       (ref.null $grandchild)
+       (local.get $grandchild)
       )
     )
     (drop
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (drop
-      (struct.new_with_rtt $struct.A
+      (struct.new $struct.A
         (i32.const 1)
         (f32.const 2.345)
         (f64.const 3.14159)
-        (rtt.canon $struct.A)
       )
     )
     (unreachable)
   )
-  (func $arrays (param $x (ref $vector)) (result (ref $matrix))
+  (func $arrays (param $x (ref $vector)) (param $nested-child-array (ref null $nested-child-array)) (param $grandchild (ref null $grandchild)) (result (ref $matrix))
     (local $tv (ref null $vector))
     (local $tm (ref null $matrix))
     (local $tb (ref null $bytes))
     (local $tw (ref null $words))
     (drop
-      (array.new_with_rtt $vector
+      (array.new $vector
         (f64.const 3.14159)
         (i32.const 3)
-        (rtt.canon $vector)
       )
     )
     (drop
-      (array.new_default_with_rtt $matrix
+      (array.new_default $matrix
         (i32.const 10)
-        (rtt.canon $matrix)
       )
     )
     (drop
@@ -165,10 +150,10 @@
     )
     ;; values may be subtypes
     (array.set $nested-child-array
-      (ref.null $nested-child-array)
+      (local.get $nested-child-array)
       (i32.const 3)
       (ref.as_non_null
-       (ref.null $grandchild)
+       (local.get $grandchild)
       )
     )
     (drop
@@ -196,42 +181,6 @@
     )
     (unreachable)
   )
-  ;; RTT types as parameters
-  (func $rtt-param-with-depth (param $rtt (rtt 1 $parent)))
-  (func $rtt-param-without-depth (param $rtt (rtt $parent)))
-  (func $rtt-operations
-    (local $temp.A (ref null $struct.A))
-    (local $temp.B (ref null $struct.B))
-    (drop
-      (ref.test (ref.null $struct.A) (rtt.canon $struct.B))
-    )
-    (drop
-      (ref.cast (ref.null $struct.A) (rtt.canon $struct.B))
-    )
-    (drop
-      (block $out (result (ref $struct.B))
-        ;; set the value to a local with type $struct.A, showing that the value
-        ;; flowing out has the right type
-        (local.set $temp.A
-          (br_on_cast $out (ref.null $struct.A) (rtt.canon $struct.B))
-        )
-        ;; an untaken br_on_cast, with unreachable rtt - so we cannot use the
-        ;; RTT in binaryen IR to find the cast type.
-        (br_on_cast $out (ref.null $struct.A) (unreachable))
-        (unreachable)
-      )
-    )
-    (drop
-      (block $out2 (result (ref null $struct.A))
-        ;; set the value to a local with type $struct.A, showing that the value
-        ;; flowing out has the right type
-        (local.set $temp.B
-          (br_on_cast_fail $out2 (ref.null $struct.A) (rtt.canon $struct.B))
-        )
-        (ref.null $struct.A)
-      )
-    )
-  )
   (func $ref.is_X (param $x anyref)
     (if (ref.is_func (local.get $x)) (unreachable))
     (if (ref.is_data (local.get $x)) (unreachable))
@@ -314,8 +263,8 @@
       (struct.get $struct.C 0 (unreachable))
     )
   )
-  (func $unreachables-2
-    (struct.set $struct.C 0 (ref.null $struct.C) (unreachable))
+  (func $unreachables-2 (param $struct.C (ref null $struct.C))
+    (struct.set $struct.C 0 (local.get $struct.C) (unreachable))
   )
   (func $unreachables-3
     (struct.set $struct.C 0 (unreachable) (unreachable))
@@ -329,9 +278,9 @@
       (i32.const 2)
     )
   )
-  (func $unreachables-array-2
+  (func $unreachables-array-2 (param $vector (ref null $vector))
     (array.get $vector
-      (ref.null $vector)
+      (local.get $vector)
       (unreachable)
     )
   )
@@ -342,16 +291,16 @@
       (f64.const 2.18281828)
     )
   )
-  (func $unreachables-array-4
+  (func $unreachables-array-4 (param $vector (ref null $vector))
     (array.set $vector
-      (ref.null $vector)
+      (local.get $vector)
       (unreachable)
       (f64.const 2.18281828)
     )
   )
-  (func $unreachables-array-5
+  (func $unreachables-array-5 (param $vector (ref null $vector))
     (array.set $vector
-      (ref.null $vector)
+      (local.get $vector)
       (i32.const 2)
       (unreachable)
     )
@@ -363,13 +312,6 @@
       )
     )
   )
-  (func $unreachables-7
-    (drop
-      (struct.new_default_with_rtt $struct.A
-        (unreachable)
-      )
-    )
-  )
   (func $array-copy (param $x (ref $vector)) (param $y (ref null $vector))
     (array.copy $vector $vector
       (local.get $x)
@@ -380,20 +322,18 @@
     )
   )
   (func $array-init (result (ref $vector))
-    (array.init $vector
+    (array.init_static $vector
       (f64.const 1)
       (f64.const 2)
       (f64.const 4)
       (f64.const 8)
-      (rtt.canon $vector)
     )
   )
   (func $array-init-packed (result (ref $bytes))
-    (array.init $bytes
+    (array.init_static $bytes
       (i32.const 4)
       (i32.const 2)
       (i32.const 1)
-      (rtt.canon $bytes)
     )
   )
   (func $static-operations
@@ -422,35 +362,4 @@
       )
     )
   )
-  (func $static-constructions
-    (drop
-      (struct.new_default $struct.A)
-    )
-    (drop
-      (struct.new $struct.A
-        (i32.const 1)
-        (f32.const 2.345)
-        (f64.const 3.14159)
-      )
-    )
-    (drop
-      (array.new $vector
-        (f64.const 3.14159)
-        (i32.const 3)
-      )
-    )
-    (drop
-      (array.new_default $matrix
-        (i32.const 10)
-      )
-    )
-    (drop
-      (array.init_static $vector
-        (f64.const 1)
-        (f64.const 2)
-        (f64.const 4)
-        (f64.const 8)
-      )
-    )
-  )
 )
diff --git a/test/heap-types.wast.from-wast b/test/heap-types.wast.from-wast
index 937b4e6..dc41451 100644
--- a/test/heap-types.wast.from-wast
+++ b/test/heap-types.wast.from-wast
@@ -1,39 +1,26 @@
 (module
  (type $struct.A (struct (field i32) (field f32) (field $named f64)))
- (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
  (type $vector (array (mut f64)))
+ (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
  (type $none_=>_none (func))
- (type $grandchild (struct (field i32) (field i64)))
- (type $matrix (array (mut (ref null $vector))))
  (type $struct.C (struct (field $named-mut (mut f32))))
- (type $parent (struct ))
- (type $child (struct (field i32)))
+ (type $matrix (array (mut (ref null $vector))))
  (type $bytes (array (mut i8)))
+ (type $grandchild (struct (field i32) (field i64)))
  (type $anyref_=>_none (func (param anyref)))
+ (type $ref?|$vector|_=>_none (func (param (ref null $vector))))
  (type $nested-child-struct (struct (field (mut (ref $child)))))
- (type $ref|$struct.A|_=>_ref|$struct.B| (func (param (ref $struct.A)) (result (ref $struct.B))))
- (type $ref|$vector|_=>_ref|$matrix| (func (param (ref $vector)) (result (ref $matrix))))
  (type $words (array (mut i32)))
  (type $nested-child-array (array (mut (ref $child))))
- (type $rtt_1_$parent_=>_none (func (param (rtt 1 $parent))))
- (type $rtt_$parent_=>_none (func (param (rtt $parent))))
+ (type $child (struct (field i32)))
+ (type $ref|$struct.A|_ref?|$struct.A|_ref?|$grandchild|_ref?|$struct.C|_ref?|$nested-child-struct|_=>_ref|$struct.B| (func (param (ref $struct.A) (ref null $struct.A) (ref null $grandchild) (ref null $struct.C) (ref null $nested-child-struct)) (result (ref $struct.B))))
+ (type $ref|$vector|_ref?|$nested-child-array|_ref?|$grandchild|_=>_ref|$matrix| (func (param (ref $vector) (ref null $nested-child-array) (ref null $grandchild)) (result (ref $matrix))))
+ (type $ref?|$struct.C|_=>_none (func (param (ref null $struct.C))))
  (type $ref|$vector|_ref?|$vector|_=>_none (func (param (ref $vector) (ref null $vector))))
  (type $none_=>_ref|$vector| (func (result (ref $vector))))
  (type $none_=>_ref|$bytes| (func (result (ref $bytes))))
- (global $rttparent (rtt 0 $parent) (rtt.canon $parent))
- (global $rttchild (rtt 1 $child) (rtt.sub $child
-  (global.get $rttparent)
- ))
- (global $rttgrandchild (rtt 2 $grandchild) (rtt.sub $grandchild
-  (global.get $rttchild)
- ))
- (global $rttfreshgrandchild (rtt 2 $grandchild) (rtt.fresh_sub $grandchild
-  (global.get $rttchild)
- ))
- (global $struct.new-in-global (ref $struct.A) (struct.new_default_with_rtt $struct.A
-  (rtt.canon $struct.A)
- ))
- (func $structs (param $x (ref $struct.A)) (result (ref $struct.B))
+ (global $struct.new-in-global (ref $struct.A) (struct.new_default $struct.A))
+ (func $structs (param $x (ref $struct.A)) (param $struct.A.prime (ref null $struct.A)) (param $grandchild (ref null $grandchild)) (param $struct.C (ref null $struct.C)) (param $nested-child-struct (ref null $nested-child-struct)) (result (ref $struct.B))
   (local $tA (ref null $struct.A))
   (local $tB (ref null $struct.B))
   (local $tc (ref null $struct.C))
@@ -64,7 +51,7 @@
   )
   (drop
    (struct.get $struct.A $named
-    (ref.null $struct.A)
+    (local.get $struct.A.prime)
    )
   )
   (drop
@@ -79,14 +66,11 @@
   )
   (drop
    (struct.get $grandchild 0
-    (ref.null $grandchild)
+    (local.get $grandchild)
    )
   )
   (drop
-   (ref.null $struct.A)
-  )
-  (drop
-   (block $block (result (ref null $struct.A))
+   (block (result (ref null $struct.A))
     (local.get $x)
    )
   )
@@ -110,46 +94,41 @@
    )
   )
   (struct.set $struct.C $named-mut
-   (ref.null $struct.C)
+   (local.get $struct.C)
    (f32.const 100)
   )
   (struct.set $nested-child-struct 0
-   (ref.null $nested-child-struct)
+   (local.get $nested-child-struct)
    (ref.as_non_null
-    (ref.null $grandchild)
+    (local.get $grandchild)
    )
   )
   (drop
-   (struct.new_default_with_rtt $struct.A
-    (rtt.canon $struct.A)
-   )
+   (struct.new_default $struct.A)
   )
   (drop
-   (struct.new_with_rtt $struct.A
+   (struct.new $struct.A
     (i32.const 1)
     (f32.const 2.3450000286102295)
     (f64.const 3.14159)
-    (rtt.canon $struct.A)
    )
   )
   (unreachable)
  )
- (func $arrays (param $x (ref $vector)) (result (ref $matrix))
+ (func $arrays (param $x (ref $vector)) (param $nested-child-array (ref null $nested-child-array)) (param $grandchild (ref null $grandchild)) (result (ref $matrix))
   (local $tv (ref null $vector))
   (local $tm (ref null $matrix))
   (local $tb (ref null $bytes))
   (local $tw (ref null $words))
   (drop
-   (array.new_with_rtt $vector
+   (array.new $vector
     (f64.const 3.14159)
     (i32.const 3)
-    (rtt.canon $vector)
    )
   )
   (drop
-   (array.new_default_with_rtt $matrix
+   (array.new_default $matrix
     (i32.const 10)
-    (rtt.canon $matrix)
    )
   )
   (drop
@@ -164,14 +143,14 @@
    (f64.const 2.18281828)
   )
   (array.set $nested-child-array
-   (ref.null $nested-child-array)
+   (local.get $nested-child-array)
    (i32.const 3)
    (ref.as_non_null
-    (ref.null $grandchild)
+    (local.get $grandchild)
    )
   )
   (drop
-   (array.len $vector
+   (array.len
     (local.get $x)
    )
   )
@@ -195,56 +174,6 @@
   )
   (unreachable)
  )
- (func $rtt-param-with-depth (param $rtt (rtt 1 $parent))
-  (nop)
- )
- (func $rtt-param-without-depth (param $rtt (rtt $parent))
-  (nop)
- )
- (func $rtt-operations
-  (local $temp.A (ref null $struct.A))
-  (local $temp.B (ref null $struct.B))
-  (drop
-   (ref.test
-    (ref.null $struct.A)
-    (rtt.canon $struct.B)
-   )
-  )
-  (drop
-   (ref.cast
-    (ref.null $struct.A)
-    (rtt.canon $struct.B)
-   )
-  )
-  (drop
-   (block $out (result (ref $struct.B))
-    (local.set $temp.A
-     (br_on_cast $out
-      (ref.null $struct.A)
-      (rtt.canon $struct.B)
-     )
-    )
-    (block
-     (drop
-      (ref.null $struct.A)
-     )
-     (unreachable)
-    )
-    (unreachable)
-   )
-  )
-  (drop
-   (block $out2 (result (ref null $struct.A))
-    (local.set $temp.B
-     (br_on_cast_fail $out2
-      (ref.null $struct.A)
-      (rtt.canon $struct.B)
-     )
-    )
-    (ref.null $struct.A)
-   )
-  )
- )
  (func $ref.is_X (param $x anyref)
   (if
    (ref.is_func
@@ -291,8 +220,8 @@
   (local $y anyref)
   (local $z anyref)
   (local $temp-func funcref)
-  (local $temp-data (ref null data))
-  (local $temp-i31 (ref null i31))
+  (local $temp-data dataref)
+  (local $temp-i31 i31ref)
   (block $null
    (local.set $z
     (br_on_null $null
@@ -307,27 +236,27 @@
       (local.get $x)
      )
     )
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
-   (block $data (result (ref null data))
+   (block $data (result dataref)
     (local.set $y
      (br_on_data $data
       (local.get $x)
      )
     )
-    (ref.null data)
+    (ref.null none)
    )
   )
   (drop
-   (block $i31 (result (ref null i31))
+   (block $i31 (result i31ref)
     (local.set $y
      (br_on_i31 $i31
       (local.get $x)
      )
     )
-    (ref.null i31)
+    (ref.null none)
    )
   )
   (drop
@@ -345,7 +274,7 @@
       (local.get $x)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -355,7 +284,7 @@
       (local.get $x)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -365,7 +294,7 @@
       (local.get $x)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
@@ -375,12 +304,13 @@
     (drop
      (unreachable)
     )
+    (unreachable)
    )
   )
  )
- (func $unreachables-2
+ (func $unreachables-2 (param $struct.C (ref null $struct.C))
   (struct.set $struct.C $named-mut
-   (ref.null $struct.C)
+   (local.get $struct.C)
    (unreachable)
   )
  )
@@ -392,6 +322,7 @@
    (drop
     (unreachable)
    )
+   (unreachable)
   )
  )
  (func $unreachables-4
@@ -402,6 +333,7 @@
    (drop
     (f32.const 1)
    )
+   (unreachable)
   )
  )
  (func $unreachables-array-1
@@ -412,11 +344,12 @@
    (drop
     (i32.const 2)
    )
+   (unreachable)
   )
  )
- (func $unreachables-array-2
+ (func $unreachables-array-2 (param $vector (ref null $vector))
   (array.get $vector
-   (ref.null $vector)
+   (local.get $vector)
    (unreachable)
   )
  )
@@ -431,38 +364,30 @@
    (drop
     (f64.const 2.18281828)
    )
+   (unreachable)
   )
  )
- (func $unreachables-array-4
+ (func $unreachables-array-4 (param $vector (ref null $vector))
   (array.set $vector
-   (ref.null $vector)
+   (local.get $vector)
    (unreachable)
    (f64.const 2.18281828)
   )
  )
- (func $unreachables-array-5
+ (func $unreachables-array-5 (param $vector (ref null $vector))
   (array.set $vector
-   (ref.null $vector)
+   (local.get $vector)
    (i32.const 2)
    (unreachable)
   )
  )
  (func $unreachables-array-6
   (drop
-   (block
+   (array.len
     (unreachable)
    )
   )
  )
- (func $unreachables-7
-  (drop
-   (block ;; (replaces something unreachable we can't emit)
-    (drop
-     (unreachable)
-    )
-   )
-  )
- )
  (func $array-copy (param $x (ref $vector)) (param $y (ref null $vector))
   (array.copy $vector $vector
    (local.get $x)
@@ -473,20 +398,18 @@
   )
  )
  (func $array-init (result (ref $vector))
-  (array.init $vector
+  (array.init_static $vector
    (f64.const 1)
    (f64.const 2)
    (f64.const 4)
    (f64.const 8)
-   (rtt.canon $vector)
   )
  )
  (func $array-init-packed (result (ref $bytes))
-  (array.init $bytes
+  (array.init_static $bytes
    (i32.const 4)
    (i32.const 2)
    (i32.const 1)
-   (rtt.canon $bytes)
   )
  )
  (func $static-operations
@@ -494,19 +417,19 @@
   (local $temp.B (ref null $struct.B))
   (drop
    (ref.test_static $struct.B
-    (ref.null $struct.A)
+    (ref.null none)
    )
   )
   (drop
    (ref.cast_static $struct.B
-    (ref.null $struct.A)
+    (ref.null none)
    )
   )
   (drop
    (block $out-B (result (ref $struct.B))
     (local.set $temp.A
      (br_on_cast_static $out-B $struct.B
-      (ref.null $struct.A)
+      (ref.null none)
      )
     )
     (unreachable)
@@ -516,42 +439,11 @@
    (block $out-A (result (ref null $struct.A))
     (local.set $temp.B
      (br_on_cast_static_fail $out-A $struct.B
-      (ref.null $struct.A)
+      (ref.null none)
      )
     )
     (unreachable)
    )
   )
  )
- (func $static-constructions
-  (drop
-   (struct.new_default $struct.A)
-  )
-  (drop
-   (struct.new $struct.A
-    (i32.const 1)
-    (f32.const 2.3450000286102295)
-    (f64.const 3.14159)
-   )
-  )
-  (drop
-   (array.new $vector
-    (f64.const 3.14159)
-    (i32.const 3)
-   )
-  )
-  (drop
-   (array.new_default $matrix
-    (i32.const 10)
-   )
-  )
-  (drop
-   (array.init_static $vector
-    (f64.const 1)
-    (f64.const 2)
-    (f64.const 4)
-    (f64.const 8)
-   )
-  )
- )
 )
diff --git a/test/heap-types.wast.fromBinary b/test/heap-types.wast.fromBinary
index f6b364b..f16e07c 100644
--- a/test/heap-types.wast.fromBinary
+++ b/test/heap-types.wast.fromBinary
@@ -1,39 +1,26 @@
 (module
  (type $struct.A (struct (field i32) (field f32) (field $named f64)))
- (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
  (type $vector (array (mut f64)))
+ (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
  (type $none_=>_none (func))
- (type $grandchild (struct (field i32) (field i64)))
  (type $matrix (array (mut (ref null $vector))))
- (type $struct.C (struct (field $named-mut (mut f32))))
- (type $parent (struct ))
- (type $child (struct (field i32)))
  (type $bytes (array (mut i8)))
+ (type $struct.C (struct (field $named-mut (mut f32))))
+ (type $grandchild (struct (field i32) (field i64)))
  (type $anyref_=>_none (func (param anyref)))
+ (type $ref?|$vector|_=>_none (func (param (ref null $vector))))
  (type $nested-child-struct (struct (field (mut (ref $child)))))
- (type $ref|$struct.A|_=>_ref|$struct.B| (func (param (ref $struct.A)) (result (ref $struct.B))))
- (type $ref|$vector|_=>_ref|$matrix| (func (param (ref $vector)) (result (ref $matrix))))
  (type $words (array (mut i32)))
  (type $nested-child-array (array (mut (ref $child))))
- (type $rtt_1_$parent_=>_none (func (param (rtt 1 $parent))))
- (type $rtt_$parent_=>_none (func (param (rtt $parent))))
+ (type $child (struct (field i32)))
+ (type $ref|$struct.A|_ref?|$struct.A|_ref?|$grandchild|_ref?|$struct.C|_ref?|$nested-child-struct|_=>_ref|$struct.B| (func (param (ref $struct.A) (ref null $struct.A) (ref null $grandchild) (ref null $struct.C) (ref null $nested-child-struct)) (result (ref $struct.B))))
+ (type $ref|$vector|_ref?|$nested-child-array|_ref?|$grandchild|_=>_ref|$matrix| (func (param (ref $vector) (ref null $nested-child-array) (ref null $grandchild)) (result (ref $matrix))))
+ (type $ref?|$struct.C|_=>_none (func (param (ref null $struct.C))))
  (type $ref|$vector|_ref?|$vector|_=>_none (func (param (ref $vector) (ref null $vector))))
  (type $none_=>_ref|$vector| (func (result (ref $vector))))
  (type $none_=>_ref|$bytes| (func (result (ref $bytes))))
- (global $rttparent (rtt 0 $parent) (rtt.canon $parent))
- (global $rttchild (rtt 1 $child) (rtt.sub $child
-  (global.get $rttparent)
- ))
- (global $rttgrandchild (rtt 2 $grandchild) (rtt.sub $grandchild
-  (global.get $rttchild)
- ))
- (global $rttfreshgrandchild (rtt 2 $grandchild) (rtt.fresh_sub $grandchild
-  (global.get $rttchild)
- ))
- (global $struct.new-in-global (ref $struct.A) (struct.new_default_with_rtt $struct.A
-  (rtt.canon $struct.A)
- ))
- (func $structs (param $x (ref $struct.A)) (result (ref $struct.B))
+ (global $struct.new-in-global (ref $struct.A) (struct.new_default $struct.A))
+ (func $structs (param $x (ref $struct.A)) (param $struct.A.prime (ref null $struct.A)) (param $grandchild (ref null $grandchild)) (param $struct.C (ref null $struct.C)) (param $nested-child-struct (ref null $nested-child-struct)) (result (ref $struct.B))
   (local $tA (ref null $struct.A))
   (local $tB (ref null $struct.B))
   (local $tc (ref null $struct.C))
@@ -64,7 +51,7 @@
   )
   (drop
    (struct.get $struct.A $named
-    (ref.null $struct.A)
+    (local.get $struct.A.prime)
    )
   )
   (drop
@@ -79,16 +66,11 @@
   )
   (drop
    (struct.get $grandchild 0
-    (ref.null $grandchild)
+    (local.get $grandchild)
    )
   )
   (drop
-   (ref.null $struct.A)
-  )
-  (drop
-   (block $label$1 (result (ref null $struct.A))
-    (local.get $x)
-   )
+   (local.get $x)
   )
   (drop
    (if (result (ref null $struct.A))
@@ -98,7 +80,7 @@
    )
   )
   (drop
-   (loop $label$4 (result (ref null $struct.A))
+   (loop $label$3 (result (ref null $struct.A))
     (local.get $x)
    )
   )
@@ -110,46 +92,41 @@
    )
   )
   (struct.set $struct.C $named-mut
-   (ref.null $struct.C)
+   (local.get $struct.C)
    (f32.const 100)
   )
   (struct.set $nested-child-struct 0
-   (ref.null $nested-child-struct)
+   (local.get $nested-child-struct)
    (ref.as_non_null
-    (ref.null $grandchild)
+    (local.get $grandchild)
    )
   )
   (drop
-   (struct.new_default_with_rtt $struct.A
-    (rtt.canon $struct.A)
-   )
+   (struct.new_default $struct.A)
   )
   (drop
-   (struct.new_with_rtt $struct.A
+   (struct.new $struct.A
     (i32.const 1)
     (f32.const 2.3450000286102295)
     (f64.const 3.14159)
-    (rtt.canon $struct.A)
    )
   )
   (unreachable)
  )
- (func $arrays (param $x (ref $vector)) (result (ref $matrix))
+ (func $arrays (param $x (ref $vector)) (param $nested-child-array (ref null $nested-child-array)) (param $grandchild (ref null $grandchild)) (result (ref $matrix))
   (local $tv (ref null $vector))
   (local $tm (ref null $matrix))
   (local $tb (ref null $bytes))
   (local $tw (ref null $words))
   (drop
-   (array.new_with_rtt $vector
+   (array.new $vector
     (f64.const 3.14159)
     (i32.const 3)
-    (rtt.canon $vector)
    )
   )
   (drop
-   (array.new_default_with_rtt $matrix
+   (array.new_default $matrix
     (i32.const 10)
-    (rtt.canon $matrix)
    )
   )
   (drop
@@ -164,14 +141,14 @@
    (f64.const 2.18281828)
   )
   (array.set $nested-child-array
-   (ref.null $nested-child-array)
+   (local.get $nested-child-array)
    (i32.const 3)
    (ref.as_non_null
-    (ref.null $grandchild)
+    (local.get $grandchild)
    )
   )
   (drop
-   (array.len $vector
+   (array.len
     (local.get $x)
    )
   )
@@ -195,55 +172,6 @@
   )
   (unreachable)
  )
- (func $rtt-param-with-depth (param $rtt (rtt 1 $parent))
-  (nop)
- )
- (func $rtt-param-without-depth (param $rtt (rtt $parent))
-  (nop)
- )
- (func $rtt-operations
-  (local $temp.A (ref null $struct.A))
-  (local $temp.B (ref null $struct.B))
-  (drop
-   (ref.test
-    (ref.null $struct.A)
-    (rtt.canon $struct.B)
-   )
-  )
-  (drop
-   (ref.cast
-    (ref.null $struct.A)
-    (rtt.canon $struct.B)
-   )
-  )
-  (drop
-   (block $label$1 (result (ref $struct.B))
-    (local.set $temp.A
-     (br_on_cast $label$1
-      (ref.null $struct.A)
-      (rtt.canon $struct.B)
-     )
-    )
-    (block $label$2
-     (drop
-      (ref.null $struct.A)
-     )
-     (unreachable)
-    )
-   )
-  )
-  (drop
-   (block $label$3 (result (ref null $struct.A))
-    (local.set $temp.B
-     (br_on_cast_fail $label$3
-      (ref.null $struct.A)
-      (rtt.canon $struct.B)
-     )
-    )
-    (ref.null $struct.A)
-   )
-  )
- )
  (func $ref.is_X (param $x anyref)
   (if
    (ref.is_func
@@ -290,8 +218,8 @@
   (local $y anyref)
   (local $z anyref)
   (local $temp-func funcref)
-  (local $temp-data (ref null data))
-  (local $temp-i31 (ref null i31))
+  (local $temp-data dataref)
+  (local $temp-i31 i31ref)
   (block $label$1
    (local.set $z
     (br_on_null $label$1
@@ -306,27 +234,27 @@
       (local.get $x)
      )
     )
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
-   (block $label$3 (result (ref null data))
+   (block $label$3 (result dataref)
     (local.set $y
      (br_on_data $label$3
       (local.get $x)
      )
     )
-    (ref.null data)
+    (ref.null none)
    )
   )
   (drop
-   (block $label$4 (result (ref null i31))
+   (block $label$4 (result i31ref)
     (local.set $y
      (br_on_i31 $label$4
       (local.get $x)
      )
     )
-    (ref.null i31)
+    (ref.null none)
    )
   )
   (drop
@@ -344,7 +272,7 @@
       (local.get $x)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -354,7 +282,7 @@
       (local.get $x)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -364,16 +292,16 @@
       (local.get $x)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
  (func $unreachables-1
   (unreachable)
  )
- (func $unreachables-2
+ (func $unreachables-2 (param $struct.C (ref null $struct.C))
   (drop
-   (ref.null $struct.C)
+   (local.get $struct.C)
   )
   (unreachable)
  )
@@ -386,24 +314,24 @@
  (func $unreachables-array-1
   (unreachable)
  )
- (func $unreachables-array-2
+ (func $unreachables-array-2 (param $vector (ref null $vector))
   (drop
-   (ref.null $vector)
+   (local.get $vector)
   )
   (unreachable)
  )
  (func $unreachables-array-3
   (unreachable)
  )
- (func $unreachables-array-4
+ (func $unreachables-array-4 (param $vector (ref null $vector))
   (drop
-   (ref.null $vector)
+   (local.get $vector)
   )
   (unreachable)
  )
- (func $unreachables-array-5
+ (func $unreachables-array-5 (param $vector (ref null $vector))
   (drop
-   (ref.null $vector)
+   (local.get $vector)
   )
   (drop
    (i32.const 2)
@@ -413,9 +341,6 @@
  (func $unreachables-array-6
   (unreachable)
  )
- (func $unreachables-7
-  (unreachable)
- )
  (func $array-copy (param $x (ref $vector)) (param $y (ref null $vector))
   (array.copy $vector $vector
    (local.get $x)
@@ -426,20 +351,18 @@
   )
  )
  (func $array-init (result (ref $vector))
-  (array.init $vector
+  (array.init_static $vector
    (f64.const 1)
    (f64.const 2)
    (f64.const 4)
    (f64.const 8)
-   (rtt.canon $vector)
   )
  )
  (func $array-init-packed (result (ref $bytes))
-  (array.init $bytes
+  (array.init_static $bytes
    (i32.const 4)
    (i32.const 2)
    (i32.const 1)
-   (rtt.canon $bytes)
   )
  )
  (func $static-operations
@@ -447,19 +370,19 @@
   (local $temp.B (ref null $struct.B))
   (drop
    (ref.test_static $struct.B
-    (ref.null $struct.A)
+    (ref.null none)
    )
   )
   (drop
    (ref.cast_static $struct.B
-    (ref.null $struct.A)
+    (ref.null none)
    )
   )
   (drop
    (block $label$1 (result (ref $struct.B))
     (local.set $temp.A
      (br_on_cast_static $label$1 $struct.B
-      (ref.null $struct.A)
+      (ref.null none)
      )
     )
     (unreachable)
@@ -469,43 +392,12 @@
    (block $label$2 (result (ref null $struct.A))
     (local.set $temp.B
      (br_on_cast_static_fail $label$2 $struct.B
-      (ref.null $struct.A)
+      (ref.null none)
      )
     )
     (unreachable)
    )
   )
  )
- (func $static-constructions
-  (drop
-   (struct.new_default $struct.A)
-  )
-  (drop
-   (struct.new $struct.A
-    (i32.const 1)
-    (f32.const 2.3450000286102295)
-    (f64.const 3.14159)
-   )
-  )
-  (drop
-   (array.new $vector
-    (f64.const 3.14159)
-    (i32.const 3)
-   )
-  )
-  (drop
-   (array.new_default $matrix
-    (i32.const 10)
-   )
-  )
-  (drop
-   (array.init_static $vector
-    (f64.const 1)
-    (f64.const 2)
-    (f64.const 4)
-    (f64.const 8)
-   )
-  )
- )
 )
 
diff --git a/test/heap-types.wast.fromBinary.noDebugInfo b/test/heap-types.wast.fromBinary.noDebugInfo
index c33ba0c..53c3b0b 100644
--- a/test/heap-types.wast.fromBinary.noDebugInfo
+++ b/test/heap-types.wast.fromBinary.noDebugInfo
@@ -1,44 +1,31 @@
 (module
  (type ${i32_f32_f64} (struct (field i32) (field f32) (field f64)))
- (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64})))))
  (type $[mut:f64] (array (mut f64)))
+ (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64})))))
  (type $none_=>_none (func))
- (type ${i32_i64} (struct (field i32) (field i64)))
  (type $[mut:ref?|[mut:f64]|] (array (mut (ref null $[mut:f64]))))
- (type ${mut:f32} (struct (field (mut f32))))
- (type ${} (struct ))
- (type ${i32} (struct (field i32)))
  (type $[mut:i8] (array (mut i8)))
+ (type ${mut:f32} (struct (field (mut f32))))
+ (type ${i32_i64} (struct (field i32) (field i64)))
  (type $anyref_=>_none (func (param anyref)))
+ (type $ref?|[mut:f64]|_=>_none (func (param (ref null $[mut:f64]))))
  (type ${mut:ref|{i32}|} (struct (field (mut (ref ${i32})))))
- (type $ref|{i32_f32_f64}|_=>_ref|{i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}| (func (param (ref ${i32_f32_f64})) (result (ref ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))))
- (type $ref|[mut:f64]|_=>_ref|[mut:ref?|[mut:f64]|]| (func (param (ref $[mut:f64])) (result (ref $[mut:ref?|[mut:f64]|]))))
  (type $[mut:i32] (array (mut i32)))
  (type $[mut:ref|{i32}|] (array (mut (ref ${i32}))))
- (type $rtt_1_{}_=>_none (func (param (rtt 1 ${}))))
- (type $rtt_{}_=>_none (func (param (rtt ${}))))
+ (type ${i32} (struct (field i32)))
+ (type $ref|{i32_f32_f64}|_ref?|{i32_f32_f64}|_ref?|{i32_i64}|_ref?|{mut:f32}|_ref?|{mut:ref|{i32}|}|_=>_ref|{i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}| (func (param (ref ${i32_f32_f64}) (ref null ${i32_f32_f64}) (ref null ${i32_i64}) (ref null ${mut:f32}) (ref null ${mut:ref|{i32}|})) (result (ref ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))))
+ (type $ref|[mut:f64]|_ref?|[mut:ref|{i32}|]|_ref?|{i32_i64}|_=>_ref|[mut:ref?|[mut:f64]|]| (func (param (ref $[mut:f64]) (ref null $[mut:ref|{i32}|]) (ref null ${i32_i64})) (result (ref $[mut:ref?|[mut:f64]|]))))
+ (type $ref?|{mut:f32}|_=>_none (func (param (ref null ${mut:f32}))))
  (type $ref|[mut:f64]|_ref?|[mut:f64]|_=>_none (func (param (ref $[mut:f64]) (ref null $[mut:f64]))))
  (type $none_=>_ref|[mut:f64]| (func (result (ref $[mut:f64]))))
  (type $none_=>_ref|[mut:i8]| (func (result (ref $[mut:i8]))))
- (global $global$0 (rtt 0 ${}) (rtt.canon ${}))
- (global $global$1 (rtt 1 ${i32}) (rtt.sub ${i32}
-  (global.get $global$0)
- ))
- (global $global$2 (rtt 2 ${i32_i64}) (rtt.sub ${i32_i64}
-  (global.get $global$1)
- ))
- (global $global$3 (rtt 2 ${i32_i64}) (rtt.fresh_sub ${i32_i64}
-  (global.get $global$1)
- ))
- (global $global$4 (ref ${i32_f32_f64}) (struct.new_default_with_rtt ${i32_f32_f64}
-  (rtt.canon ${i32_f32_f64})
- ))
- (func $0 (param $0 (ref ${i32_f32_f64})) (result (ref ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
-  (local $1 (ref null ${i32_f32_f64}))
-  (local $2 (ref null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
-  (local $3 (ref null ${mut:f32}))
-  (local $4 (ref null $[mut:f64]))
-  (local $5 (ref null $[mut:ref?|[mut:f64]|]))
+ (global $global$0 (ref ${i32_f32_f64}) (struct.new_default ${i32_f32_f64}))
+ (func $0 (param $0 (ref ${i32_f32_f64})) (param $1 (ref null ${i32_f32_f64})) (param $2 (ref null ${i32_i64})) (param $3 (ref null ${mut:f32})) (param $4 (ref null ${mut:ref|{i32}|})) (result (ref ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
+  (local $5 (ref null ${i32_f32_f64}))
+  (local $6 (ref null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
+  (local $7 (ref null ${mut:f32}))
+  (local $8 (ref null $[mut:f64]))
+  (local $9 (ref null $[mut:ref?|[mut:f64]|]))
   (drop
    (local.get $0)
   )
@@ -64,31 +51,26 @@
   )
   (drop
    (struct.get ${i32_f32_f64} 2
-    (ref.null ${i32_f32_f64})
+    (local.get $1)
    )
   )
   (drop
    (struct.get_u ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} 0
-    (local.get $2)
+    (local.get $6)
    )
   )
   (drop
    (struct.get_s ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} 0
-    (local.get $2)
+    (local.get $6)
    )
   )
   (drop
    (struct.get ${i32_i64} 0
-    (ref.null ${i32_i64})
+    (local.get $2)
    )
   )
   (drop
-   (ref.null ${i32_f32_f64})
-  )
-  (drop
-   (block $label$1 (result (ref null ${i32_f32_f64}))
-    (local.get $0)
-   )
+   (local.get $0)
   )
   (drop
    (if (result (ref null ${i32_f32_f64}))
@@ -98,7 +80,7 @@
    )
   )
   (drop
-   (loop $label$4 (result (ref null ${i32_f32_f64}))
+   (loop $label$3 (result (ref null ${i32_f32_f64}))
     (local.get $0)
    )
   )
@@ -110,46 +92,41 @@
    )
   )
   (struct.set ${mut:f32} 0
-   (ref.null ${mut:f32})
+   (local.get $3)
    (f32.const 100)
   )
   (struct.set ${mut:ref|{i32}|} 0
-   (ref.null ${mut:ref|{i32}|})
+   (local.get $4)
    (ref.as_non_null
-    (ref.null ${i32_i64})
+    (local.get $2)
    )
   )
   (drop
-   (struct.new_default_with_rtt ${i32_f32_f64}
-    (rtt.canon ${i32_f32_f64})
-   )
+   (struct.new_default ${i32_f32_f64})
   )
   (drop
-   (struct.new_with_rtt ${i32_f32_f64}
+   (struct.new ${i32_f32_f64}
     (i32.const 1)
     (f32.const 2.3450000286102295)
     (f64.const 3.14159)
-    (rtt.canon ${i32_f32_f64})
    )
   )
   (unreachable)
  )
- (func $1 (param $0 (ref $[mut:f64])) (result (ref $[mut:ref?|[mut:f64]|]))
-  (local $1 (ref null $[mut:f64]))
-  (local $2 (ref null $[mut:ref?|[mut:f64]|]))
-  (local $3 (ref null $[mut:i8]))
-  (local $4 (ref null $[mut:i32]))
+ (func $1 (param $0 (ref $[mut:f64])) (param $1 (ref null $[mut:ref|{i32}|])) (param $2 (ref null ${i32_i64})) (result (ref $[mut:ref?|[mut:f64]|]))
+  (local $3 (ref null $[mut:f64]))
+  (local $4 (ref null $[mut:ref?|[mut:f64]|]))
+  (local $5 (ref null $[mut:i8]))
+  (local $6 (ref null $[mut:i32]))
   (drop
-   (array.new_with_rtt $[mut:f64]
+   (array.new $[mut:f64]
     (f64.const 3.14159)
     (i32.const 3)
-    (rtt.canon $[mut:f64])
    )
   )
   (drop
-   (array.new_default_with_rtt $[mut:ref?|[mut:f64]|]
+   (array.new_default $[mut:ref?|[mut:f64]|]
     (i32.const 10)
-    (rtt.canon $[mut:ref?|[mut:f64]|])
    )
   )
   (drop
@@ -164,87 +141,38 @@
    (f64.const 2.18281828)
   )
   (array.set $[mut:ref|{i32}|]
-   (ref.null $[mut:ref|{i32}|])
+   (local.get $1)
    (i32.const 3)
    (ref.as_non_null
-    (ref.null ${i32_i64})
+    (local.get $2)
    )
   )
   (drop
-   (array.len $[mut:f64]
+   (array.len
     (local.get $0)
    )
   )
   (drop
    (array.get $[mut:i32]
-    (local.get $4)
+    (local.get $6)
     (i32.const 1)
    )
   )
   (drop
    (array.get_u $[mut:i8]
-    (local.get $3)
+    (local.get $5)
     (i32.const 2)
    )
   )
   (drop
    (array.get_s $[mut:i8]
-    (local.get $3)
+    (local.get $5)
     (i32.const 3)
    )
   )
   (unreachable)
  )
- (func $2 (param $0 (rtt 1 ${}))
-  (nop)
- )
- (func $3 (param $0 (rtt ${}))
-  (nop)
- )
- (func $4
-  (local $0 (ref null ${i32_f32_f64}))
-  (local $1 (ref null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
-  (drop
-   (ref.test
-    (ref.null ${i32_f32_f64})
-    (rtt.canon ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|})
-   )
-  )
-  (drop
-   (ref.cast
-    (ref.null ${i32_f32_f64})
-    (rtt.canon ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|})
-   )
-  )
-  (drop
-   (block $label$1 (result (ref ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
-    (local.set $0
-     (br_on_cast $label$1
-      (ref.null ${i32_f32_f64})
-      (rtt.canon ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|})
-     )
-    )
-    (block $label$2
-     (drop
-      (ref.null ${i32_f32_f64})
-     )
-     (unreachable)
-    )
-   )
-  )
-  (drop
-   (block $label$3 (result (ref null ${i32_f32_f64}))
-    (local.set $1
-     (br_on_cast_fail $label$3
-      (ref.null ${i32_f32_f64})
-      (rtt.canon ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|})
-     )
-    )
-    (ref.null ${i32_f32_f64})
-   )
-  )
- )
- (func $5 (param $0 anyref)
+ (func $2 (param $0 anyref)
   (if
    (ref.is_func
     (local.get $0)
@@ -264,7 +192,7 @@
    (unreachable)
   )
  )
- (func $6 (param $0 anyref)
+ (func $3 (param $0 anyref)
   (drop
    (ref.as_non_null
     (local.get $0)
@@ -286,12 +214,12 @@
    )
   )
  )
- (func $7 (param $0 anyref)
+ (func $4 (param $0 anyref)
   (local $1 anyref)
   (local $2 anyref)
   (local $3 funcref)
-  (local $4 (ref null data))
-  (local $5 (ref null i31))
+  (local $4 dataref)
+  (local $5 i31ref)
   (block $label$1
    (local.set $2
     (br_on_null $label$1
@@ -306,27 +234,27 @@
       (local.get $0)
      )
     )
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
-   (block $label$3 (result (ref null data))
+   (block $label$3 (result dataref)
     (local.set $1
      (br_on_data $label$3
       (local.get $0)
      )
     )
-    (ref.null data)
+    (ref.null none)
    )
   )
   (drop
-   (block $label$4 (result (ref null i31))
+   (block $label$4 (result i31ref)
     (local.set $1
      (br_on_i31 $label$4
       (local.get $0)
      )
     )
-    (ref.null i31)
+    (ref.null none)
    )
   )
   (drop
@@ -344,7 +272,7 @@
       (local.get $0)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -354,7 +282,7 @@
       (local.get $0)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -364,59 +292,56 @@
       (local.get $0)
      )
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
- (func $8
+ (func $5
   (unreachable)
  )
- (func $9
+ (func $6 (param $0 (ref null ${mut:f32}))
   (drop
-   (ref.null ${mut:f32})
+   (local.get $0)
   )
   (unreachable)
  )
- (func $10
+ (func $7
   (unreachable)
  )
- (func $11
+ (func $8
   (unreachable)
  )
- (func $12
+ (func $9
   (unreachable)
  )
- (func $13
+ (func $10 (param $0 (ref null $[mut:f64]))
   (drop
-   (ref.null $[mut:f64])
+   (local.get $0)
   )
   (unreachable)
  )
- (func $14
+ (func $11
   (unreachable)
  )
- (func $15
+ (func $12 (param $0 (ref null $[mut:f64]))
   (drop
-   (ref.null $[mut:f64])
+   (local.get $0)
   )
   (unreachable)
  )
- (func $16
+ (func $13 (param $0 (ref null $[mut:f64]))
   (drop
-   (ref.null $[mut:f64])
+   (local.get $0)
   )
   (drop
    (i32.const 2)
   )
   (unreachable)
  )
- (func $17
-  (unreachable)
- )
- (func $18
+ (func $14
   (unreachable)
  )
- (func $19 (param $0 (ref $[mut:f64])) (param $1 (ref null $[mut:f64]))
+ (func $15 (param $0 (ref $[mut:f64])) (param $1 (ref null $[mut:f64]))
   (array.copy $[mut:f64] $[mut:f64]
    (local.get $0)
    (i32.const 11)
@@ -425,41 +350,39 @@
    (i32.const 1337)
   )
  )
- (func $20 (result (ref $[mut:f64]))
-  (array.init $[mut:f64]
+ (func $16 (result (ref $[mut:f64]))
+  (array.init_static $[mut:f64]
    (f64.const 1)
    (f64.const 2)
    (f64.const 4)
    (f64.const 8)
-   (rtt.canon $[mut:f64])
   )
  )
- (func $21 (result (ref $[mut:i8]))
-  (array.init $[mut:i8]
+ (func $17 (result (ref $[mut:i8]))
+  (array.init_static $[mut:i8]
    (i32.const 4)
    (i32.const 2)
    (i32.const 1)
-   (rtt.canon $[mut:i8])
   )
  )
- (func $22
+ (func $18
   (local $0 (ref null ${i32_f32_f64}))
   (local $1 (ref null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
   (drop
    (ref.test_static ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}
-    (ref.null ${i32_f32_f64})
+    (ref.null none)
    )
   )
   (drop
    (ref.cast_static ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}
-    (ref.null ${i32_f32_f64})
+    (ref.null none)
    )
   )
   (drop
    (block $label$1 (result (ref ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
     (local.set $0
      (br_on_cast_static $label$1 ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}
-      (ref.null ${i32_f32_f64})
+      (ref.null none)
      )
     )
     (unreachable)
@@ -469,43 +392,12 @@
    (block $label$2 (result (ref null ${i32_f32_f64}))
     (local.set $1
      (br_on_cast_static_fail $label$2 ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}
-      (ref.null ${i32_f32_f64})
+      (ref.null none)
      )
     )
     (unreachable)
    )
   )
  )
- (func $23
-  (drop
-   (struct.new_default ${i32_f32_f64})
-  )
-  (drop
-   (struct.new ${i32_f32_f64}
-    (i32.const 1)
-    (f32.const 2.3450000286102295)
-    (f64.const 3.14159)
-   )
-  )
-  (drop
-   (array.new $[mut:f64]
-    (f64.const 3.14159)
-    (i32.const 3)
-   )
-  )
-  (drop
-   (array.new_default $[mut:ref?|[mut:f64]|]
-    (i32.const 10)
-   )
-  )
-  (drop
-   (array.init_static $[mut:f64]
-    (f64.const 1)
-    (f64.const 2)
-    (f64.const 4)
-    (f64.const 8)
-   )
-  )
- )
 )
 
diff --git a/test/let.txt b/test/let.txt
deleted file mode 100644
index 9901aaa..0000000
--- a/test/let.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-;; source code for let.wasm, which was created by wasp
-(module
-  (type $vector (array (mut f64)))
-  (func $main
-    (local $x i32)
-    (local $y i32)
-    (drop (local.get $x)) ;; 0 is the index appearing in the binary
-    ;; first let
-    (array.new_with_rtt $vector
-      (f64.const 3.14159)
-      (i32.const 1)
-      (rtt.canon $vector)
-    )
-    (let (local $v (ref $vector))
-      (drop (local.get $v)) ;; 0
-      (drop (local.get $x)) ;; 1
-      ;; another one, nested
-      (array.new_with_rtt $vector
-        (f64.const 1234)
-        (i32.const 2)
-        (rtt.canon $vector)
-      )
-      (let (local $w (ref $vector))
-        (drop (local.get $v)) ;; 1
-        (drop (local.get $w)) ;; 0
-        (drop (local.get $x)) ;; 2
-      )
-    )
-    ;; another one, later
-    (array.new_with_rtt $vector
-      (f64.const 2.1828)
-      (i32.const 3)
-      (rtt.canon $vector)
-    )
-    (let (local $v (ref $vector))
-      (drop (local.get $v)) ;; 0
-      (drop (local.get $x)) ;; 1
-    )
-    (drop (local.get $x)) ;; 0
-  )
-)
-
diff --git a/test/let.wasm b/test/let.wasm
deleted file mode 100644
index 8a24b7d..0000000
Binary files a/test/let.wasm and /dev/null differ
diff --git a/test/let.wasm.fromBinary b/test/let.wasm.fromBinary
deleted file mode 100644
index de51c10..0000000
--- a/test/let.wasm.fromBinary
+++ /dev/null
@@ -1,80 +0,0 @@
-(module
- (type $[mut:f64] (array (mut f64)))
- (type $none_=>_none (func))
- (func $0
-  (local $0 i32)
-  (local $1 i32)
-  (local $2 (ref null $[mut:f64]))
-  (local $3 (ref null $[mut:f64]))
-  (local $4 (ref null $[mut:f64]))
-  (drop
-   (local.get $0)
-  )
-  (block
-   (local.set $2
-    (array.new_with_rtt $[mut:f64]
-     (f64.const 3.14159)
-     (i32.const 1)
-     (rtt.canon $[mut:f64])
-    )
-   )
-   (block
-    (drop
-     (ref.as_non_null
-      (local.get $2)
-     )
-    )
-    (drop
-     (local.get $0)
-    )
-    (block
-     (local.set $3
-      (array.new_with_rtt $[mut:f64]
-       (f64.const 1234)
-       (i32.const 2)
-       (rtt.canon $[mut:f64])
-      )
-     )
-     (block
-      (drop
-       (ref.as_non_null
-        (local.get $2)
-       )
-      )
-      (drop
-       (ref.as_non_null
-        (local.get $3)
-       )
-      )
-      (drop
-       (local.get $0)
-      )
-     )
-    )
-   )
-  )
-  (block
-   (local.set $4
-    (array.new_with_rtt $[mut:f64]
-     (f64.const 2.1828)
-     (i32.const 3)
-     (rtt.canon $[mut:f64])
-    )
-   )
-   (block
-    (drop
-     (ref.as_non_null
-      (local.get $4)
-     )
-    )
-    (drop
-     (local.get $0)
-    )
-   )
-  )
-  (drop
-   (local.get $0)
-  )
- )
-)
-
diff --git a/test/lit/array-new-seg-note-count.wast b/test/lit/array-new-seg-note-count.wast
new file mode 100644
index 0000000..45c08e3
--- /dev/null
+++ b/test/lit/array-new-seg-note-count.wast
@@ -0,0 +1,25 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s
+
+;; Test that the array type is emitted into the type section properly.
+(module
+ ;; CHECK:      (type $vec (array i32))
+ (type $vec (array i32))
+ ;; CHECK:      (type $none_=>_ref|$vec| (func (result (ref $vec))))
+
+ ;; CHECK:      (data "")
+ (data "")
+ ;; CHECK:      (func $test (result (ref $vec))
+ ;; CHECK-NEXT:  (array.new_data $vec 0
+ ;; CHECK-NEXT:   (i32.const 0)
+ ;; CHECK-NEXT:   (i32.const 0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $test (result (ref $vec))
+  (array.new_data $vec 0
+   (i32.const 0)
+   (i32.const 0)
+  )
+ )
+)
diff --git a/test/lit/arrays.wast b/test/lit/arrays.wast
new file mode 100644
index 0000000..bccbaeb
--- /dev/null
+++ b/test/lit/arrays.wast
@@ -0,0 +1,148 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Check that array types and operations are emitted properly in the binary format.
+
+;; RUN: wasm-opt %s -all -S -o - | filecheck %s
+;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=ROUNDTRIP
+
+;; Check that we can roundtrip through the text format as well.
+
+;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK:      (type $arrayref_=>_i32 (func (param arrayref) (result i32)))
+
+ ;; CHECK:      (type $byte-array (array (mut i8)))
+ ;; ROUNDTRIP:      (type $arrayref_=>_i32 (func (param arrayref) (result i32)))
+
+ ;; ROUNDTRIP:      (type $byte-array (array (mut i8)))
+ (type $byte-array (array (mut i8)))
+ ;; CHECK:      (type $func-array (array (mut funcref)))
+ ;; ROUNDTRIP:      (type $func-array (array (mut funcref)))
+ (type $func-array (array (mut funcref)))
+
+ ;; CHECK:      (type $ref|array|_=>_i32 (func (param (ref array)) (result i32)))
+
+ ;; CHECK:      (type $nullref_=>_i32 (func (param nullref) (result i32)))
+
+ ;; CHECK:      (type $none_=>_ref|$byte-array| (func (result (ref $byte-array))))
+
+ ;; CHECK:      (type $none_=>_ref|$func-array| (func (result (ref $func-array))))
+
+ ;; CHECK:      (data "hello")
+ ;; ROUNDTRIP:      (type $ref|array|_=>_i32 (func (param (ref array)) (result i32)))
+
+ ;; ROUNDTRIP:      (type $nullref_=>_i32 (func (param nullref) (result i32)))
+
+ ;; ROUNDTRIP:      (type $none_=>_ref|$byte-array| (func (result (ref $byte-array))))
+
+ ;; ROUNDTRIP:      (type $none_=>_ref|$func-array| (func (result (ref $func-array))))
+
+ ;; ROUNDTRIP:      (data "hello")
+ (data "hello")
+ ;; CHECK:      (elem func $len $impossible-len $unreachable-len)
+ ;; ROUNDTRIP:      (elem func $len $impossible-len $unreachable-len)
+ (elem func $len $impossible-len $unreachable-len)
+
+
+ ;; CHECK:      (func $len (param $a (ref array)) (result i32)
+ ;; CHECK-NEXT:  (array.len
+ ;; CHECK-NEXT:   (local.get $a)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; ROUNDTRIP:      (func $len (param $a (ref array)) (result i32)
+ ;; ROUNDTRIP-NEXT:  (array.len
+ ;; ROUNDTRIP-NEXT:   (local.get $a)
+ ;; ROUNDTRIP-NEXT:  )
+ ;; ROUNDTRIP-NEXT: )
+ (func $len (param $a (ref array)) (result i32)
+  ;; TODO: remove the unused type annotation
+  (array.len $byte-array
+   (local.get $a)
+  )
+ )
+
+ ;; CHECK:      (func $impossible-len (param $none nullref) (result i32)
+ ;; CHECK-NEXT:  (array.len
+ ;; CHECK-NEXT:   (local.get $none)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; ROUNDTRIP:      (func $impossible-len (param $none nullref) (result i32)
+ ;; ROUNDTRIP-NEXT:  (array.len
+ ;; ROUNDTRIP-NEXT:   (local.get $none)
+ ;; ROUNDTRIP-NEXT:  )
+ ;; ROUNDTRIP-NEXT: )
+ (func $impossible-len (param $none nullref) (result i32)
+  (array.len $byte-array
+   (local.get $none)
+  )
+ )
+
+ ;; CHECK:      (func $unreachable-len (param $a arrayref) (result i32)
+ ;; CHECK-NEXT:  (array.len
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; ROUNDTRIP:      (func $unreachable-len (param $a arrayref) (result i32)
+ ;; ROUNDTRIP-NEXT:  (unreachable)
+ ;; ROUNDTRIP-NEXT: )
+ (func $unreachable-len (param $a arrayref) (result i32)
+  (array.len $byte-array
+   (unreachable)
+  )
+ )
+
+ ;; CHECK:      (func $unannotated-len (param $a arrayref) (result i32)
+ ;; CHECK-NEXT:  (array.len
+ ;; CHECK-NEXT:   (local.get $a)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; ROUNDTRIP:      (func $unannotated-len (param $a arrayref) (result i32)
+ ;; ROUNDTRIP-NEXT:  (array.len
+ ;; ROUNDTRIP-NEXT:   (local.get $a)
+ ;; ROUNDTRIP-NEXT:  )
+ ;; ROUNDTRIP-NEXT: )
+ (func $unannotated-len (param $a arrayref) (result i32)
+  (array.len
+   (local.get $a)
+  )
+ )
+
+ ;; CHECK:      (func $new-data (result (ref $byte-array))
+ ;; CHECK-NEXT:  (array.new_data $byte-array 0
+ ;; CHECK-NEXT:   (i32.const 0)
+ ;; CHECK-NEXT:   (i32.const 5)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; ROUNDTRIP:      (func $new-data (result (ref $byte-array))
+ ;; ROUNDTRIP-NEXT:  (array.new_data $byte-array 0
+ ;; ROUNDTRIP-NEXT:   (i32.const 0)
+ ;; ROUNDTRIP-NEXT:   (i32.const 5)
+ ;; ROUNDTRIP-NEXT:  )
+ ;; ROUNDTRIP-NEXT: )
+ (func $new-data (result (ref $byte-array))
+  (array.new_data $byte-array 0
+   (i32.const 0)
+   (i32.const 5)
+  )
+ )
+
+ ;; CHECK:      (func $new-elem (result (ref $func-array))
+ ;; CHECK-NEXT:  (array.new_elem $func-array 0
+ ;; CHECK-NEXT:   (i32.const 0)
+ ;; CHECK-NEXT:   (i32.const 3)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; ROUNDTRIP:      (func $new-elem (result (ref $func-array))
+ ;; ROUNDTRIP-NEXT:  (array.new_elem $func-array 0
+ ;; ROUNDTRIP-NEXT:   (i32.const 0)
+ ;; ROUNDTRIP-NEXT:   (i32.const 3)
+ ;; ROUNDTRIP-NEXT:  )
+ ;; ROUNDTRIP-NEXT: )
+ (func $new-elem (result (ref $func-array))
+  (array.new_elem $func-array 0
+   (i32.const 0)
+   (i32.const 3)
+  )
+ )
+)
diff --git a/test/lit/binary/annotated-array-len.test b/test/lit/binary/annotated-array-len.test
new file mode 100644
index 0000000..a8eb08f
--- /dev/null
+++ b/test/lit/binary/annotated-array-len.test
@@ -0,0 +1,18 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Test the we can properly parse the annotated array.len format that we no
+;; longer emit.
+
+;; RUN: wasm-dis %s.wasm -all --nominal | filecheck %s
+
+;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+;; CHECK:      (type $[mut:i8] (array_subtype (mut i8) data))
+
+;; CHECK:      (func $0 (type $none_=>_i32) (result i32)
+;; CHECK-NEXT:  (array.len
+;; CHECK-NEXT:   (array.new_default $[mut:i8]
+;; CHECK-NEXT:    (i32.const 0)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
diff --git a/test/lit/binary/annotated-array-len.test.wasm b/test/lit/binary/annotated-array-len.test.wasm
new file mode 100644
index 0000000..b199f58
Binary files /dev/null and b/test/lit/binary/annotated-array-len.test.wasm differ
diff --git a/test/lit/binary/prototype-nominal-format.test b/test/lit/binary/prototype-nominal-format.test
index def3e14..98ed511 100644
--- a/test/lit/binary/prototype-nominal-format.test
+++ b/test/lit/binary/prototype-nominal-format.test
@@ -1,27 +1,27 @@
 ;; Test the we can properly parse the prototype nominal binary format that we no
 ;; longer emit.
 
-;; RUN: wasm-dis %s.wasm -all | filecheck %s
+;; RUN: wasm-dis %s.wasm -all --nominal | filecheck %s
 
 ;; CHECK:      (module
-;; CHECK-NEXT:  (type $super-struct (struct (field i32)))
-;; CHECK-NEXT:  (type $sub-struct (struct (field i32) (field i64)))
-;; CHECK-NEXT:  (type $none_=>_ref|$super-struct| (func (result (ref $super-struct))))
-;; CHECK-NEXT:  (type $none_=>_ref|$sub-struct| (func (result (ref $sub-struct))))
-;; CHECK-NEXT:  (type $none_=>_ref|$super-array| (func (result (ref $super-array))))
-;; CHECK-NEXT:  (type $none_=>_ref|$sub-array| (func (result (ref $sub-array))))
-;; CHECK-NEXT:  (type $super-array (array (ref $super-struct)))
-;; CHECK-NEXT:  (type $sub-array (array (ref $sub-struct)))
-;; CHECK-NEXT:  (func $make-super-struct (result (ref $super-struct))
+;; CHECK-NEXT:  (type $super-struct (struct_subtype (field i32) data))
+;; CHECK-NEXT:  (type $sub-struct (struct_subtype (field i32) (field i64) data))
+;; CHECK-NEXT:  (type $none_=>_ref|$super-struct| (func_subtype (result (ref $super-struct)) func))
+;; CHECK-NEXT:  (type $none_=>_ref|$sub-struct| (func_subtype (result (ref $sub-struct)) func))
+;; CHECK-NEXT:  (type $none_=>_ref|$super-array| (func_subtype (result (ref $super-array)) func))
+;; CHECK-NEXT:  (type $none_=>_ref|$sub-array| (func_subtype (result (ref $sub-array)) func))
+;; CHECK-NEXT:  (type $super-array (array_subtype (ref $super-struct) data))
+;; CHECK-NEXT:  (type $sub-array (array_subtype (ref $sub-struct) data))
+;; CHECK-NEXT:  (func $make-super-struct (type $none_=>_ref|$super-struct|) (result (ref $super-struct))
 ;; CHECK-NEXT:   (call $make-sub-struct)
 ;; CHECK-NEXT:  )
-;; CHECK-NEXT:  (func $make-sub-struct (result (ref $sub-struct))
+;; CHECK-NEXT:  (func $make-sub-struct (type $none_=>_ref|$sub-struct|) (result (ref $sub-struct))
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
-;; CHECK-NEXT:  (func $make-super-array (result (ref $super-array))
+;; CHECK-NEXT:  (func $make-super-array (type $none_=>_ref|$super-array|) (result (ref $super-array))
 ;; CHECK-NEXT:   (call $make-sub-array)
 ;; CHECK-NEXT:  )
-;; CHECK-NEXT:  (func $make-sub-array (result (ref $sub-array))
+;; CHECK-NEXT:  (func $make-sub-array (type $none_=>_ref|$sub-array|) (result (ref $sub-array))
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
diff --git a/test/lit/exec/i31.wast b/test/lit/exec/i31.wast
new file mode 100644
index 0000000..c8b43e6
--- /dev/null
+++ b/test/lit/exec/i31.wast
@@ -0,0 +1,106 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+  ;; CHECK:      [fuzz-exec] calling null-local
+  ;; CHECK-NEXT: [fuzz-exec] note result: null-local => 1
+  (func "null-local" (result i32)
+    (local $ref (ref null i31))
+    (ref.is_null
+      (local.get $ref)
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling null-immediate
+  ;; CHECK-NEXT: [fuzz-exec] note result: null-immediate => 1
+  (func "null-immediate" (result i32)
+    (ref.is_null
+      (ref.null i31)
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling non-null
+  ;; CHECK-NEXT: [fuzz-exec] note result: non-null => 0
+  (func "non-null" (result i32)
+    (ref.is_null
+      (i31.new
+        (i32.const 1234)
+      )
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling nn-u
+  ;; CHECK-NEXT: [fuzz-exec] note result: nn-u => 2147483647
+  (func "nn-u" (result i32)
+    (i31.get_u
+      (i31.new
+        (i32.const 0xffffffff)
+      )
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling nn-s
+  ;; CHECK-NEXT: [fuzz-exec] note result: nn-s => -1
+  (func "nn-s" (result i32)
+    (i31.get_s
+      (i31.new
+        (i32.const 0xffffffff)
+      )
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling zero-is-not-null
+  ;; CHECK-NEXT: [fuzz-exec] note result: zero-is-not-null => 0
+  (func "zero-is-not-null" (result i32)
+    (local $ref (ref null i31))
+    (local.set $ref
+      (i31.new
+        (i32.const 0)
+      )
+    )
+    (i32.add ;; 0 + 0 is 0
+      (ref.is_null
+        (local.get $ref)
+      )
+      (i31.get_u ;; this should not trap on null
+        (local.get $ref)
+      )
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling trap
+  ;; CHECK-NEXT: [trap null ref]
+  (func "trap" (result i32)
+    (i31.get_u
+      (ref.null i31)
+    )
+  )
+)
+;; CHECK:      [fuzz-exec] calling null-local
+;; CHECK-NEXT: [fuzz-exec] note result: null-local => 1
+
+;; CHECK:      [fuzz-exec] calling null-immediate
+;; CHECK-NEXT: [fuzz-exec] note result: null-immediate => 1
+
+;; CHECK:      [fuzz-exec] calling non-null
+;; CHECK-NEXT: [fuzz-exec] note result: non-null => 0
+
+;; CHECK:      [fuzz-exec] calling nn-u
+;; CHECK-NEXT: [fuzz-exec] note result: nn-u => 2147483647
+
+;; CHECK:      [fuzz-exec] calling nn-s
+;; CHECK-NEXT: [fuzz-exec] note result: nn-s => -1
+
+;; CHECK:      [fuzz-exec] calling zero-is-not-null
+;; CHECK-NEXT: [fuzz-exec] note result: zero-is-not-null => 0
+
+;; CHECK:      [fuzz-exec] calling trap
+;; CHECK-NEXT: [trap null ref]
+;; CHECK-NEXT: [fuzz-exec] comparing nn-s
+;; CHECK-NEXT: [fuzz-exec] comparing nn-u
+;; CHECK-NEXT: [fuzz-exec] comparing non-null
+;; CHECK-NEXT: [fuzz-exec] comparing null-immediate
+;; CHECK-NEXT: [fuzz-exec] comparing null-local
+;; CHECK-NEXT: [fuzz-exec] comparing trap
+;; CHECK-NEXT: [fuzz-exec] comparing zero-is-not-null
diff --git a/test/lit/exec/intrinsics.wast b/test/lit/exec/intrinsics.wast
new file mode 100644
index 0000000..d53082a
--- /dev/null
+++ b/test/lit/exec/intrinsics.wast
@@ -0,0 +1,24 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+  (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param i32 funcref) (result i32)))
+
+  ;; CHECK:      [fuzz-exec] calling get-ref
+  ;; CHECK-NEXT: [fuzz-exec] note result: get-ref => 42
+  (func "get-ref" (result i32)
+    (call $cwe
+      (i32.const 41)
+      (ref.func $add-one)
+    )
+  )
+
+  (func $add-one (param $x i32) (result i32)
+    (i32.add
+      (local.get $x)
+      (i32.const 1)
+    )
+  )
+)
+
diff --git a/test/lit/exec/memory.grow.wast b/test/lit/exec/memory.grow.wast
new file mode 100644
index 0000000..64d88bf
--- /dev/null
+++ b/test/lit/exec/memory.grow.wast
@@ -0,0 +1,26 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+  (memory $0 1)
+
+  ;; CHECK:      [fuzz-exec] calling grow_twice
+  ;; CHECK-NEXT: [fuzz-exec] note result: grow_twice => 3
+  (func "grow_twice" (result i32)
+    ;; The nested grow will increase the size from 1 to 3, and return the old
+    ;; size, 1. Then the outer grow will grow by that amount 1, from 3 to 4.
+    (memory.grow
+      (memory.grow
+        (i32.const 2)
+      )
+    )
+  )
+
+  ;; CHECK:      [fuzz-exec] calling measure
+  ;; CHECK-NEXT: [fuzz-exec] note result: measure => 4
+  (func "measure" (export "measure") (result i32)
+    ;; This should return the final size, 4.
+    (memory.size)
+  )
+)
diff --git a/test/lit/exec/no-compare-refs.wast b/test/lit/exec/no-compare-refs.wast
new file mode 100644
index 0000000..bb9766a
--- /dev/null
+++ b/test/lit/exec/no-compare-refs.wast
@@ -0,0 +1,22 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s --hybrid -all --signature-pruning --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+  (type $f (func (param i32)))
+
+  (func $no-use-param (type $f) (param i32)
+    (unreachable)
+  )
+
+  ;; The type of the reference this function returns will change as a result of
+  ;; signature pruning. The fuzzer should not complain about this.
+  ;; CHECK:      [fuzz-exec] calling return-ref
+  ;; CHECK-NEXT: [fuzz-exec] note result: return-ref => funcref
+  (func "return-ref" (result funcref)
+    (ref.func $no-use-param)
+  )
+)
+;; CHECK:      [fuzz-exec] calling return-ref
+;; CHECK-NEXT: [fuzz-exec] note result: return-ref => funcref
+;; CHECK-NEXT: [fuzz-exec] comparing return-ref
diff --git a/test/lit/exec/read-nn-null.wast b/test/lit/exec/read-nn-null.wast
new file mode 100644
index 0000000..d726889
--- /dev/null
+++ b/test/lit/exec/read-nn-null.wast
@@ -0,0 +1,74 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s      -all --coalesce-locals --optimize-instructions --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s --check-prefix=NORMAL
+;; RUN: wasm-opt %s -tnh -all --coalesce-locals --optimize-instructions --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s --check-prefix=TNH
+
+;; The sequence of passes here will do the following:
+;;
+;;  * coalesce-locals will remove the local.set. That does not reach any
+;;    local.get due to the unreachable, so it is a dead store that we can
+;;    remove.
+;;  * optimize-instructions will reorder the select's arms (to get rid of the
+;;    i32.eqz). It looks ok to reorder them because the local.get on one arm has
+;;    no side effects at all.
+;;
+;; After those, if we did not fix up non-nullable locals in between, then we'd
+;; end up executing a local.get of a non-nullable local that has no local.set,
+;; which means we are reading a null by a non-nullable local - an internal
+;; error. We avoid this situation by fixing up non-nullable locals in between
+;; these passes, which adds a ref.as_non_null on the local.get, and that
+;; possibly-trapping instruction will prevent dangerous reordering. In
+;; particular, --fuzz-exec result should be identical before and after: always
+;; log 42 and then trap on that unreachable.
+;;
+;; This also tests traps-never-happen mode. Atm that mode changes nothing here,
+;; but that may change in the future as tnh starts to optimize more things. In
+;; particular, tnh can in principle remove the ref.as_non_null that is added on
+;; the local.get, which would then let optimize-instructions reorder - but that
+;; will still not affect observable behavior, so it is fine.
+
+(module
+  (import "fuzzing-support" "log-i32" (func $log (param i32)))
+
+  (func $foo (export "foo") (param $i i32) (result funcref)
+    (local $ref (ref func))
+    (local.set $ref
+      (ref.func $foo)
+    )
+    (select (result funcref)
+      (block $trap (result funcref)
+        (call $log
+          (i32.const 42)
+        )
+        ;; We never reach the br, but its existence makes the block's type none
+        ;; instead of unreachable (optimization passes may ignore such
+        ;; obviously-unreachable code, so we make it less obvious this way).
+        (unreachable)
+        (br $trap
+          (ref.func $foo)
+        )
+      )
+      (local.get $ref)
+      (i32.eqz
+        (local.get $i)
+      )
+    )
+  )
+)
+;; NORMAL:      [fuzz-exec] calling foo
+;; NORMAL-NEXT: [LoggingExternalInterface logging 42]
+;; NORMAL-NEXT: [trap unreachable]
+
+;; NORMAL:      [fuzz-exec] calling foo
+;; NORMAL-NEXT: [LoggingExternalInterface logging 42]
+;; NORMAL-NEXT: [trap unreachable]
+;; NORMAL-NEXT: [fuzz-exec] comparing foo
+
+;; TNH:      [fuzz-exec] calling foo
+;; TNH-NEXT: [LoggingExternalInterface logging 42]
+;; TNH-NEXT: [trap unreachable]
+
+;; TNH:      [fuzz-exec] calling foo
+;; TNH-NEXT: [LoggingExternalInterface logging 42]
+;; TNH-NEXT: [trap unreachable]
+;; TNH-NEXT: [fuzz-exec] comparing foo
diff --git a/test/lit/exec/rtts.wast b/test/lit/exec/rtts.wast
deleted file mode 100644
index 7c090f1..0000000
--- a/test/lit/exec/rtts.wast
+++ /dev/null
@@ -1,153 +0,0 @@
-;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
-
-;; Check that allocation and casting instructions with and without RTTs can be
-;; mixed correctly.
-
-;; RUN: wasm-opt %s -all --fuzz-exec-before -q --structural -o /dev/null 2>&1 \
-;; RUN:   | filecheck %s --check-prefix=EQREC
-
-;; RUN: wasm-opt %s -all --fuzz-exec-before -q --nominal -o /dev/null 2>&1 \
-;; RUN:   | filecheck %s --check-prefix=NOMNL
-
-(module
-  (type $struct (struct_subtype i32 data))
-  (type $sub-struct (struct_subtype i32 i32 $struct))
-
-  (import "fuzzing-support" "log-i32" (func $log (param i32)))
-
-  (global $sub-rtt (rtt 1 $sub-struct)
-    (rtt.sub $sub-struct
-      (rtt.canon $struct)
-    )
-  )
-
-  (func $make-sub-struct-canon (result (ref $struct))
-    (struct.new_default_with_rtt $sub-struct
-      (rtt.canon $sub-struct)
-    )
-  )
-
-  (func $make-sub-struct-sub (result (ref $struct))
-    (struct.new_default_with_rtt $sub-struct
-      (global.get $sub-rtt)
-    )
-  )
-
-  (func $make-sub-struct-static (result (ref $struct))
-    (struct.new_default $sub-struct)
-  )
-
-  ;; EQREC:      [fuzz-exec] calling canon-canon
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 1]
-  ;; NOMNL:      [fuzz-exec] calling canon-canon
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "canon-canon"
-    (call $log
-      (ref.test
-        (call $make-sub-struct-canon)
-        (rtt.canon $sub-struct)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling canon-sub
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 0]
-  ;; NOMNL:      [fuzz-exec] calling canon-sub
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "canon-sub"
-    (call $log
-      (ref.test
-        (call $make-sub-struct-canon)
-        (global.get $sub-rtt)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling canon-static
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 1]
-  ;; NOMNL:      [fuzz-exec] calling canon-static
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "canon-static"
-    (call $log
-      (ref.test_static $sub-struct
-        (call $make-sub-struct-canon)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling sub-canon
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 0]
-  ;; NOMNL:      [fuzz-exec] calling sub-canon
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "sub-canon"
-    (call $log
-      (ref.test
-        (call $make-sub-struct-sub)
-        (rtt.canon $sub-struct)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling sub-sub
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 1]
-  ;; NOMNL:      [fuzz-exec] calling sub-sub
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "sub-sub"
-    (call $log
-      (ref.test
-        (call $make-sub-struct-sub)
-        (global.get $sub-rtt)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling sub-static
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 0]
-  ;; NOMNL:      [fuzz-exec] calling sub-static
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "sub-static"
-    (call $log
-      (ref.test_static $sub-struct
-        (call $make-sub-struct-sub)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling static-canon
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 1]
-  ;; NOMNL:      [fuzz-exec] calling static-canon
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "static-canon"
-    (call $log
-      (ref.test
-        (call $make-sub-struct-static)
-        (rtt.canon $sub-struct)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling static-sub
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 0]
-  ;; NOMNL:      [fuzz-exec] calling static-sub
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "static-sub"
-    (call $log
-      (ref.test
-        (call $make-sub-struct-static)
-        (global.get $sub-rtt)
-      )
-    )
-  )
-
-  ;; EQREC:      [fuzz-exec] calling static-static
-  ;; EQREC-NEXT: [LoggingExternalInterface logging 1]
-  ;; NOMNL:      [fuzz-exec] calling static-static
-  ;; NOMNL-NEXT: [LoggingExternalInterface logging 1]
-  (func "static-static"
-    (call $log
-      (ref.test_static $sub-struct
-        (call $make-sub-struct-static)
-      )
-    )
-  )
-)
diff --git a/test/lit/exec/trap.wast b/test/lit/exec/trap.wast
deleted file mode 100644
index a0ed6f0..0000000
--- a/test/lit/exec/trap.wast
+++ /dev/null
@@ -1,55 +0,0 @@
-;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
-
-;; RUN: wasm-opt %s --vacuum --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
-;; RUN: wasm-opt %s --ignore-implicit-traps --vacuum --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s --check-prefix=IIT
-;; RUN: wasm-opt %s --traps-never-happen --vacuum --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s --check-prefix=TNH
-
-(module
-  ;; CHECK:      [fuzz-exec] calling trap
-  ;; CHECK-NEXT: [trap unreachable]
-  ;; IIT:      [fuzz-exec] calling trap
-  ;; IIT-NEXT: [trap unreachable]
-  ;; TNH:      [fuzz-exec] calling trap
-  ;; TNH-NEXT: [trap unreachable]
-  (func "trap"
-    (unreachable)
-  )
-
-  (memory 1 1)
-  ;; CHECK:      [fuzz-exec] calling load-trap
-  ;; CHECK-NEXT: [trap highest > memory: 65536 > 65532]
-  ;; IIT:      [fuzz-exec] calling load-trap
-  ;; IIT-NEXT: [trap highest > memory: 65536 > 65532]
-  ;; TNH:      [fuzz-exec] calling load-trap
-  ;; TNH-NEXT: [trap highest > memory: 65536 > 65532]
-  (func "load-trap"
-    ;; This load traps, so this cannot be removed. But if either of
-    ;; --ignore-implicit-traps or --traps-never-happen is set, this load is
-    ;; assumed not to trap and we end up optimizing this out with --vacuum,
-    ;; causes the trap behavior to change. This should not result in [fuzz-exec]
-    ;; comparison failure.
-    (drop
-      (i32.load (i32.const 65536))
-    )
-  )
-)
-;; CHECK:      [fuzz-exec] calling trap
-;; CHECK-NEXT: [trap unreachable]
-
-;; CHECK:      [fuzz-exec] calling load-trap
-;; CHECK-NEXT: [trap highest > memory: 65536 > 65532]
-;; CHECK-NEXT: [fuzz-exec] comparing load-trap
-;; CHECK-NEXT: [fuzz-exec] comparing trap
-
-;; IIT:      [fuzz-exec] calling trap
-;; IIT-NEXT: [trap unreachable]
-
-;; IIT:      [fuzz-exec] calling load-trap
-;; IIT-NEXT: [fuzz-exec] comparing load-trap
-;; IIT-NEXT: [fuzz-exec] comparing trap
-
-;; TNH:      [fuzz-exec] calling trap
-
-;; TNH:      [fuzz-exec] calling load-trap
-;; TNH-NEXT: [fuzz-exec] comparing load-trap
-;; TNH-NEXT: [fuzz-exec] comparing trap
diff --git a/test/lit/extern-conversions.wast b/test/lit/extern-conversions.wast
new file mode 100644
index 0000000..d3ad4d2
--- /dev/null
+++ b/test/lit/extern-conversions.wast
@@ -0,0 +1,39 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Check that extern conversion instructions are emitted properly in the binary format.
+;; Also check that the optimizer does not break on this code.
+
+;; RUN: wasm-opt %s -all -O1 --roundtrip -S -o - | filecheck %s
+
+
+(module
+ ;; CHECK:      (type $ref|any|_=>_ref|extern| (func (param (ref any)) (result (ref extern))))
+
+ ;; CHECK:      (type $externref_=>_anyref (func (param externref) (result anyref)))
+
+ ;; CHECK:      (export "ext" (func $extern.externalize))
+
+ ;; CHECK:      (export "int" (func $extern.internalize))
+
+ ;; CHECK:      (func $extern.externalize (param $0 (ref any)) (result (ref extern))
+ ;; CHECK-NEXT:  (extern.externalize
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $extern.externalize (export "ext") (param $x (ref any)) (result (ref extern))
+  (extern.externalize
+   (local.get $x)
+  )
+ )
+
+ ;; CHECK:      (func $extern.internalize (param $0 externref) (result anyref)
+ ;; CHECK-NEXT:  (extern.internalize
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $extern.internalize (export "int") (param $x (ref null extern)) (result (ref null any))
+  (extern.internalize
+   (local.get $x)
+  )
+ )
+)
diff --git a/test/lit/forward-declared-types.wast b/test/lit/forward-declared-types.wast
deleted file mode 100644
index c20cc3a..0000000
--- a/test/lit/forward-declared-types.wast
+++ /dev/null
@@ -1,26 +0,0 @@
-;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; Test that types can be used before they are defined
-
-;; RUN: wasm-opt %s -all -S -o - | filecheck %s
-;; RUN: wasm-opt %s -all --nominal -S -o - | filecheck %s --check-prefix=NOMNL
-
-(module
-  ;; CHECK:      (type $func (func))
-
-  ;; CHECK:      (type $struct (struct (field (ref $array)) (field (ref null $func))))
-  ;; NOMNL:      (type $func (func_subtype func))
-
-  ;; NOMNL:      (type $struct (struct_subtype (field (ref $array)) (field (ref null $func)) data))
-  (type $struct (struct
-    (field (ref $array))
-    (field (ref null $func))
-  ))
-  ;; CHECK:      (type $array (array (rtt 2 $func)))
-  ;; NOMNL:      (type $array (array_subtype (rtt 2 $func) data))
-  (type $array (array (field (rtt 2 $func))))
-  (type $func (func))
-
-  (func (result (ref null $struct))
-    (unreachable)
-  )
-)
diff --git a/test/lit/fuzz-types/isorecursive.test b/test/lit/fuzz-types/isorecursive.test
index 9165d92..ccbb63d 100644
--- a/test/lit/fuzz-types/isorecursive.test
+++ b/test/lit/fuzz-types/isorecursive.test
@@ -1,26 +1,26 @@
 ;; RUN: wasm-fuzz-types --hybrid -v --seed=0 | filecheck %s
 
 ;; CHECK:      (rec
-;; CHECK-NEXT:  (type $0 (struct_subtype data))
-;; CHECK-NEXT:  (type $1 (func_subtype (param i31ref) (result dataref) func))
-;; CHECK-NEXT:  (type $2 (array_subtype i32 data))
-;; CHECK-NEXT:  (type $3 (array_subtype i32 $2))
-;; CHECK-NEXT:  (type $4 (array_subtype i32 $2))
-;; CHECK-NEXT:  (type $5 (array_subtype i32 $3))
-;; CHECK-NEXT:  (type $6 (array_subtype i32 $3))
-;; CHECK-NEXT:  (type $7 (struct_subtype $0))
-;; CHECK-NEXT:  (type $8 (struct_subtype $0))
-;; CHECK-NEXT:  (type $9 (array_subtype i32 $6))
-;; CHECK-NEXT:  (type $10 (struct_subtype $0))
-;; CHECK-NEXT:  (type $11 (array_subtype i32 $2))
-;; CHECK-NEXT:  (type $12 (struct_subtype $0))
-;; CHECK-NEXT:  (type $13 (array_subtype i32 $6))
+;; CHECK-NEXT:  (type $0 (array_subtype (mut i64) data))
+;; CHECK-NEXT:  (type $1 (func_subtype (param i32 (ref eq) (ref null $3)) (result v128) func))
+;; CHECK-NEXT:  (type $2 (array_subtype (mut v128) data))
+;; CHECK-NEXT:  (type $3 (array_subtype (mut i64) $0))
+;; CHECK-NEXT:  (type $4 (array_subtype i32 data))
+;; CHECK-NEXT:  (type $5 none)
+;; CHECK-NEXT:  (type $6 extern)
 ;; CHECK-NEXT: )
 ;; CHECK-NEXT: (rec
-;; CHECK-NEXT:  (type $14 (struct_subtype $10))
-;; CHECK-NEXT:  (type $15 (func_subtype (param i31ref) (result dataref) $1))
-;; CHECK-NEXT:  (type $16 (array_subtype i32 $2))
-;; CHECK-NEXT:  (type $17 (struct_subtype $14))
-;; CHECK-NEXT:  (type $18 (struct_subtype (field (mut funcref) (mut (ref null $7)) funcref) $12))
-;; CHECK-NEXT:  (type $19 (func_subtype (param i31ref) (result dataref) $15))
+;; CHECK-NEXT:  (type $7 (array_subtype (mut i64) $3))
+;; CHECK-NEXT:  (type $8 (func_subtype (result v128) func))
+;; CHECK-NEXT:  (type $9 (array_subtype (mut v128) $2))
+;; CHECK-NEXT:  (type $10 none)
+;; CHECK-NEXT:  (type $11 none)
+;; CHECK-NEXT:  (type $12 (array_subtype (mut v128) $2))
+;; CHECK-NEXT:  (type $13 (func_subtype (param f32 (ref none) (ref null $3)) (result eqref) func))
+;; CHECK-NEXT:  (type $14 (array_subtype (mut i64) $7))
+;; CHECK-NEXT:  (type $15 (func_subtype (param (ref null $17) v128 (ref i31)) func))
+;; CHECK-NEXT:  (type $16 (array_subtype i32 data))
+;; CHECK-NEXT:  (type $17 (func_subtype (param (ref i31)) (result v128) func))
+;; CHECK-NEXT:  (type $18 (func_subtype (param f32) func))
+;; CHECK-NEXT:  (type $19 (array_subtype (mut i64) $3))
 ;; CHECK-NEXT: )
diff --git a/test/lit/fuzz-types/nominal.test b/test/lit/fuzz-types/nominal.test
index 248ce65..bb84f69 100644
--- a/test/lit/fuzz-types/nominal.test
+++ b/test/lit/fuzz-types/nominal.test
@@ -1,22 +1,22 @@
 ;; RUN: wasm-fuzz-types --nominal -v --seed=0 | filecheck %s
 
-;; CHECK:      (type $0 (struct_subtype (field (ref null $9) (ref $5)) data))
-;; CHECK-NEXT: (type $1 (func_subtype (param (rtt 0 $8)) func))
-;; CHECK-NEXT: (type $2 (struct_subtype (field (mut (rtt $19)) (ref $4)) data))
-;; CHECK-NEXT: (type $3 (struct_subtype (field (mut (rtt $19)) (ref $4) i64 (mut (ref null $2)) (mut i64)) $2))
-;; CHECK-NEXT: (type $4 (struct_subtype (field (mut (rtt $19)) (ref $4) (mut (ref $13))) $2))
-;; CHECK-NEXT: (type $5 (struct_subtype (field (mut (rtt $19)) (ref $4) i64 (mut (ref null $2)) (mut i64)) $3))
-;; CHECK-NEXT: (type $6 (struct_subtype (field (mut (rtt $19)) (ref $4) i64 (mut (ref null $2)) (mut i64)) $3))
-;; CHECK-NEXT: (type $7 (struct_subtype (field (ref null $9) (ref $5) eqref (mut (ref null $3)) (mut (rtt $18)) dataref) $0))
-;; CHECK-NEXT: (type $8 (struct_subtype (field (ref $9) (ref $5) (rtt 2 $1) (mut (rtt $17)) (mut (rtt i31)) (rtt $8)) $0))
-;; CHECK-NEXT: (type $9 (struct_subtype (field (mut (rtt $19)) (ref $4) i64 (mut (ref null $2)) (mut i64) (rtt 2 $15)) $6))
-;; CHECK-NEXT: (type $10 (struct_subtype (field (ref null $9) (ref $5)) $0))
-;; CHECK-NEXT: (type $11 (struct_subtype (field (mut (rtt $19)) (ref $4)) $2))
-;; CHECK-NEXT: (type $12 (struct_subtype (field (ref null $9) (ref $5)) $0))
-;; CHECK-NEXT: (type $13 (struct_subtype (field (mut (rtt $19)) (ref $4) i64 (mut (ref null $2)) (mut i64)) $6))
-;; CHECK-NEXT: (type $14 (struct_subtype (field (ref null $9) (ref $5)) $10))
-;; CHECK-NEXT: (type $15 (func_subtype (param (rtt 0 $8)) $1))
-;; CHECK-NEXT: (type $16 (struct_subtype (field (mut (rtt $19)) (ref $4) v128) $2))
-;; CHECK-NEXT: (type $17 (struct_subtype (field (ref null $9) (ref $5)) $14))
-;; CHECK-NEXT: (type $18 (struct_subtype (field (ref null $9) (ref $5)) $12))
-;; CHECK-NEXT: (type $19 (func_subtype (param (rtt 0 $8)) $15))
+;; CHECK:      (type $0 (array_subtype (mut (ref null $8)) data))
+;; CHECK-NEXT: (type $1 (func_subtype (param nullref) (result v128) func))
+;; CHECK-NEXT: (type $2 (struct_subtype (field (mut i64) (mut i8) (mut (ref $12)) (mut f32)) data))
+;; CHECK-NEXT: (type $3 (array_subtype (mut (ref null $8)) $0))
+;; CHECK-NEXT: (type $4 (array_subtype (mut i64) data))
+;; CHECK-NEXT: (type $5 none)
+;; CHECK-NEXT: (type $6 extern)
+;; CHECK-NEXT: (type $7 (array_subtype (mut (ref null $8)) $3))
+;; CHECK-NEXT: (type $8 (func_subtype (result (ref $9)) func))
+;; CHECK-NEXT: (type $9 (struct_subtype (field (mut i64) (mut i8) (mut (ref $12)) (mut f32) i16) $2))
+;; CHECK-NEXT: (type $10 none)
+;; CHECK-NEXT: (type $11 none)
+;; CHECK-NEXT: (type $12 (struct_subtype (field (mut i64) (mut i8) (mut (ref $12)) (mut f32) (mut f32) (mut (ref $4))) $2))
+;; CHECK-NEXT: (type $13 (func_subtype (param dataref) func))
+;; CHECK-NEXT: (type $14 (array_subtype (mut (ref null $8)) $7))
+;; CHECK-NEXT: (type $15 (func_subtype (param externref) func))
+;; CHECK-NEXT: (type $16 (array_subtype v128 data))
+;; CHECK-NEXT: (type $17 (func_subtype (result (ref extern)) func))
+;; CHECK-NEXT: (type $18 (func_subtype (param v128 i64) func))
+;; CHECK-NEXT: (type $19 (array_subtype (mut (ref null $8)) $3))
diff --git a/test/lit/fuzz-types/structural.test b/test/lit/fuzz-types/structural.test
index a7b38ed..3dcec31 100644
--- a/test/lit/fuzz-types/structural.test
+++ b/test/lit/fuzz-types/structural.test
@@ -1,22 +1,22 @@
 ;; RUN: wasm-fuzz-types --structural -v --seed=0 | filecheck %s
 
-;; CHECK:      (type $0 (struct (field (ref null $9) (ref $3))))
-;; CHECK-NEXT: (type $1 (func (param (rtt 0 $8))))
-;; CHECK-NEXT: (type $2 (struct (field (mut (rtt $1)) (ref $4))))
-;; CHECK-NEXT: (type $3 (struct (field (mut (rtt $1)) (ref $4) i64 (mut (ref null $2)) (mut i64))))
-;; CHECK-NEXT: (type $4 (struct (field (mut (rtt $1)) (ref $4) (mut (ref $3)))))
-;; CHECK-NEXT: (type $5 identical to $3)
-;; CHECK-NEXT: (type $6 identical to $3)
-;; CHECK-NEXT: (type $7 (struct (field (ref null $9) (ref $3) eqref (mut (ref null $3)) (mut (rtt $0)) dataref)))
-;; CHECK-NEXT: (type $8 (struct (field (ref $9) (ref $3) (rtt 2 $1) (mut (rtt $0)) (mut (rtt i31)) (rtt $8))))
-;; CHECK-NEXT: (type $9 (struct (field (mut (rtt $1)) (ref $4) i64 (mut (ref null $2)) (mut i64) (rtt 2 $1))))
-;; CHECK-NEXT: (type $10 identical to $0)
-;; CHECK-NEXT: (type $11 identical to $2)
-;; CHECK-NEXT: (type $12 identical to $0)
-;; CHECK-NEXT: (type $13 identical to $3)
+;; CHECK:      (type $0 (array (mut (ref null $8))))
+;; CHECK-NEXT: (type $1 (func (param nullref) (result v128)))
+;; CHECK-NEXT: (type $2 (struct (field (mut i64) (mut i8) (mut (ref $12)) (mut f32))))
+;; CHECK-NEXT: (type $3 identical to $0)
+;; CHECK-NEXT: (type $4 (array (mut i64)))
+;; CHECK-NEXT: (type $5 none)
+;; CHECK-NEXT: (type $6 extern)
+;; CHECK-NEXT: (type $7 identical to $0)
+;; CHECK-NEXT: (type $8 (func (result (ref $9))))
+;; CHECK-NEXT: (type $9 (struct (field (mut i64) (mut i8) (mut (ref $12)) (mut f32) i16)))
+;; CHECK-NEXT: (type $10 none)
+;; CHECK-NEXT: (type $11 none)
+;; CHECK-NEXT: (type $12 (struct (field (mut i64) (mut i8) (mut (ref $12)) (mut f32) (mut f32) (mut (ref $4)))))
+;; CHECK-NEXT: (type $13 (func (param dataref)))
 ;; CHECK-NEXT: (type $14 identical to $0)
-;; CHECK-NEXT: (type $15 identical to $1)
-;; CHECK-NEXT: (type $16 (struct (field (mut (rtt $1)) (ref $4) v128)))
-;; CHECK-NEXT: (type $17 identical to $0)
-;; CHECK-NEXT: (type $18 identical to $0)
-;; CHECK-NEXT: (type $19 identical to $1)
+;; CHECK-NEXT: (type $15 (func (param externref)))
+;; CHECK-NEXT: (type $16 (array v128))
+;; CHECK-NEXT: (type $17 (func (result (ref extern))))
+;; CHECK-NEXT: (type $18 (func (param v128 i64)))
+;; CHECK-NEXT: (type $19 identical to $0)
diff --git a/test/lit/gc-eh.wast b/test/lit/gc-eh.wast
index 41897d0..335bad5 100644
--- a/test/lit/gc-eh.wast
+++ b/test/lit/gc-eh.wast
@@ -27,7 +27,7 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (ref.null $A)
+  ;; CHECK-NEXT:  (ref.null none)
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $foo (type $none_=>_ref?|$A|) (result (ref null $A))
   ;; NOMNL-NEXT:  (try $try
@@ -40,7 +40,7 @@
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
-  ;; NOMNL-NEXT:  (ref.null $A)
+  ;; NOMNL-NEXT:  (ref.null none)
   ;; NOMNL-NEXT: )
   (func $foo (result (ref null $A))
     (try
diff --git a/test/lit/global-only.wast b/test/lit/global-only.wast
new file mode 100644
index 0000000..ac3e1ee
--- /dev/null
+++ b/test/lit/global-only.wast
@@ -0,0 +1,13 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Regression test for a bug where types on globals were not collected, so if
+;; they weren't used anywhere else they were not emitted in the output.
+
+;; RUN: wasm-opt -all %s -S -o - | filecheck %s
+
+(module $parse
+  ;; CHECK:      (type $t (struct ))
+  (type $t (struct))
+  ;; CHECK:      (global $g (ref null $t) (ref.null none))
+  (global $g (ref null $t) (ref.null $t))
+)
diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast
index ff9e592..13d3721 100644
--- a/test/lit/heap-types.wast
+++ b/test/lit/heap-types.wast
@@ -10,21 +10,20 @@
 
 (module
   ;; CHECK:      (type $struct.A (struct (field i32)))
-  ;; NOMNL:      (type $struct.A (struct_subtype (field i32) data))
   (type $struct.A (struct i32))
   ;; NOMNL:      (type $struct.B (struct_subtype (field i32) data))
   (type $struct.B (struct i32))
   ;; CHECK:      (func $test
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.test_static $struct.A
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $test (type $none_=>_none)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.test_static $struct.B
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -37,21 +36,20 @@
 
 (module
   ;; CHECK:      (type $struct.A (struct (field i32)))
-  ;; NOMNL:      (type $struct.A (struct_subtype (field i32) data))
   (type $struct.A (struct i32))
   ;; NOMNL:      (type $struct.B (struct_subtype (field i32) data))
   (type $struct.B (struct i32))
   ;; CHECK:      (func $test
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.cast_static $struct.A
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $test (type $none_=>_none)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.cast_static $struct.B
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test
index 50ab573..bbce250 100644
--- a/test/lit/help/wasm-as.test
+++ b/test/lit/help/wasm-as.test
@@ -88,10 +88,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -104,6 +100,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test
index 518d536..c20ccc8 100644
--- a/test/lit/help/wasm-ctor-eval.test
+++ b/test/lit/help/wasm-ctor-eval.test
@@ -92,10 +92,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -108,6 +104,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test
index c2b4af2..f6baf04 100644
--- a/test/lit/help/wasm-dis.test
+++ b/test/lit/help/wasm-dis.test
@@ -81,10 +81,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -97,6 +93,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test
index 2f9ae3a..45d3a3e 100644
--- a/test/lit/help/wasm-emscripten-finalize.test
+++ b/test/lit/help/wasm-emscripten-finalize.test
@@ -20,9 +20,6 @@
 ;; CHECK-NEXT:                                        output file. In this mode if no output
 ;; CHECK-NEXT:                                        file is specified, we write to stdout.
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --no-emit-metadata,-n                Skip the writing to emscripten metadata
-;; CHECK-NEXT:                                        JSON to stdout.
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --global-base                        The address at which static globals were
 ;; CHECK-NEXT:                                        placed
 ;; CHECK-NEXT:
@@ -131,10 +128,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -147,6 +140,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test
index 99a8300..10e6787 100644
--- a/test/lit/help/wasm-metadce.test
+++ b/test/lit/help/wasm-metadce.test
@@ -129,10 +129,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -145,6 +141,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index d786bfd..bc3d710 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -76,6 +76,9 @@
 ;; CHECK-NEXT:   --output-source-map-url,-osu                  Emit specified string as source
 ;; CHECK-NEXT:                                                 map URL
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --new-wat-parser                              Use the experimental new WAT
+;; CHECK-NEXT:                                                 parser
+;; CHECK-NEXT:
 ;; CHECK-NEXT:
 ;; CHECK-NEXT: Optimization passes:
 ;; CHECK-NEXT: --------------------
@@ -126,6 +129,8 @@
 ;; CHECK-NEXT:   --directize                                   turns indirect calls into direct
 ;; CHECK-NEXT:                                                 ones
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --discard-global-effects                      discards global effect info
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --duplicate-function-elimination              removes duplicate functions
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --duplicate-import-elimination                removes duplicate imports
@@ -154,6 +159,9 @@
 ;; CHECK-NEXT:   --generate-dyncalls                           generate dynCall fuctions used
 ;; CHECK-NEXT:                                                 by emscripten ABI
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --generate-global-effects                     generate global effect info
+;; CHECK-NEXT:                                                 (helps later passes)
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --generate-i64-dyncalls                       generate dynCall functions used
 ;; CHECK-NEXT:                                                 by emscripten ABI, but only for
 ;; CHECK-NEXT:                                                 functions with i64 in their
@@ -166,8 +174,19 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --global-refining                             refine the types of globals
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --gsi                                         globally optimize struct values
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --gto                                         globally optimize GC types
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --gufa                                        Grand Unified Flow Analysis:
+;; CHECK-NEXT:                                                 optimize the entire program
+;; CHECK-NEXT:                                                 using information about what
+;; CHECK-NEXT:                                                 content can actually appear in
+;; CHECK-NEXT:                                                 each location
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --gufa-optimizing                             GUFA plus local optimizations in
+;; CHECK-NEXT:                                                 functions we modified
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --heap2local                                  replace GC allocations with
 ;; CHECK-NEXT:                                                 locals
 ;; CHECK-NEXT:
@@ -192,6 +211,9 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --intrinsic-lowering                          lower away binaryen intrinsics
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --jspi                                        wrap imports and exports for
+;; CHECK-NEXT:                                                 JavaScript promise integration
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --legalize-js-interface                       legalizes i64 types on the
 ;; CHECK-NEXT:                                                 import/export boundary
 ;; CHECK-NEXT:
@@ -251,6 +273,15 @@
 ;; CHECK-NEXT:   --mod-asyncify-never-unwind                   apply the assumption that
 ;; CHECK-NEXT:                                                 asyncify never unwinds
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --monomorphize                                creates specialized versions of
+;; CHECK-NEXT:                                                 functions
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --monomorphize-always                         creates specialized versions of
+;; CHECK-NEXT:                                                 functions (even if unhelpful)
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --multi-memory-lowering                       combines multiple memories into
+;; CHECK-NEXT:                                                 a single memory
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --name-types                                  (re)name all heap types
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --nm                                          name list
@@ -265,6 +296,8 @@
 ;; CHECK-NEXT:                                                 load/store offsets, propagating
 ;; CHECK-NEXT:                                                 them across locals too
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --optimize-casts                              eliminate and reuse casts
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --optimize-for-js                             early optimize of the
 ;; CHECK-NEXT:                                                 instruction combinations for js
 ;; CHECK-NEXT:
@@ -330,6 +363,9 @@
 ;; CHECK-NEXT:   --reorder-functions                           sorts functions by access
 ;; CHECK-NEXT:                                                 frequency
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --reorder-globals                             sorts globals by access
+;; CHECK-NEXT:                                                 frequency
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --reorder-locals                              sorts locals by access frequency
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --rereloop                                    re-optimize control flow using
@@ -352,6 +388,9 @@
 ;; CHECK-NEXT:   --signature-refining                          apply more specific subtypes to
 ;; CHECK-NEXT:                                                 signature types where possible
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --signext-lowering                            lower sign-ext operations to
+;; CHECK-NEXT:                                                 wasm mvp
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --simplify-globals                            miscellaneous globals-related
 ;; CHECK-NEXT:                                                 optimizations
 ;; CHECK-NEXT:
@@ -382,6 +421,9 @@
 ;; CHECK-NEXT:   --souperify-single-use                        emit Souper IR in text form
 ;; CHECK-NEXT:                                                 (single-use nodes only)
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --spill-pointers                              spill pointers to the C stack
+;; CHECK-NEXT:                                                 (useful for Boehm-style GC)
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --ssa                                         ssa-ify variables so that they
 ;; CHECK-NEXT:                                                 have a single assignment
 ;; CHECK-NEXT:
@@ -583,11 +625,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                            Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references            Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references           Disable typed function
-;; CHECK-NEXT:                                                 references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                         Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals                        Disable GC non-null locals
@@ -602,6 +639,18 @@
 ;; CHECK-NEXT:   --disable-extended-const                      Disable extended const
 ;; CHECK-NEXT:                                                 expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                              Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                             Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories                       Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories                      Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references            Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references           Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                            Disables validation, assumes
 ;; CHECK-NEXT:                                                 inputs are correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test
index 2360ed5..9b394a7 100644
--- a/test/lit/help/wasm-reduce.test
+++ b/test/lit/help/wasm-reduce.test
@@ -117,10 +117,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -133,6 +129,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test
index f79d1c0..8da75cc 100644
--- a/test/lit/help/wasm-split.test
+++ b/test/lit/help/wasm-split.test
@@ -22,6 +22,9 @@
 ;; CHECK-NEXT:   --merge-profiles                     Merge multiple profiles for the same
 ;; CHECK-NEXT:                                        module into a single profile.
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --print-profile                      [print-profile] Print profile contents in
+;; CHECK-NEXT:                                        a human-readable format.
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --profile                            [split] The profile to use to guide
 ;; CHECK-NEXT:                                        splitting.
 ;; CHECK-NEXT:
@@ -51,9 +54,13 @@
 ;; CHECK-NEXT:   --placeholdermap                     [split] Write a file mapping placeholder
 ;; CHECK-NEXT:                                        indices to the function names.
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --import-namespace                   [split] The namespace from which to
-;; CHECK-NEXT:                                        import objects from the primary module
-;; CHECK-NEXT:                                        into the secondary module.
+;; CHECK-NEXT:   --import-namespace                   [split, instrument] When provided as an
+;; CHECK-NEXT:                                        option for module splitting, the
+;; CHECK-NEXT:                                        namespace from which to import objects
+;; CHECK-NEXT:                                        from the primary module into the
+;; CHECK-NEXT:                                        secondary module. In instrument mode,
+;; CHECK-NEXT:                                        refers to the namespace from which to
+;; CHECK-NEXT:                                        import the secondary memory, if any.
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --placeholder-namespace              [split] The namespace from which to
 ;; CHECK-NEXT:                                        import placeholder functions into the
@@ -82,6 +89,18 @@
 ;; CHECK-NEXT:                                        module does not use the initial memory
 ;; CHECK-NEXT:                                        region for anything else.
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --in-secondary-memory                [instrument] Store profile information in
+;; CHECK-NEXT:                                        a separate memory, rather than in module
+;; CHECK-NEXT:                                        main memory or globals (the default).
+;; CHECK-NEXT:                                        With this option, users do not need to
+;; CHECK-NEXT:                                        reserve the initial memory region for
+;; CHECK-NEXT:                                        profile data and the data can be shared
+;; CHECK-NEXT:                                        between multiple threads.
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --secondary-memory-name              [instrument] The name of the secondary
+;; CHECK-NEXT:                                        memory created to store profile
+;; CHECK-NEXT:                                        information.
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --emit-module-names                  [split, instrument] Emit module names,
 ;; CHECK-NEXT:                                        even if not emitting the rest of the
 ;; CHECK-NEXT:                                        names section. Can help differentiate the
@@ -105,6 +124,9 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --output,-o                          [instrument, merge-profiles] Output file.
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --unescape,-u                        Un-escape function names (in
+;; CHECK-NEXT:                                        print-profile output)
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --verbose,-v                         Verbose output mode. Prints the functions
 ;; CHECK-NEXT:                                        that will be kept and split out when
 ;; CHECK-NEXT:                                        splitting a module.
@@ -175,10 +197,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                   Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references   Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references  Disable typed function references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals               Disable GC non-null locals
@@ -191,6 +209,18 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-extended-const             Disable extended const expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                     Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                    Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories              Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories             Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references   Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references  Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                   Disables validation, assumes inputs are
 ;; CHECK-NEXT:                                        correct
 ;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 504e087..ce27483 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -88,6 +88,8 @@
 ;; CHECK-NEXT:   --directize                                   turns indirect calls into direct
 ;; CHECK-NEXT:                                                 ones
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --discard-global-effects                      discards global effect info
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --duplicate-function-elimination              removes duplicate functions
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --duplicate-import-elimination                removes duplicate imports
@@ -116,6 +118,9 @@
 ;; CHECK-NEXT:   --generate-dyncalls                           generate dynCall fuctions used
 ;; CHECK-NEXT:                                                 by emscripten ABI
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --generate-global-effects                     generate global effect info
+;; CHECK-NEXT:                                                 (helps later passes)
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --generate-i64-dyncalls                       generate dynCall functions used
 ;; CHECK-NEXT:                                                 by emscripten ABI, but only for
 ;; CHECK-NEXT:                                                 functions with i64 in their
@@ -128,8 +133,19 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --global-refining                             refine the types of globals
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --gsi                                         globally optimize struct values
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --gto                                         globally optimize GC types
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --gufa                                        Grand Unified Flow Analysis:
+;; CHECK-NEXT:                                                 optimize the entire program
+;; CHECK-NEXT:                                                 using information about what
+;; CHECK-NEXT:                                                 content can actually appear in
+;; CHECK-NEXT:                                                 each location
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --gufa-optimizing                             GUFA plus local optimizations in
+;; CHECK-NEXT:                                                 functions we modified
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --heap2local                                  replace GC allocations with
 ;; CHECK-NEXT:                                                 locals
 ;; CHECK-NEXT:
@@ -154,6 +170,9 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --intrinsic-lowering                          lower away binaryen intrinsics
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --jspi                                        wrap imports and exports for
+;; CHECK-NEXT:                                                 JavaScript promise integration
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --legalize-js-interface                       legalizes i64 types on the
 ;; CHECK-NEXT:                                                 import/export boundary
 ;; CHECK-NEXT:
@@ -213,6 +232,15 @@
 ;; CHECK-NEXT:   --mod-asyncify-never-unwind                   apply the assumption that
 ;; CHECK-NEXT:                                                 asyncify never unwinds
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --monomorphize                                creates specialized versions of
+;; CHECK-NEXT:                                                 functions
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --monomorphize-always                         creates specialized versions of
+;; CHECK-NEXT:                                                 functions (even if unhelpful)
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --multi-memory-lowering                       combines multiple memories into
+;; CHECK-NEXT:                                                 a single memory
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --name-types                                  (re)name all heap types
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --nm                                          name list
@@ -227,6 +255,8 @@
 ;; CHECK-NEXT:                                                 load/store offsets, propagating
 ;; CHECK-NEXT:                                                 them across locals too
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --optimize-casts                              eliminate and reuse casts
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --optimize-for-js                             early optimize of the
 ;; CHECK-NEXT:                                                 instruction combinations for js
 ;; CHECK-NEXT:
@@ -292,6 +322,9 @@
 ;; CHECK-NEXT:   --reorder-functions                           sorts functions by access
 ;; CHECK-NEXT:                                                 frequency
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --reorder-globals                             sorts globals by access
+;; CHECK-NEXT:                                                 frequency
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --reorder-locals                              sorts locals by access frequency
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --rereloop                                    re-optimize control flow using
@@ -314,6 +347,9 @@
 ;; CHECK-NEXT:   --signature-refining                          apply more specific subtypes to
 ;; CHECK-NEXT:                                                 signature types where possible
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --signext-lowering                            lower sign-ext operations to
+;; CHECK-NEXT:                                                 wasm mvp
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --simplify-globals                            miscellaneous globals-related
 ;; CHECK-NEXT:                                                 optimizations
 ;; CHECK-NEXT:
@@ -344,6 +380,9 @@
 ;; CHECK-NEXT:   --souperify-single-use                        emit Souper IR in text form
 ;; CHECK-NEXT:                                                 (single-use nodes only)
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --spill-pointers                              spill pointers to the C stack
+;; CHECK-NEXT:                                                 (useful for Boehm-style GC)
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --ssa                                         ssa-ify variables so that they
 ;; CHECK-NEXT:                                                 have a single assignment
 ;; CHECK-NEXT:
@@ -545,11 +584,6 @@
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-memory64                            Disable memory64
 ;; CHECK-NEXT:
-;; CHECK-NEXT:   --enable-typed-function-references            Enable typed function references
-;; CHECK-NEXT:
-;; CHECK-NEXT:   --disable-typed-function-references           Disable typed function
-;; CHECK-NEXT:                                                 references
-;; CHECK-NEXT:
 ;; CHECK-NEXT:   --enable-gc-nn-locals                         Enable GC non-null locals
 ;; CHECK-NEXT:
 ;; CHECK-NEXT:   --disable-gc-nn-locals                        Disable GC non-null locals
@@ -564,6 +598,18 @@
 ;; CHECK-NEXT:   --disable-extended-const                      Disable extended const
 ;; CHECK-NEXT:                                                 expressions
 ;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-strings                              Enable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-strings                             Disable strings
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-multi-memories                       Enable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-multi-memories                      Disable multi-memories
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --enable-typed-function-references            Deprecated compatibility flag
+;; CHECK-NEXT:
+;; CHECK-NEXT:   --disable-typed-function-references           Deprecated compatibility flag
+;; CHECK-NEXT:
 ;; CHECK-NEXT:   --no-validation,-n                            Disables validation, assumes
 ;; CHECK-NEXT:                                                 inputs are correct
 ;; CHECK-NEXT:
diff --git a/test/lit/isorecursive-good.wast b/test/lit/isorecursive-good.wast
index 7ced0d8..2d38bc9 100644
--- a/test/lit/isorecursive-good.wast
+++ b/test/lit/isorecursive-good.wast
@@ -1,4 +1,4 @@
-;; TODO: Autogenerate these checks! The current script cannot handle `rec`.
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
 
 ;; RUN: wasm-opt %s -all --hybrid -S -o - | filecheck %s --check-prefix HYBRID
 ;; RUN: wasm-opt %s -all --hybrid --roundtrip -S -o - | filecheck %s --check-prefix HYBRID
@@ -6,46 +6,63 @@
 
 (module
 
-;; HYBRID:      (rec
-;; HYBRID-NEXT:  (type $super-struct (struct_subtype (field i32) data))
-;; HYBRID-NEXT:  (type $sub-struct (struct_subtype (field i32) (field i64) $super-struct))
-;; HYBRID-NEXT: )
-
-;; HYBRID:      (rec
-;; HYBRID-NEXT:   (type $super-array (array_subtype (ref $super-struct) data))
-;; HYBRID-NEXT:   (type $sub-array (array_subtype (ref $sub-struct) $super-array))
-;; HYBRID-NEXT: )
-
-;; NOMINAL-NOT: rec
-
-;; NOMINAL:      (type $super-struct (struct_subtype (field i32) data))
-;; NOMINAL-NEXT: (type $sub-struct (struct_subtype (field i32) (field i64) $super-struct))
-
-;; NOMINAL:      (type $super-array (array_subtype (ref $super-struct) data))
-;; NOMINAL-NEXT: (type $sub-array (array_subtype (ref $sub-struct) $super-array))
 
   (rec
+    ;; HYBRID:      (rec
+    ;; HYBRID-NEXT:  (type $super-struct (struct_subtype (field i32) data))
+    ;; NOMINAL:      (type $super-struct (struct_subtype (field i32) data))
     (type $super-struct (struct i32))
+    ;; HYBRID:       (type $sub-struct (struct_subtype (field i32) (field i64) $super-struct))
+    ;; NOMINAL:      (type $sub-struct (struct_subtype (field i32) (field i64) $super-struct))
     (type $sub-struct (struct_subtype i32 i64 $super-struct))
   )
 
   (rec
+    ;; HYBRID:      (rec
+    ;; HYBRID-NEXT:  (type $super-array (array_subtype (ref $super-struct) data))
+    ;; NOMINAL:      (type $super-array (array_subtype (ref $super-struct) data))
     (type $super-array (array (ref $super-struct)))
+    ;; HYBRID:       (type $sub-array (array_subtype (ref $sub-struct) $super-array))
+    ;; NOMINAL:      (type $sub-array (array_subtype (ref $sub-struct) $super-array))
     (type $sub-array (array_subtype (ref $sub-struct) $super-array))
   )
 
+  ;; HYBRID:      (func $make-super-struct (type $none_=>_ref|$super-struct|) (result (ref $super-struct))
+  ;; HYBRID-NEXT:  (call $make-sub-struct)
+  ;; HYBRID-NEXT: )
+  ;; NOMINAL:      (func $make-super-struct (type $none_=>_ref|$super-struct|) (result (ref $super-struct))
+  ;; NOMINAL-NEXT:  (call $make-sub-struct)
+  ;; NOMINAL-NEXT: )
   (func $make-super-struct (result (ref $super-struct))
     (call $make-sub-struct)
   )
 
+  ;; HYBRID:      (func $make-sub-struct (type $none_=>_ref|$sub-struct|) (result (ref $sub-struct))
+  ;; HYBRID-NEXT:  (unreachable)
+  ;; HYBRID-NEXT: )
+  ;; NOMINAL:      (func $make-sub-struct (type $none_=>_ref|$sub-struct|) (result (ref $sub-struct))
+  ;; NOMINAL-NEXT:  (unreachable)
+  ;; NOMINAL-NEXT: )
   (func $make-sub-struct (result (ref $sub-struct))
     (unreachable)
   )
 
+  ;; HYBRID:      (func $make-super-array (type $none_=>_ref|$super-array|) (result (ref $super-array))
+  ;; HYBRID-NEXT:  (call $make-sub-array)
+  ;; HYBRID-NEXT: )
+  ;; NOMINAL:      (func $make-super-array (type $none_=>_ref|$super-array|) (result (ref $super-array))
+  ;; NOMINAL-NEXT:  (call $make-sub-array)
+  ;; NOMINAL-NEXT: )
   (func $make-super-array (result (ref $super-array))
     (call $make-sub-array)
   )
 
+  ;; HYBRID:      (func $make-sub-array (type $none_=>_ref|$sub-array|) (result (ref $sub-array))
+  ;; HYBRID-NEXT:  (unreachable)
+  ;; HYBRID-NEXT: )
+  ;; NOMINAL:      (func $make-sub-array (type $none_=>_ref|$sub-array|) (result (ref $sub-array))
+  ;; NOMINAL-NEXT:  (unreachable)
+  ;; NOMINAL-NEXT: )
   (func $make-sub-array (result (ref $sub-array))
     (unreachable)
   )
diff --git a/test/lit/isorecursive-output-ordering.wast b/test/lit/isorecursive-output-ordering.wast
index ae96512..b7fd614 100644
--- a/test/lit/isorecursive-output-ordering.wast
+++ b/test/lit/isorecursive-output-ordering.wast
@@ -1,4 +1,4 @@
-;; TODO: Autogenerate these checks! The current script cannot handle `rec`.
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
 
 ;; RUN: foreach %s %t wasm-opt -all --hybrid -S -o - | filecheck %s
 ;; RUN: foreach %s %t wasm-opt -all --hybrid --roundtrip -S -o - | filecheck %s
@@ -6,26 +6,25 @@
 (module
  ;; Test that we order groups by average uses.
 
- ;; CHECK:      (rec
- ;; CHECK-NEXT:  (type $unused-6 (struct_subtype data))
- ;; CHECK-NEXT:  (type $used-a-bit (struct_subtype data))
- ;; CHECK-NEXT: )
-
- ;; CHECK-NEXT: (rec
- ;; CHECK-NEXT:  (type $unused-1 (struct_subtype data))
- ;; CHECK-NEXT:  (type $unused-2 (struct_subtype data))
- ;; CHECK-NEXT:  (type $unused-3 (struct_subtype data))
- ;; CHECK-NEXT:  (type $unused-4 (struct_subtype data))
- ;; CHECK-NEXT:  (type $used-a-lot (struct_subtype data))
- ;; CHECK-NEXT:  (type $unused-5 (struct_subtype data))
- ;; CHECK-NEXT: )
 
  (rec
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $unused-6 (struct_subtype  data))
+
+  ;; CHECK:       (type $used-a-bit (struct_subtype  data))
+
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $unused-1 (struct_subtype  data))
   (type $unused-1 (struct_subtype data))
+  ;; CHECK:       (type $unused-2 (struct_subtype  data))
   (type $unused-2 (struct_subtype data))
+  ;; CHECK:       (type $unused-3 (struct_subtype  data))
   (type $unused-3 (struct_subtype data))
+  ;; CHECK:       (type $unused-4 (struct_subtype  data))
   (type $unused-4 (struct_subtype data))
+  ;; CHECK:       (type $used-a-lot (struct_subtype  data))
   (type $used-a-lot (struct_subtype data))
+  ;; CHECK:       (type $unused-5 (struct_subtype  data))
   (type $unused-5 (struct_subtype data))
  )
 
@@ -34,6 +33,9 @@
   (type $used-a-bit (struct_subtype data))
  )
 
+ ;; CHECK:      (func $use (type $ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_=>_ref|$used-a-bit|_ref|$used-a-bit|_ref|$used-a-bit|_ref|$used-a-bit|) (param $0 (ref $used-a-lot)) (param $1 (ref $used-a-lot)) (param $2 (ref $used-a-lot)) (param $3 (ref $used-a-lot)) (param $4 (ref $used-a-lot)) (param $5 (ref $used-a-lot)) (result (ref $used-a-bit) (ref $used-a-bit) (ref $used-a-bit) (ref $used-a-bit))
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
  (func $use (param (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot)) (result (ref $used-a-bit) (ref $used-a-bit) (ref $used-a-bit) (ref $used-a-bit))
   (unreachable)
  )
@@ -42,33 +44,25 @@
 (module
  ;; Test that we respect dependencies between groups before considering counts.
 
- ;; CHECK:      (rec
- ;; CHECK-NEXT:  (type $leaf (struct_subtype  data))
- ;; CHECK-NEXT:  (type $unused (struct_subtype  data))
- ;; CHECK-NEXT: )
-
- ;; CHECK-NEXT: (rec
- ;; CHECK-NEXT:  (type $shrub (struct_subtype  $leaf))
- ;; CHECK-NEXT:  (type $used-a-ton (struct_subtype  data))
- ;; CHECK-NEXT: )
-
- ;; CHECK-NEXT: (rec
- ;; CHECK-NEXT:  (type $twig (struct_subtype  data))
- ;; CHECK-NEXT:  (type $used-a-bit (struct_subtype (field (ref $leaf)) data))
- ;; CHECK-NEXT: )
-
- ;; CHECK-NEXT: (rec
- ;; CHECK-NEXT:  (type $root (struct_subtype  data))
- ;; CHECK-NEXT:  (type $used-a-lot (struct_subtype  $twig))
- ;; CHECK-NEXT: )
 
  (rec
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $leaf (struct_subtype  data))
   (type $leaf (struct_subtype data))
+  ;; CHECK:       (type $unused (struct_subtype  data))
   (type $unused (struct_subtype data))
  )
 
  (rec
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $shrub (struct_subtype  $leaf))
+
+  ;; CHECK:       (type $used-a-ton (struct_subtype  data))
+
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $twig (struct_subtype  data))
   (type $twig (struct_subtype data))
+  ;; CHECK:       (type $used-a-bit (struct_subtype (field (ref $leaf)) data))
   (type $used-a-bit (struct_subtype (ref $leaf) data))
  )
 
@@ -78,10 +72,23 @@
  )
 
  (rec
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $root (struct_subtype  data))
   (type $root (struct_subtype data))
+  ;; CHECK:       (type $used-a-lot (struct_subtype  $twig))
   (type $used-a-lot (struct_subtype $twig))
  )
 
+ ;; CHECK:      (func $use (type $ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_ref|$used-a-lot|_=>_ref|$used-a-bit|_ref|$used-a-bit|_ref|$used-a-bit|) (param $0 (ref $used-a-lot)) (param $1 (ref $used-a-lot)) (param $2 (ref $used-a-lot)) (param $3 (ref $used-a-lot)) (param $4 (ref $used-a-lot)) (param $5 (ref $used-a-lot)) (result (ref $used-a-bit) (ref $used-a-bit) (ref $used-a-bit))
+ ;; CHECK-NEXT:  (local $6 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (local $7 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (local $8 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (local $9 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (local $10 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (local $11 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (local $12 (ref null $used-a-ton))
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
  (func $use (param (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot) (ref $used-a-lot)) (result (ref $used-a-bit) (ref $used-a-bit) (ref $used-a-bit))
   (local (ref null $used-a-ton) (ref null $used-a-ton) (ref null $used-a-ton) (ref null $used-a-ton) (ref null $used-a-ton) (ref null $used-a-ton) (ref null $used-a-ton))
   (unreachable)
@@ -92,9 +99,13 @@
  ;; Test that basic heap type children do not trigger assertions.
 
  (rec
+  ;; CHECK:      (type $contains-basic (struct_subtype (field (ref any)) data))
   (type $contains-basic (struct_subtype (ref any) data))
  )
 
+ ;; CHECK:      (func $use (type $ref|$contains-basic|_=>_none) (param $0 (ref $contains-basic))
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
  (func $use (param (ref $contains-basic))
    (unreachable)
  )
diff --git a/test/lit/isorecursive-singleton-group.wast b/test/lit/isorecursive-singleton-group.wast
index eeb92ac..bde2848 100644
--- a/test/lit/isorecursive-singleton-group.wast
+++ b/test/lit/isorecursive-singleton-group.wast
@@ -1,4 +1,4 @@
-;; TODO: Autogenerate these checks! The current script cannot handle `rec`.
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
 
 ;; RUN: wasm-opt %s -all --hybrid -S -o - | filecheck %s
 ;; RUN: wasm-opt %s -all --hybrid --roundtrip -S -o - | filecheck %s
@@ -8,13 +8,13 @@
 
 (module
 
-;; CHECK-NOT: rec
-;; CHECK: (type $singleton (struct_subtype data))
 
  (rec
+  ;; CHECK:      (type $singleton (struct_subtype  data))
   (type $singleton (struct_subtype data))
  )
 
  ;; Use the type so it appears in the output.
+ ;; CHECK:      (global $g (ref null $singleton) (ref.null none))
  (global $g (ref null $singleton) (ref.null $singleton))
 )
diff --git a/test/lit/isorecursive-whole-group.wast b/test/lit/isorecursive-whole-group.wast
index 4d349a3..70f2f1e 100644
--- a/test/lit/isorecursive-whole-group.wast
+++ b/test/lit/isorecursive-whole-group.wast
@@ -1,4 +1,4 @@
-;; TODO: Autogenerate these checks! The current script cannot handle `rec`.
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
 
 ;; RUN: wasm-opt %s -all --hybrid -S -o - | filecheck %s
 ;; RUN: wasm-opt %s -all --hybrid --roundtrip -S -o - | filecheck %s
@@ -8,15 +8,15 @@
 
 (module
 
-;; CHECK:      (rec
-;; CHECK-NEXT:  (type $used (struct_subtype data))
-;; CHECK-NEXT:  (type $unused (struct_subtype data))
-;; CHECK-NEXT: )
 
  (rec
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $used (struct_subtype  data))
   (type $used (struct_subtype data))
+  ;; CHECK:       (type $unused (struct_subtype  data))
   (type $unused (struct_subtype data))
  )
 
+ ;; CHECK:      (global $g (ref null $used) (ref.null none))
  (global $g (ref null $used) (ref.null $used))
 )
diff --git a/test/lit/lub-bug-3843.wast b/test/lit/lub-bug-3843.wast
index f5bb439..768d47f 100644
--- a/test/lit/lub-bug-3843.wast
+++ b/test/lit/lub-bug-3843.wast
@@ -15,37 +15,38 @@
  ;; NOMNL:      (type $B (struct_subtype (field (ref null $D)) $A))
  (type $B (struct_subtype (field (ref null $D)) $A))
 
+ ;; CHECK:      (type $C (struct (field (mut (ref $A)))))
+
  ;; CHECK:      (type $D (struct (field (mut (ref $A))) (field (mut (ref $A)))))
  ;; NOMNL:      (type $C (struct_subtype (field (mut (ref $A))) data))
 
  ;; NOMNL:      (type $D (struct_subtype (field (mut (ref $A))) (field (mut (ref $A))) $C))
  (type $D (struct_subtype (field (mut (ref $A))) (field (mut (ref $A))) $C))
 
- ;; CHECK:      (type $C (struct (field (mut (ref $A)))))
  (type $C (struct (field (mut (ref $A)))))
 
 
- ;; CHECK:      (func $foo (param $a (ref null $A)) (result (ref null $A))
+ ;; CHECK:      (func $foo (param $a (ref null $A)) (param $b (ref null $B)) (result (ref null $A))
  ;; CHECK-NEXT:  (select (result (ref null $A))
  ;; CHECK-NEXT:   (local.get $a)
- ;; CHECK-NEXT:   (ref.null $B)
+ ;; CHECK-NEXT:   (local.get $b)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $foo (type $ref?|$A|_=>_ref?|$A|) (param $a (ref null $A)) (result (ref null $A))
+ ;; NOMNL:      (func $foo (type $ref?|$A|_ref?|$B|_=>_ref?|$A|) (param $a (ref null $A)) (param $b (ref null $B)) (result (ref null $A))
  ;; NOMNL-NEXT:  (select (result (ref null $A))
  ;; NOMNL-NEXT:   (local.get $a)
- ;; NOMNL-NEXT:   (ref.null $B)
+ ;; NOMNL-NEXT:   (local.get $b)
  ;; NOMNL-NEXT:   (i32.const 0)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
- (func $foo (param $a (ref null $A)) (result (ref null $A))
+ (func $foo (param $a (ref null $A)) (param $b (ref null $B)) (result (ref null $A))
   ;; the select should have type $A
   (select (result (ref null $A))
    ;; one arm has type $A
    (local.get $a)
    ;; one arm has type $B (a subtype of $A)
-   (ref.null $B)
+   (local.get $b)
    (i32.const 0)
   )
  )
diff --git a/test/lit/memory64-limits.wast b/test/lit/memory64-limits.wast
index 29e51d2..f2d395a 100644
--- a/test/lit/memory64-limits.wast
+++ b/test/lit/memory64-limits.wast
@@ -1,9 +1,35 @@
 ;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s
 
+;; CHECK:     (module
+;; CHECK-NEXT: (type $none_=>_i64 (func (result i64)))
+;; CHECK-NEXT: (type $none_=>_none (func))
+;; CHECK-NEXT: (memory $0 i64 1 4294967296)
+;; CHECK-NEXT: (func $load_i64 (result i64)
+;; CHECK-NEXT:  (i64.load offset=8589934592
+;; CHECK-NEXT:   (i64.const 4294967296)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (func $store
+;; CHECK-NEXT:  (i64.store offset=8589934592
+;; CHECK-NEXT:   (i64.const 4294967296)
+;; CHECK-NEXT:   (i64.const 123)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+;; CHECK-NEXT:)
+
 (module
  (memory $0 i64 1 4294967296)
-)
 
-;; CHECK:      (module
-;; CHECK-NEXT:  (memory $0 i64 1 4294967296)
-;; CHECK-NEXT: )
+  (func $load_i64 (result i64)
+    (i64.load offset=8589934592
+      (i64.const 0x100000000)
+    )
+  )
+
+  (func $store (result)
+    (i64.store offset=8589934592
+      (i64.const 0x100000000)
+      (i64.const 123)
+    )
+  )
+)
diff --git a/test/lit/memory64-ops.wast b/test/lit/memory64-ops.wast
new file mode 100644
index 0000000..5ed44df
--- /dev/null
+++ b/test/lit/memory64-ops.wast
@@ -0,0 +1,32 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt -all --roundtrip -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (memory $mem i64 1)
+  (memory $mem i64 1)
+
+  ;; CHECK:      (func $get_heap_size (result i64)
+  ;; CHECK-NEXT:  (i64.shl
+  ;; CHECK-NEXT:   (memory.size)
+  ;; CHECK-NEXT:   (i64.const 16)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get_heap_size (result i64)
+    (i64.shl
+      (memory.size $mem)
+      (i64.const 16)
+    )
+  )
+
+  ;; CHECK:      (func $grow-heap (result i64)
+  ;; CHECK-NEXT:  (memory.grow
+  ;; CHECK-NEXT:   (i64.const 32)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $grow-heap (result i64)
+    (memory.grow $mem
+      (i64.const 32)
+    )
+  )
+)
diff --git a/test/lit/multi-memories-atomics64.wast b/test/lit/multi-memories-atomics64.wast
new file mode 100644
index 0000000..15941b1
--- /dev/null
+++ b/test/lit/multi-memories-atomics64.wast
@@ -0,0 +1,704 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-as %s -all -g -o %t.wasm
+;; RUN: wasm-dis %t.wasm -o - | filecheck %s
+
+(module
+ ;; CHECK:      (type $0 (func))
+ (type $0 (func))
+ ;; CHECK:      (memory $appMemory (shared i64 23 256))
+ (memory $appMemory (shared i64 23 256))
+ ;; CHECK:      (memory $dataMemory (shared i64 23 256))
+ (memory $dataMemory (shared i64 23 256))
+ ;; CHECK:      (memory $instrumentMemory (shared i64 23 256))
+ (memory $instrumentMemory (shared i64 23 256))
+ ;; CHECK:      (func $atomic-loadstore
+ ;; CHECK-NEXT:  (local $0 i64)
+ ;; CHECK-NEXT:  (local $1 i64)
+ ;; CHECK-NEXT:  (local $2 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load8_u $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load8_u $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load16_u $dataMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load16_u $instrumentMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load $dataMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load8_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load8_u $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load16_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load16_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load32_u $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load32_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.load $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.atomic.store $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.atomic.store $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.atomic.store8 $instrumentMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.atomic.store8 $dataMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.atomic.store16 $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.atomic.store16 $dataMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store8 $dataMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store8 $instrumentMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store16 $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store16 $appMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store32 $instrumentMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store32 $dataMemory offset=4
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-loadstore (type $0)
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.load8_u 0 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u 1 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $instrumentMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load 1 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $dataMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u 2
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (i32.atomic.store 0 offset=4 align=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store $appMemory offset=4 align=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 2 offset=4 align=1
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $dataMemory offset=4 align=1
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 0 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i64.atomic.store offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 1 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 2 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $atomic-rmw
+ ;; CHECK-NEXT:  (local $0 i64)
+ ;; CHECK-NEXT:  (local $1 i64)
+ ;; CHECK-NEXT:  (local $2 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw.add $dataMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw.add $instrumentMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.add_u $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.add_u $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw16.and_u $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw16.and_u $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw32.or_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw32.or_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.xchg_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.xchg_u $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-rmw (type $0)
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.add $dataMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.add 2 offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u 0 offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u 1 align=2
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $instrumentMemory align=2
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u 0
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u 0 align=1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $dataMemory align=1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+ )
+ ;; CHECK:      (func $atomic-cmpxchg
+ ;; CHECK-NEXT:  (local $0 i64)
+ ;; CHECK-NEXT:  (local $1 i64)
+ ;; CHECK-NEXT:  (local $2 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw.cmpxchg $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw.cmpxchg $instrumentMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.cmpxchg_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.cmpxchg_u $appMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw.cmpxchg $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw.cmpxchg $dataMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw32.cmpxchg_u $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw32.cmpxchg_u $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-cmpxchg (type $0)
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.cmpxchg 0 offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.cmpxchg $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $dataMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u 2 align=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $dataMemory align=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ ;; CHECK:      (func $atomic-wait-notify
+ ;; CHECK-NEXT:  (local $0 i64)
+ ;; CHECK-NEXT:  (local $1 i64)
+ ;; CHECK-NEXT:  (local $2 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait32 $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait32 $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait32 $appMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait32 $instrumentMemory offset=4
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.notify $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.notify $dataMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.notify $appMemory offset=24
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.notify $dataMemory offset=24
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait64 $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait64 $instrumentMemory
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait64 $appMemory offset=16
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait64 $appMemory offset=16
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-wait-notify (type $0)
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (memory.atomic.wait32 $dataMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 2
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $appMemory offset=4 align=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 2 offset=4 align=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.notify 1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $appMemory offset=24 align=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify 1 offset=24 align=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 2
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory align=8 offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 0 align=8 offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ ;; CHECK:      (func $atomic-fence
+ ;; CHECK-NEXT:  (atomic.fence)
+ ;; CHECK-NEXT: )
+ (func $atomic-fence (type $0)
+  (atomic.fence)
+ )
+)
diff --git a/test/lit/multi-memories-basics.wast b/test/lit/multi-memories-basics.wast
new file mode 100644
index 0000000..42b6091
--- /dev/null
+++ b/test/lit/multi-memories-basics.wast
@@ -0,0 +1,175 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-as %s -all -g -o %t.wasm
+;; RUN: wasm-dis %t.wasm -o - | filecheck %s
+
+(module
+  ;; CHECK:      (import "env" "memory" (memory $importedMemory 1 1))
+
+  ;; CHECK:      (memory $memory1 1 500)
+  (memory $memory1 1 500)
+  ;; CHECK:      (memory $memory2 1 800)
+  (memory $memory2 1 800)
+  ;; CHECK:      (memory $memory3 1 400)
+  (memory $memory3 1 400)
+  (data (memory $memory1) (i32.const 0) "a" "" "bcd")
+  (import "env" "memory" (memory $importedMemory 1 1))
+  ;; CHECK:      (data (i32.const 0) "abcd")
+
+  ;; CHECK:      (func $memory.fill
+  ;; CHECK-NEXT:  (memory.fill $memory2
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $memory.fill
+    (memory.fill 1
+      (i32.const 0)
+      (i32.const 1)
+      (i32.const 2)
+    )
+  )
+  ;; CHECK:      (func $memory.copy
+  ;; CHECK-NEXT:  (memory.copy $memory2 $memory3
+  ;; CHECK-NEXT:   (i32.const 512)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $memory.copy
+    (memory.copy 1 2
+      (i32.const 512)
+      (i32.const 0)
+      (i32.const 12)
+    )
+  )
+  ;; CHECK:      (func $memory.init
+  ;; CHECK-NEXT:  (memory.init $memory1 0
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 45)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $memory.init
+    (memory.init 0 0
+      (i32.const 0)
+      (i32.const 0)
+      (i32.const 45)
+    )
+  )
+  ;; CHECK:      (func $memory.grow (result i32)
+  ;; CHECK-NEXT:  (memory.grow $memory3
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $memory.grow (result i32)
+    (memory.grow 2
+	  (i32.const 10)
+    )
+  )
+  ;; CHECK:      (func $memory.size (result i32)
+  ;; CHECK-NEXT:  (memory.size $memory3)
+  ;; CHECK-NEXT: )
+  (func $memory.size (result i32)
+    (memory.size 2)
+  )
+  ;; CHECK:      (func $loads
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load $memory1
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load $memory3
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load16_s $memory2
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load16_s $memory2
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load8_s $memory3
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load8_s $memory3
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load16_u $memory1
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load16_u $memory1
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load8_u $memory2
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load8_u $memory2
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $loads
+    (drop (i32.load 0 (i32.const 12)))
+    (drop (i32.load $memory3 (i32.const 12)))
+    (drop (i32.load16_s 1 (i32.const 12)))
+    (drop (i32.load16_s $memory2 (i32.const 12)))
+    (drop (i32.load8_s 2 (i32.const 12)))
+    (drop (i32.load8_s $memory3 (i32.const 12)))
+    (drop (i32.load16_u 0 (i32.const 12)))
+    (drop (i32.load16_u $memory1 (i32.const 12)))
+    (drop (i32.load8_u 1 (i32.const 12)))
+    (drop (i32.load8_u $memory2 (i32.const 12)))
+  )
+  ;; CHECK:      (func $stores
+  ;; CHECK-NEXT:  (i32.store $memory1
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:   (i32.const 115)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store $memory1
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:   (i32.const 115)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store16 $memory2
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:   (i32.const 31353)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store16 $importedMemory
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:   (i32.const 31353)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store8 $memory3
+  ;; CHECK-NEXT:   (i32.const 23)
+  ;; CHECK-NEXT:   (i32.const 120)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store8 $memory3
+  ;; CHECK-NEXT:   (i32.const 23)
+  ;; CHECK-NEXT:   (i32.const 120)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $stores
+    (i32.store 0 (i32.const 12) (i32.const 115))
+    (i32.store $memory1 (i32.const 12) (i32.const 115))
+	(i32.store16 1 (i32.const 20) (i32.const 31353))
+	(i32.store16 $importedMemory (i32.const 20) (i32.const 31353))
+    (i32.store8 2 (i32.const 23) (i32.const 120))
+    (i32.store8 $memory3 (i32.const 23) (i32.const 120))
+  )
+)
+
diff --git a/test/lit/multi-memories-simd.wast b/test/lit/multi-memories-simd.wast
new file mode 100644
index 0000000..184d988
--- /dev/null
+++ b/test/lit/multi-memories-simd.wast
@@ -0,0 +1,635 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-as %s -all -g -o %t.wasm
+;; RUN: wasm-dis %t.wasm -o - | filecheck %s
+
+(module
+ ;; CHECK:      (memory $memorya 1 1)
+ (memory $memorya 1 1)
+ ;; CHECK:      (memory $memoryb 1 1)
+ (memory $memoryb 1 1)
+ ;; CHECK:      (memory $memoryc 1 1)
+ (memory $memoryc 1 1)
+ ;; CHECK:      (memory $memoryd 1 1)
+ (memory $memoryd 1 1)
+ ;; CHECK:      (func $v128.load (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load (param $0 i32) (result v128)
+  (v128.load offset=0 align=16
+    (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load2 (param $0 i32) (result v128)
+  (v128.load $memoryb offset=0 align=16
+    (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load8x8_s (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load8x8_s $memoryc
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8x8_s (param $0 i32) (result v128)
+  (v128.load8x8_s 2
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load8x8_s2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load8x8_s $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8x8_s2 (param $0 i32) (result v128)
+  (v128.load8x8_s 1
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load8x8_u (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load8x8_u $memoryd
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8x8_u (param $0 i32) (result v128)
+  (v128.load8x8_u 3
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load8x8_u2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load8x8_u $memoryd
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8x8_u2 (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load16x4_s (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load16x4_s $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16x4_s (param $0 i32) (result v128)
+  (v128.load16x4_s 0
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load16x4_s2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load16x4_s $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16x4_s2 (param $0 i32) (result v128)
+  (v128.load16x4_s $memoryb
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load16x4_u (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load16x4_u $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16x4_u (param $0 i32) (result v128)
+  (v128.load16x4_u 0
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load16x4_u2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load16x4_u $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16x4_u2 (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32x2_s (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32x2_s $memoryc
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32x2_s (param $0 i32) (result v128)
+  (v128.load32x2_s 2
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32x2_s2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32x2_s $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32x2_s2 (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryb
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32x2_u (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32x2_u $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32x2_u (param $0 i32) (result v128)
+  (v128.load32x2_u 1
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32x2_u2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32x2_u $memoryc
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32x2_u2 (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryc
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load8_splat (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load8_splat $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8_splat (param $0 i32) (result v128)
+  (v128.load8_splat 1
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load8_splat2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load8_splat $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8_splat2 (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load16_splat (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load16_splat $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16_splat (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load16_splat2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load16_splat $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16_splat2 (param $0 i32) (result v128)
+  (v128.load16_splat 0
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32_splat (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32_splat $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32_splat (param $0 i32) (result v128)
+  (v128.load32_splat 1
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32_splat2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32_splat $memoryd
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32_splat2 (param $0 i32) (result v128)
+  (v128.load32_splat $memoryd
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_splat (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_splat $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_splat (param $0 i32) (result v128)
+  (v128.load64_splat 1
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_splat2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_splat $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_splat2 (param $0 i32) (result v128)
+  (v128.load64_splat $memorya
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.store (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store (param $0 i32) (param $1 v128)
+  (v128.store 0 offset=0 align=16
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store2 (param $0 i32) (param $1 v128)
+  (v128.store 1 offset=0 align=16
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load8_lane (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load8_lane $memorya 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane 0 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load8_lane2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load8_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load8_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load16_lane (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load16_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane 1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load16_lane2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load16_lane $memoryd 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load16_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load32_lane (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load32_lane $memorya 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load32_lane2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load32_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane 1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memoryd 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane 1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane_align (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memorya align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane_align (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane 0 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane_align2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memoryb align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane_align2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane_offset (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memoryc offset=32 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane 2 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane_offset2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memoryb offset=32 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane_align_offset (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memorya offset=32 align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane_align_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane align=1 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_lane_align_offset2 (param $0 i32) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_lane $memoryd offset=32 align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_lane_align_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd align=1 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store8_lane (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store8_lane $memorya 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store8_lane (param $0 i32) (param $1 v128)
+  (v128.store8_lane 0 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store8_lane2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store8_lane $memoryd 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store8_lane2 (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store16_lane (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store16_lane $memorya 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store16_lane (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store16_lane2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store16_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store16_lane2 (param $0 i32) (param $1 v128)
+  (v128.store16_lane 1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store32_lane (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store32_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store32_lane (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store32_lane2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store32_lane $memoryc 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store32_lane2 (param $0 i32) (param $1 v128)
+  (v128.store32_lane 2 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memoryc 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memoryb 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane 1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane_align (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memoryb align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane_align (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane_align2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memorya align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane_align2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane 0 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane_offset (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memoryd offset=32 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane 3 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane_offset2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memorya offset=32 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane_align_offset (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memoryb offset=32 align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane_align_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane 1 align=1 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.store64_lane_align_offset2 (param $0 i32) (param $1 v128)
+ ;; CHECK-NEXT:  (v128.store64_lane $memoryd offset=32 align=1 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.store64_lane_align_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd align=1 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ ;; CHECK:      (func $v128.load32_zero (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32_zero $memorya
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32_zero (param $0 i32) (result v128)
+  (v128.load32_zero 0
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load32_zero2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load32_zero $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load32_zero2 (param $0 i32) (result v128)
+  (v128.load32_zero $memoryb
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_zero (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_zero $memoryb
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_zero (param $0 i32) (result v128)
+  (v128.load64_zero 1
+   (local.get $0)
+  )
+ )
+ ;; CHECK:      (func $v128.load64_zero2 (param $0 i32) (result v128)
+ ;; CHECK-NEXT:  (v128.load64_zero $memoryc
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $v128.load64_zero2 (param $0 i32) (result v128)
+  (v128.load64_zero $memoryc
+   (local.get $0)
+  )
+ )
+)
+
diff --git a/test/lit/nominal-chain.wast b/test/lit/nominal-chain.wast
index dda04b3..1403fbe 100644
--- a/test/lit/nominal-chain.wast
+++ b/test/lit/nominal-chain.wast
@@ -26,10 +26,10 @@
 
   (type $root (struct))
 
-  ;; CHECK:      (func $make-root (type $none_=>_ref?|$root|) (result (ref null $root))
-  ;; CHECK-NEXT:  (ref.null $leaf)
+  ;; CHECK:      (func $make-root (type $ref|$leaf|_=>_ref?|$root|) (param $leaf (ref $leaf)) (result (ref null $root))
+  ;; CHECK-NEXT:  (local.get $leaf)
   ;; CHECK-NEXT: )
-  (func $make-root (result (ref null $root))
-    (ref.null $leaf)
+  (func $make-root (param $leaf (ref $leaf)) (result (ref null $root))
+    (local.get $leaf)
   )
 )
diff --git a/test/lit/nominal-no-gc.wast b/test/lit/nominal-no-gc.wast
new file mode 100644
index 0000000..82f2698
--- /dev/null
+++ b/test/lit/nominal-no-gc.wast
@@ -0,0 +1,5 @@
+;; Using --nominal without GC is not allowed.
+
+;; RUN: not wasm-opt %s --nominal --disable-gc -g -o %t.wasm 2>&1 | filecheck %s
+
+;; CHECK: Nominal typing is only allowed when GC is enabled
diff --git a/test/lit/nominal-to-isorecursive.wast b/test/lit/nominal-to-isorecursive.wast
index c1deadb..0d31a37 100644
--- a/test/lit/nominal-to-isorecursive.wast
+++ b/test/lit/nominal-to-isorecursive.wast
@@ -1,4 +1,4 @@
-;; TODO: Autogenerate these checks! The current script cannot handle `rec`.
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
 
 ;; RUN: wasm-as %s -all --nominal -g -o %t.wasm
 ;; RUN: wasm-dis %t.wasm -all --hybrid -o - | filecheck %s
@@ -6,30 +6,29 @@
 ;; Check that the nominal binary format is parseable as isorecursive with a
 ;; single recursion group.
 
-;; CHECK:      (module
-;; CHECK-NEXT:  (rec
-;; CHECK-NEXT:   (type $make-super-t (func_subtype (result (ref $super)) func))
-;; CHECK-NEXT:   (type $make-sub-t (func_subtype (result (ref $sub)) func))
-;; CHECK-NEXT:   (type $super (struct_subtype (field i32) data))
-;; CHECK-NEXT:   (type $sub (struct_subtype (field i32) $super))
-;; CHECK-NEXT:  )
-;; CHECK-NEXT:  (func $make-super (type $make-super-t) (result (ref $super))
-;; CHECK-NEXT:   (unreachable)
-;; CHECK-NEXT:  )
-;; CHECK-NEXT:  (func $make-sub (type $make-sub-t) (result (ref $sub))
-;; CHECK-NEXT:   (unreachable)
-;; CHECK-NEXT:  )
-;; CHECK-NEXT: )
 (module
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $make-super-t (func_subtype (result (ref $super)) func))
+
+  ;; CHECK:       (type $make-sub-t (func_subtype (result (ref $sub)) func))
+
+  ;; CHECK:       (type $super (struct_subtype (field i32) data))
   (type $super (struct i32))
+  ;; CHECK:       (type $sub (struct_subtype (field i32) $super))
   (type $sub (struct_subtype i32 $super))
   (type $make-super-t (func (result (ref $super))))
   (type $make-sub-t (func (result (ref $sub))))
 
+  ;; CHECK:      (func $make-super (type $make-super-t) (result (ref $super))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
   (func $make-super (type $make-super-t)
     (unreachable)
   )
 
+  ;; CHECK:      (func $make-sub (type $make-sub-t) (result (ref $sub))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
   (func $make-sub (type $make-sub-t)
     (unreachable)
   )
diff --git a/test/lit/non-nullable-locals.wast b/test/lit/non-nullable-locals.wast
new file mode 100644
index 0000000..2a469b9
--- /dev/null
+++ b/test/lit/non-nullable-locals.wast
@@ -0,0 +1,176 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt -all -S -o - | filecheck %s
+
+;; Tests for validation of non-nullable locals.
+
+(module
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $ref|func|_=>_none (func (param (ref func))))
+
+  ;; CHECK:      (type $funcref_=>_i32 (func (param funcref) (result i32)))
+
+  ;; CHECK:      (elem declare func $helper)
+
+  ;; CHECK:      (func $no-uses
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $no-uses
+    ;; A local with no uses validates.
+    (local $x (ref func))
+  )
+
+  ;; CHECK:      (func $func-scope
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (ref.func $helper)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func-scope
+    ;; a set in the func scope helps a get validate there.
+    (local $x (ref func))
+    (local.set $x
+      (ref.func $helper)
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $inner-scope
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (block $b
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $helper)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $inner-scope
+    ;; a set in an inner scope helps a get validate there.
+    (local $x (ref func))
+    (block $b
+      (local.set $x
+        (ref.func $helper)
+      )
+      (drop
+        (local.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $func-to-inner
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (ref.func $helper)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block $b
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func-to-inner
+    ;; a set in an outer scope helps a get validate.
+    (local $x (ref func))
+    (local.set $x
+      (ref.func $helper)
+    )
+    (block $b
+      (drop
+        (local.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $inner-to-func
+  ;; CHECK-NEXT:  (local $x funcref)
+  ;; CHECK-NEXT:  (block $b
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $helper)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $inner-to-func
+    ;; a set in an inner scope does *not* help a get validate, but the type is
+    ;; nullable so that's ok.
+    (local $x (ref null func))
+    (block $b
+      (local.set $x
+        (ref.func $helper)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $if-condition
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (call $helper2
+  ;; CHECK-NEXT:    (local.tee $x
+  ;; CHECK-NEXT:     (ref.func $helper)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-condition
+    (local $x (ref func))
+    (if
+      (call $helper2
+        ;; Tee in the condition is good enough for the arms.
+        (local.tee $x
+          (ref.func $helper)
+        )
+      )
+      (drop
+        (local.get $x)
+      )
+      (drop
+        (local.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-without-set-but-param (param $x (ref func))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-without-set-but-param
+    ;; As a parameter, this is ok to get without a set.
+    (param $x (ref func))
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $helper
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $helper)
+
+  ;; CHECK:      (func $helper2 (param $0 funcref) (result i32)
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $helper2 (param funcref) (result i32)
+    (unreachable)
+  )
+)
diff --git a/test/lit/parse-double-unreachable.wast b/test/lit/parse-double-unreachable.wast
new file mode 100644
index 0000000..0dd657a
--- /dev/null
+++ b/test/lit/parse-double-unreachable.wast
@@ -0,0 +1,43 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+
+;; RUN: wasm-opt %s -all --nominal --roundtrip -S -o - | filecheck %s
+
+;; Regression test for a bug in which we could pop the expression stack past an
+;; unreachable if we were already in unreachable parsing mode.
+
+(module
+
+ ;; CHECK:      (type $array (array_subtype i8 data))
+ (type $array (array i8))
+ (type $func (func (result i32)))
+
+ ;; CHECK:      (func $double-unreachable (type $ref|$array|_=>_i32) (param $x (ref $array)) (result i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.null nofunc)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $double-unreachable (param $x (ref $array)) (result i32)
+
+  (drop
+   ;; This gets emitted as an unreachable, but it doesn't have type
+   ;; unreachable, so we continue emitting instructions afterward. When
+   ;; parsing, this will put us into unreachable mode.
+   (call_ref $func
+    (ref.null nofunc)
+   )
+  )
+
+  (array.get_u $array
+   (local.get $x)
+
+   ;; Since this call_ref will be emitted as an unreachable, it does not consume
+   ;; the ref.null when parsing. Due to the bug, the ref.null would remain on
+   ;; the stack and would be incorrectly consumed as the index to the
+   ;; array.get_u, producing a type error.
+   (call_ref $func
+    (ref.null nofunc)
+   )
+  )
+ )
+)
diff --git a/test/lit/parse-named-memory.wast b/test/lit/parse-named-memory.wast
new file mode 100644
index 0000000..3573376
--- /dev/null
+++ b/test/lit/parse-named-memory.wast
@@ -0,0 +1,14 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Regression test for a binary parser bug in which memory names on data
+;; segments were not properly updated to use memory names from the name section.
+
+;; RUN: wasm-as %s -g -o %t.wasm
+;; RUN: wasm-opt %t.wasm -S -o - | filecheck %s
+
+(module
+ ;; CHECK:      (memory $mem0 100)
+ (memory $mem0 100)
+ (data (offset (i32.const 0)) "")
+)
+;; CHECK:      (data (i32.const 0) "")
diff --git a/test/lit/parse-nominal-types-extends.wast b/test/lit/parse-nominal-types-extends.wast
index ea9461e..9dbf6a0 100644
--- a/test/lit/parse-nominal-types-extends.wast
+++ b/test/lit/parse-nominal-types-extends.wast
@@ -8,65 +8,55 @@
 
 ;; void function type
 (module
-  ;; CHECK:      (type $super (func_subtype func))
-
-  ;; CHECK:      (type $sub (func_subtype $super))
   (type $sub (func) (extends $super))
 
+  ;; CHECK:      (type $super (func_subtype func))
   (type $super (func))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null nofunc))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; function type with params and results
 (module
-  ;; CHECK:      (type $super (func_subtype (param i32) (result i32) func))
-
-  ;; CHECK:      (type $sub (func_subtype (param i32) (result i32) $super))
   (type $sub (func (param i32) (result i32)) (extends $super))
 
+  ;; CHECK:      (type $super (func_subtype (param i32) (result i32) func))
   (type $super (func (param i32) (result i32)))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null nofunc))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; empty struct type
 (module
-  ;; CHECK:      (type $super (struct_subtype  data))
-
-  ;; CHECK:      (type $sub (struct_subtype  $super))
   (type $sub (struct) (extends $super))
 
+  ;; CHECK:      (type $super (struct_subtype  data))
   (type $super (struct))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null none))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; struct type with fields
 (module
-  ;; CHECK:      (type $super (struct_subtype (field i32) (field i64) data))
-
-  ;; CHECK:      (type $sub (struct_subtype (field i32) (field i64) $super))
   (type $sub (struct i32 (field i64)) (extends $super))
 
+  ;; CHECK:      (type $super (struct_subtype (field i32) (field i64) data))
   (type $super (struct (field i32) i64))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null none))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; array type
 (module
-  ;; CHECK:      (type $super (array_subtype i8 data))
-
-  ;; CHECK:      (type $sub (array_subtype i8 $super))
   (type $sub (array i8) (extends $super))
 
+  ;; CHECK:      (type $super (array_subtype i8 data))
   (type $super (array i8))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null none))
   (global $g (ref null $super) (ref.null $sub))
 )
diff --git a/test/lit/parse-nominal-types.wast b/test/lit/parse-nominal-types.wast
index db681ea..6bb0c83 100644
--- a/test/lit/parse-nominal-types.wast
+++ b/test/lit/parse-nominal-types.wast
@@ -8,65 +8,55 @@
 
 ;; void function type
 (module
-  ;; CHECK:      (type $super (func_subtype func))
-
-  ;; CHECK:      (type $sub (func_subtype $super))
   (type $sub (func_subtype $super))
 
+  ;; CHECK:      (type $super (func_subtype func))
   (type $super (func_subtype func))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null nofunc))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; function type with params and results
 (module
-  ;; CHECK:      (type $super (func_subtype (param i32) (result i32) func))
-
-  ;; CHECK:      (type $sub (func_subtype (param i32) (result i32) $super))
   (type $sub (func_subtype (param i32) (result i32) $super))
 
+  ;; CHECK:      (type $super (func_subtype (param i32) (result i32) func))
   (type $super (func_subtype (param i32) (result i32) func))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null nofunc))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; empty struct type
 (module
-  ;; CHECK:      (type $super (struct_subtype  data))
-
-  ;; CHECK:      (type $sub (struct_subtype  $super))
   (type $sub (struct_subtype $super))
 
+  ;; CHECK:      (type $super (struct_subtype  data))
   (type $super (struct_subtype data))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null none))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; struct type with fields
 (module
-  ;; CHECK:      (type $super (struct_subtype (field i32) (field i64) data))
-
-  ;; CHECK:      (type $sub (struct_subtype (field i32) (field i64) $super))
   (type $sub (struct_subtype i32 (field i64) $super))
 
+  ;; CHECK:      (type $super (struct_subtype (field i32) (field i64) data))
   (type $super (struct_subtype (field i32) i64 data))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null none))
   (global $g (ref null $super) (ref.null $sub))
 )
 
 ;; array type
 (module
-  ;; CHECK:      (type $super (array_subtype i8 data))
-
-  ;; CHECK:      (type $sub (array_subtype i8 $super))
   (type $sub (array_subtype i8 $super))
 
+  ;; CHECK:      (type $super (array_subtype i8 data))
   (type $super (array_subtype i8 data))
 
-  ;; CHECK:      (global $g (ref null $super) (ref.null $sub))
+  ;; CHECK:      (global $g (ref null $super) (ref.null none))
   (global $g (ref null $super) (ref.null $sub))
 )
diff --git a/test/lit/passes/O.wast b/test/lit/passes/O.wast
index f7c9a00..946cf12 100644
--- a/test/lit/passes/O.wast
+++ b/test/lit/passes/O.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -O -S -o - | filecheck %s
 
diff --git a/test/lit/passes/O1.wast b/test/lit/passes/O1.wast
index 823b4a1..9821a21 100644
--- a/test/lit/passes/O1.wast
+++ b/test/lit/passes/O1.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -O1 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/O3_inline-functions-with-loops_flexible-inline-max-function-size=30.wast b/test/lit/passes/O3_inline-functions-with-loops_flexible-inline-max-function-size=30.wast
index 2f34c6e..e004557 100644
--- a/test/lit/passes/O3_inline-functions-with-loops_flexible-inline-max-function-size=30.wast
+++ b/test/lit/passes/O3_inline-functions-with-loops_flexible-inline-max-function-size=30.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -O3 --inline-functions-with-loops --flexible-inline-max-function-size=30 -S -o - | filecheck %s
 
@@ -8,6 +8,8 @@
   (type $t0 (func (param i32) (result i32)))
   ;; CHECK:      (memory $memory 0)
 
+  ;; CHECK:      (export "memory" (memory $memory))
+
   ;; CHECK:      (export "fib" (func $fib))
 
   ;; CHECK:      (export "looped" (func $looped))
@@ -20,8 +22,6 @@
 
   ;; CHECK:      (export "t3" (func $t3))
 
-  ;; CHECK:      (export "memory" (memory $memory))
-
   ;; CHECK:      (func $fib (; has Stack IR ;) (param $0 i32) (result i32)
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (i32.le_s
diff --git a/test/lit/passes/O3_inlining.wast b/test/lit/passes/O3_inlining.wast
index 1851551..0acb210 100644
--- a/test/lit/passes/O3_inlining.wast
+++ b/test/lit/passes/O3_inlining.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -O3 --inlining -S -o - | filecheck %s
 
diff --git a/test/lit/passes/O4_disable-bulk-memory.wast b/test/lit/passes/O4_disable-bulk-memory.wast
index 4ab4031..c806bf8 100644
--- a/test/lit/passes/O4_disable-bulk-memory.wast
+++ b/test/lit/passes/O4_disable-bulk-memory.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -O4 --disable-bulk-memory -S -o - | filecheck %s
 
@@ -58,16 +58,16 @@
  (data (i32.const 8) "\0d\00\00\00~\00l\00i\00b\00/\00a\00r\00r\00a\00y\00.\00t\00s\00")
  (data (i32.const 40) "\1c\00\00\00~\00l\00i\00b\00/\00i\00n\00t\00e\00r\00n\00a\00l\00/\00a\00r\00r\00a\00y\00b\00u\00f\00f\00e\00r\00.\00t\00s\00")
  (import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32)))
- ;; CHECK:      (data (i32.const 8) "\0d\00\00\00~\00l\00i\00b\00/\00a\00r\00r\00a\00y\00.\00t\00s\00")
-
- ;; CHECK:      (data (i32.const 40) "\1c\00\00\00~\00l\00i\00b\00/\00i\00n\00t\00e\00r\00n\00a\00l\00/\00a\00r\00r\00a\00y\00b\00u\00f\00f\00e\00r\00.\00t\00s\00")
-
  ;; CHECK:      (global $global$0 (mut i32) (i32.const 0))
 
  ;; CHECK:      (global $global$1 (mut i32) (i32.const 0))
 
  ;; CHECK:      (global $global$5 (mut i32) (i32.const 0))
 
+ ;; CHECK:      (data (i32.const 8) "\r\00\00\00~\00l\00i\00b\00/\00a\00r\00r\00a\00y\00.\00t\00s\00")
+
+ ;; CHECK:      (data (i32.const 40) "\1c\00\00\00~\00l\00i\00b\00/\00i\00n\00t\00e\00r\00n\00a\00l\00/\00a\00r\00r\00a\00y\00b\00u\00f\00f\00e\00r\00.\00t\00s\00")
+
  ;; CHECK:      (table $0 1 funcref)
  (table $0 1 funcref)
  (elem (i32.const 0) $null)
@@ -80,8 +80,8 @@
  (global $global$6 i32 (i32.const 100))
  ;; CHECK:      (elem (i32.const 0) $null)
 
- ;; CHECK:      (export "memory" (memory $0))
- (export "memory" (memory $0))
+ ;; CHECK:      (export "memory" (memory $1))
+ (export "memory" (memory $1))
  ;; CHECK:      (export "table" (table $0))
  (export "table" (table $0))
  ;; CHECK:      (export "init" (func $assembly/index/init))
@@ -236,9 +236,9 @@
  ;; CHECK-NEXT:      (i32.add
  ;; CHECK-NEXT:       (i32.add
  ;; CHECK-NEXT:        (select
- ;; CHECK-NEXT:         (local.get $0)
  ;; CHECK-NEXT:         (i32.const 1)
- ;; CHECK-NEXT:         (i32.gt_u
+ ;; CHECK-NEXT:         (local.get $0)
+ ;; CHECK-NEXT:         (i32.le_u
  ;; CHECK-NEXT:          (local.get $0)
  ;; CHECK-NEXT:          (i32.const 1)
  ;; CHECK-NEXT:         )
diff --git a/test/lit/passes/O_fast-math.wast b/test/lit/passes/O_fast-math.wast
index 4d0224f..b1b8e95 100644
--- a/test/lit/passes/O_fast-math.wast
+++ b/test/lit/passes/O_fast-math.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -O --fast-math -S -o - | filecheck %s
 
diff --git a/test/lit/passes/Oz.wast b/test/lit/passes/Oz.wast
index 7dc8c7c..59bfe46 100644
--- a/test/lit/passes/Oz.wast
+++ b/test/lit/passes/Oz.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt -Oz --all-features -S -o - | filecheck %s
 
diff --git a/test/lit/passes/alignment-lowering.wast b/test/lit/passes/alignment-lowering.wast
index 03abf79..9875f9a 100644
--- a/test/lit/passes/alignment-lowering.wast
+++ b/test/lit/passes/alignment-lowering.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --alignment-lowering -S -o - | filecheck %s
 
diff --git a/test/lit/passes/alignment-lowering64.wast b/test/lit/passes/alignment-lowering64.wast
index 61d9526..5c20cc7 100644
--- a/test/lit/passes/alignment-lowering64.wast
+++ b/test/lit/passes/alignment-lowering64.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --alignment-lowering --enable-memory64 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify-wasm64.wast b/test/lit/passes/asyncify-wasm64.wast
new file mode 100644
index 0000000..7d6c216
--- /dev/null
+++ b/test/lit/passes/asyncify-wasm64.wast
@@ -0,0 +1,1335 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt --enable-memory64 --asyncify %s -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $i32_i32_=>_none (func (param i32 i32)))
+
+  ;; CHECK:      (type $f (func (param i32)))
+  (type $f (func (param i32)))
+  (memory i64 1 2)
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $i64_=>_none (func (param i64)))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (import "env" "import" (func $import))
+  (import "env" "import" (func $import))
+  ;; CHECK:      (import "env" "import2" (func $import2 (param i32)))
+  (import "env" "import2" (func $import2 (param i32)))
+  (table funcref (elem $liveness2 $liveness2))
+  ;; CHECK:      (global $__asyncify_state (mut i32) (i32.const 0))
+
+  ;; CHECK:      (global $__asyncify_data (mut i64) (i64.const 0))
+
+  ;; CHECK:      (memory $0 i64 1 2)
+
+  ;; CHECK:      (table $0 2 2 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $liveness2 $liveness2)
+
+  ;; CHECK:      (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+
+  ;; CHECK:      (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+
+  ;; CHECK:      (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+
+  ;; CHECK:      (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+
+  ;; CHECK:      (export "asyncify_get_state" (func $asyncify_get_state))
+
+  ;; CHECK:      (func $liveness1 (param $live0 i32) (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $live1 i32)
+  ;; CHECK-NEXT:  (local $dead1 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i32)
+  ;; CHECK-NEXT:  (local $6 i32)
+  ;; CHECK-NEXT:  (local $7 i32)
+  ;; CHECK-NEXT:  (local $8 i32)
+  ;; CHECK-NEXT:  (local $9 i32)
+  ;; CHECK-NEXT:  (local $10 i64)
+  ;; CHECK-NEXT:  (local $11 i64)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i64.store
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i64.add
+  ;; CHECK-NEXT:      (i64.load
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i64.const -8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $10
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live0
+  ;; CHECK-NEXT:     (i32.load
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live1
+  ;; CHECK-NEXT:     (i32.load offset=4
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $8
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $9
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $4
+  ;; CHECK-NEXT:          (local.get $dead0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $5
+  ;; CHECK-NEXT:          (local.get $dead1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $5)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $9)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $6
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $6)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $7
+  ;; CHECK-NEXT:          (local.get $live1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $7)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $11
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store offset=4
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness1 (param $live0 i32) (param $dead0 i32)
+    (local $live1 i32)
+    (local $dead1 i32)
+    (drop (local.get $dead0))
+    (drop (local.get $dead1))
+    (call $import)
+    (drop (local.get $live0))
+    (drop (local.get $live1))
+  )
+  ;; CHECK:      (func $liveness2 (param $live0 i32) (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $live1 i32)
+  ;; CHECK-NEXT:  (local $dead1 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i32)
+  ;; CHECK-NEXT:  (local $6 i32)
+  ;; CHECK-NEXT:  (local $7 i32)
+  ;; CHECK-NEXT:  (local $8 i32)
+  ;; CHECK-NEXT:  (local $9 i32)
+  ;; CHECK-NEXT:  (local $10 i64)
+  ;; CHECK-NEXT:  (local $11 i64)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i64.store
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i64.add
+  ;; CHECK-NEXT:      (i64.load
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i64.const -8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $10
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live0
+  ;; CHECK-NEXT:     (i32.load
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live1
+  ;; CHECK-NEXT:     (i32.load offset=4
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $8
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $9
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $4
+  ;; CHECK-NEXT:          (local.get $dead0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $5
+  ;; CHECK-NEXT:          (local.get $dead1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $5)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $9)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $6
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $6)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $7
+  ;; CHECK-NEXT:          (local.get $live1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $7)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $11
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store offset=4
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness2 (param $live0 i32) (param $dead0 i32)
+    (local $live1 i32)
+    (local $dead1 i32)
+    (drop (local.get $dead0))
+    (drop (local.get $dead1))
+    (call $import)
+    (drop (local.get $live0))
+    (drop (local.get $live1))
+  )
+  ;; CHECK:      (func $liveness3 (param $live0 i32) (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $live1 i32)
+  ;; CHECK-NEXT:  (local $dead1 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i32)
+  ;; CHECK-NEXT:  (local $6 i32)
+  ;; CHECK-NEXT:  (local $7 i32)
+  ;; CHECK-NEXT:  (local $8 i64)
+  ;; CHECK-NEXT:  (local $9 i64)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i64.store
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i64.add
+  ;; CHECK-NEXT:      (i64.load
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i64.const -8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $8
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live0
+  ;; CHECK-NEXT:     (i32.load
+  ;; CHECK-NEXT:      (local.get $8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live1
+  ;; CHECK-NEXT:     (i32.load offset=4
+  ;; CHECK-NEXT:      (local.get $8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $6
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $7
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $7)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $4
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $7)
+  ;; CHECK-NEXT:          (i32.const 1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $5
+  ;; CHECK-NEXT:          (local.get $live1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $5)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $6)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $9
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (local.get $9)
+  ;; CHECK-NEXT:    (local.get $live0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store offset=4
+  ;; CHECK-NEXT:    (local.get $9)
+  ;; CHECK-NEXT:    (local.get $live1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness3 (param $live0 i32) (param $dead0 i32)
+    (local $live1 i32)
+    (local $dead1 i32)
+    (call $import)
+    (drop (local.get $live0))
+    (call $import)
+    (drop (local.get $live1))
+  )
+  ;; CHECK:      (func $liveness4 (param $live0 i32) (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $2 i32)
+  ;; CHECK-NEXT:  (local $3 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i64)
+  ;; CHECK-NEXT:  (local $6 i64)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i64.store
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i64.add
+  ;; CHECK-NEXT:      (i64.load
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i64.const -4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $5
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live0
+  ;; CHECK-NEXT:     (i32.load
+  ;; CHECK-NEXT:      (local.get $5)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $3
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $4
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.or
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 2)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (if
+  ;; CHECK-NEXT:         (if (result i32)
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i32.const 1)
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (local.get $4)
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (block
+  ;; CHECK-NEXT:          (call $import)
+  ;; CHECK-NEXT:          (if
+  ;; CHECK-NEXT:           (i32.eq
+  ;; CHECK-NEXT:            (global.get $__asyncify_state)
+  ;; CHECK-NEXT:            (i32.const 1)
+  ;; CHECK-NEXT:           )
+  ;; CHECK-NEXT:           (br $__asyncify_unwind
+  ;; CHECK-NEXT:            (i32.const 0)
+  ;; CHECK-NEXT:           )
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $2
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $2)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $6
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (local.get $6)
+  ;; CHECK-NEXT:    (local.get $live0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness4 (param $live0 i32) (param $dead0 i32)
+    (if (i32.const 0)
+      (call $import)
+    )
+    (drop (local.get $live0))
+  )
+  ;; CHECK:      (func $liveness5 (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $1 i32)
+  ;; CHECK-NEXT:  (local $2 i32)
+  ;; CHECK-NEXT:  (local $3 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $3
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $4
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $1
+  ;; CHECK-NEXT:          (local.get $dead0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.or
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 2)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (if
+  ;; CHECK-NEXT:         (if (result i32)
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i32.const 1)
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (local.get $4)
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (block
+  ;; CHECK-NEXT:          (call $import)
+  ;; CHECK-NEXT:          (if
+  ;; CHECK-NEXT:           (i32.eq
+  ;; CHECK-NEXT:            (global.get $__asyncify_state)
+  ;; CHECK-NEXT:            (i32.const 1)
+  ;; CHECK-NEXT:           )
+  ;; CHECK-NEXT:           (br $__asyncify_unwind
+  ;; CHECK-NEXT:            (i32.const 0)
+  ;; CHECK-NEXT:           )
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $dead0
+  ;; CHECK-NEXT:          (i32.const 1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $2
+  ;; CHECK-NEXT:          (local.get $dead0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $2)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $liveness5 (param $dead0 i32)
+    (drop (local.get $dead0))
+    (if (i32.const 0)
+      (call $import) ;; live before and after call, but not during
+    )
+    (local.set $dead0 (i32.const 1))
+    (drop (local.get $dead0))
+  )
+  ;; CHECK:      (func $liveness-call-kills (param $live i32)
+  ;; CHECK-NEXT:  (local $1 i32)
+  ;; CHECK-NEXT:  (local $2 i32)
+  ;; CHECK-NEXT:  (local $3 i32)
+  ;; CHECK-NEXT:  (local $4 i64)
+  ;; CHECK-NEXT:  (local $5 i64)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i64.store
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i64.add
+  ;; CHECK-NEXT:      (i64.load
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i64.const -4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $4
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $1
+  ;; CHECK-NEXT:     (i32.load
+  ;; CHECK-NEXT:      (local.get $4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $2
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $3
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $1
+  ;; CHECK-NEXT:         (local.get $live)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $3)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import2
+  ;; CHECK-NEXT:          (local.get $1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $5
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (local.get $5)
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness-call-kills (param $live i32)
+    (call $import2 (local.get $live))
+  )
+  ;; CHECK:      (func $liveness-indirect-kills (param $live0 i32) (param $live1 i32)
+  ;; CHECK-NEXT:  (local $2 i32)
+  ;; CHECK-NEXT:  (local $3 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i32)
+  ;; CHECK-NEXT:  (local $6 i64)
+  ;; CHECK-NEXT:  (local $7 i64)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i64.store
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i64.add
+  ;; CHECK-NEXT:      (i64.load
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i64.const -8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $6
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $2
+  ;; CHECK-NEXT:     (i32.load
+  ;; CHECK-NEXT:      (local.get $6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $3
+  ;; CHECK-NEXT:     (i32.load offset=4
+  ;; CHECK-NEXT:      (local.get $6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $4
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i64.store
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i64.add
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i64.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $5
+  ;; CHECK-NEXT:         (i32.load
+  ;; CHECK-NEXT:          (i64.load
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $2
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $3
+  ;; CHECK-NEXT:          (local.get $live1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $5)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call_indirect (type $f)
+  ;; CHECK-NEXT:          (local.get $2)
+  ;; CHECK-NEXT:          (local.get $3)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $7
+  ;; CHECK-NEXT:    (i64.load
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store
+  ;; CHECK-NEXT:    (local.get $7)
+  ;; CHECK-NEXT:    (local.get $2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store offset=4
+  ;; CHECK-NEXT:    (local.get $7)
+  ;; CHECK-NEXT:    (local.get $3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.store
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i64.add
+  ;; CHECK-NEXT:     (i64.load
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i64.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness-indirect-kills (param $live0 i32) (param $live1 i32)
+    (call_indirect (type $f) (local.get $live0) (local.get $live1))
+  )
+)
+;; CHECK:      (func $asyncify_start_unwind (param $0 i64)
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $__asyncify_data
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i64.gt_u
+;; CHECK-NEXT:    (i64.load
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.load offset=8
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_stop_unwind
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i64.gt_u
+;; CHECK-NEXT:    (i64.load
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.load offset=8
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_start_rewind (param $0 i64)
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 2)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $__asyncify_data
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i64.gt_u
+;; CHECK-NEXT:    (i64.load
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.load offset=8
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_stop_rewind
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i64.gt_u
+;; CHECK-NEXT:    (i64.load
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.load offset=8
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_get_state (result i32)
+;; CHECK-NEXT:  (global.get $__asyncify_state)
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/asyncify-wasm64_pass-arg=in-secondary-memory.wast b/test/lit/passes/asyncify-wasm64_pass-arg=in-secondary-memory.wast
new file mode 100644
index 0000000..7342365
--- /dev/null
+++ b/test/lit/passes/asyncify-wasm64_pass-arg=in-secondary-memory.wast
@@ -0,0 +1,312 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt --enable-memory64 --enable-multi-memories --asyncify --pass-arg=asyncify-in-secondary-memory %s -S -o - | filecheck %s
+
+(module
+  (memory i64 1 2)
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $i32_i32_=>_none (func (param i32 i32)))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (import "env" "import" (func $import))
+  (import "env" "import" (func $import))
+  ;; CHECK:      (global $__asyncify_state (mut i32) (i32.const 0))
+
+  ;; CHECK:      (global $__asyncify_data (mut i32) (i32.const 0))
+
+  ;; CHECK:      (memory $0 i64 1 2)
+
+  ;; CHECK:      (memory $asyncify_memory 1 1)
+
+  ;; CHECK:      (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+
+  ;; CHECK:      (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+
+  ;; CHECK:      (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+
+  ;; CHECK:      (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+
+  ;; CHECK:      (export "asyncify_get_state" (func $asyncify_get_state))
+
+  ;; CHECK:      (func $liveness1 (param $live0 i32) (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $live1 i32)
+  ;; CHECK-NEXT:  (local $dead1 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i32)
+  ;; CHECK-NEXT:  (local $6 i32)
+  ;; CHECK-NEXT:  (local $7 i32)
+  ;; CHECK-NEXT:  (local $8 i32)
+  ;; CHECK-NEXT:  (local $9 i32)
+  ;; CHECK-NEXT:  (local $10 i32)
+  ;; CHECK-NEXT:  (local $11 i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i32.store $asyncify_memory
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i32.add
+  ;; CHECK-NEXT:      (i32.load $asyncify_memory
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const -8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $10
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live0
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live1
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory offset=4
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $8
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i32.store $asyncify_memory
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i32.add
+  ;; CHECK-NEXT:          (i32.load $asyncify_memory
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i32.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $9
+  ;; CHECK-NEXT:         (i32.load $asyncify_memory
+  ;; CHECK-NEXT:          (i32.load $asyncify_memory
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $4
+  ;; CHECK-NEXT:          (local.get $dead0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $5
+  ;; CHECK-NEXT:          (local.get $dead1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $5)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $9)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $6
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $6)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $7
+  ;; CHECK-NEXT:          (local.get $live1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $7)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (i32.load $asyncify_memory
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $11
+  ;; CHECK-NEXT:    (i32.load $asyncify_memory
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory offset=4
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $liveness1 (param $live0 i32) (param $dead0 i32)
+    (local $live1 i32)
+    (local $dead1 i32)
+    (drop (local.get $dead0))
+    (drop (local.get $dead1))
+    (call $import)
+    (drop (local.get $live0))
+    (drop (local.get $live1))
+  )
+)
+;; CHECK:      (func $asyncify_start_unwind (param $0 i32)
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $__asyncify_data
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_stop_unwind
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_start_rewind (param $0 i32)
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 2)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $__asyncify_data
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_stop_rewind
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_get_state (result i32)
+;; CHECK-NEXT:  (global.get $__asyncify_state)
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/asyncify.wast b/test/lit/passes/asyncify.wast
index 7c52f14..0da90c1 100644
--- a/test/lit/passes/asyncify.wast
+++ b/test/lit/passes/asyncify.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_enable-multivalue.wast b/test/lit/passes/asyncify_enable-multivalue.wast
index 5f7ec2a..2821003 100644
--- a/test/lit/passes/asyncify_enable-multivalue.wast
+++ b/test/lit/passes/asyncify_enable-multivalue.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --enable-multivalue -S -o - | filecheck %s
 
@@ -49,7 +49,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (local.get $1)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (global.set $sleeping
   ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
@@ -57,7 +57,7 @@
   ;; CHECK-NEXT:     (i32.const 4)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block0
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (global.set $sleeping
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
diff --git a/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind.wast b/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind.wast
index 80c0aa8..dc89dab 100644
--- a/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind.wast
+++ b/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --mod-asyncify-always-and-only-unwind -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind_O.wast b/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind_O.wast
index 491343f..2983777 100644
--- a/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind_O.wast
+++ b/test/lit/passes/asyncify_mod-asyncify-always-and-only-unwind_O.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --mod-asyncify-always-and-only-unwind -O -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_mod-asyncify-never-unwind.wast b/test/lit/passes/asyncify_mod-asyncify-never-unwind.wast
index b572971..dce366b 100644
--- a/test/lit/passes/asyncify_mod-asyncify-never-unwind.wast
+++ b/test/lit/passes/asyncify_mod-asyncify-never-unwind.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --mod-asyncify-never-unwind -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_mod-asyncify-never-unwind_O.wast b/test/lit/passes/asyncify_mod-asyncify-never-unwind_O.wast
index 95d418a..1a2a945 100644
--- a/test/lit/passes/asyncify_mod-asyncify-never-unwind_O.wast
+++ b/test/lit/passes/asyncify_mod-asyncify-never-unwind_O.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --mod-asyncify-never-unwind -O -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_optimize-level=1.wast b/test/lit/passes/asyncify_optimize-level=1.wast
index cc1da38..0affdb7 100644
--- a/test/lit/passes/asyncify_optimize-level=1.wast
+++ b/test/lit/passes/asyncify_optimize-level=1.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --optimize-level=1 -S -o - | filecheck %s
 
@@ -752,8 +752,14 @@
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
-  ;; CHECK-NEXT:     (select
-  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     (i32.and
+  ;; CHECK-NEXT:      (i32.eqz
+  ;; CHECK-NEXT:       (select
+  ;; CHECK-NEXT:        (local.get $1)
+  ;; CHECK-NEXT:        (i32.const 0)
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (i32.or
   ;; CHECK-NEXT:       (i32.eqz
   ;; CHECK-NEXT:        (local.get $2)
@@ -763,11 +769,6 @@
   ;; CHECK-NEXT:        (i32.const 2)
   ;; CHECK-NEXT:       )
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (select
-  ;; CHECK-NEXT:       (local.get $1)
-  ;; CHECK-NEXT:       (i32.const 0)
-  ;; CHECK-NEXT:       (global.get $__asyncify_state)
-  ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (call $import3
@@ -906,8 +907,10 @@
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
-  ;; CHECK-NEXT:     (select
-  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     (i32.and
+  ;; CHECK-NEXT:      (i32.eqz
+  ;; CHECK-NEXT:       (global.get $__asyncify_state)
+  ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (i32.or
   ;; CHECK-NEXT:       (i32.eqz
   ;; CHECK-NEXT:        (local.get $1)
@@ -917,7 +920,6 @@
   ;; CHECK-NEXT:        (i32.const 2)
   ;; CHECK-NEXT:       )
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (global.get $__asyncify_state)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (return
   ;; CHECK-NEXT:      (i32.const 2)
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast
index aed1e01..861b7ff 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-addlist@foo -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast
index b514cda..28d3f88 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-ignore-indirect.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-addlist@foo --pass-arg=asyncify-ignore-indirect -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast b/test/lit/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast
index 8f263ef..03a8dfe 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-asserts --pass-arg=asyncify-onlylist@waka -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast b/test/lit/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast
index 6646290..83dd0a2 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-blacklist@foo,bar -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-ignore-imports.wast b/test/lit/passes/asyncify_pass-arg=asyncify-ignore-imports.wast
index 41297c6..9fd019f 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-ignore-imports.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-ignore-imports.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-ignore-imports -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-ignore-indirect.wast b/test/lit/passes/asyncify_pass-arg=asyncify-ignore-indirect.wast
index 8fb5518..1d7fd17 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-ignore-indirect.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-ignore-indirect.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-ignore-indirect -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast b/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast
index 7ca59a4..5b355ba 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-imports@env.import,env.import2.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-imports@env.import,env.import2 -S -o - | filecheck %s
 
@@ -48,7 +48,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (local.get $1)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (global.set $sleeping
   ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
@@ -56,7 +56,7 @@
   ;; CHECK-NEXT:     (i32.const 4)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block0
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (global.set $sleeping
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast b/test/lit/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast
index 494794c..438216c 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-onlylist@foo,bar -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast b/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast
index 8a9cd6c..fbf96a9 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --enable-mutable-globals --asyncify --pass-arg=asyncify-relocatable -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-verbose.wast b/test/lit/passes/asyncify_pass-arg=asyncify-verbose.wast
index 7d55d83..c605b26 100644
--- a/test/lit/passes/asyncify_pass-arg=asyncify-verbose.wast
+++ b/test/lit/passes/asyncify_pass-arg=asyncify-verbose.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-verbose -S -o - | filecheck %s
 
diff --git a/test/lit/passes/asyncify_pass-arg=in-secondary-memory.wast b/test/lit/passes/asyncify_pass-arg=in-secondary-memory.wast
new file mode 100644
index 0000000..961c6d7
--- /dev/null
+++ b/test/lit/passes/asyncify_pass-arg=in-secondary-memory.wast
@@ -0,0 +1,609 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt --enable-multi-memories --asyncify --pass-arg=asyncify-in-secondary-memory %s -S -o - | filecheck %s
+;; RUN: wasm-opt --enable-multi-memories --asyncify --pass-arg=asyncify-in-secondary-memory --pass-arg=asyncify-secondary-memory-size@3 %s -S -o - | filecheck %s --check-prefix SIZE
+
+(module
+  (memory 1 2)
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $i32_i32_=>_none (func (param i32 i32)))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (import "env" "import" (func $import))
+  ;; SIZE:      (type $none_=>_none (func))
+
+  ;; SIZE:      (type $i32_=>_none (func (param i32)))
+
+  ;; SIZE:      (type $i32_i32_=>_none (func (param i32 i32)))
+
+  ;; SIZE:      (type $none_=>_i32 (func (result i32)))
+
+  ;; SIZE:      (import "env" "import" (func $import))
+  (import "env" "import" (func $import))
+  ;; CHECK:      (global $__asyncify_state (mut i32) (i32.const 0))
+
+  ;; CHECK:      (global $__asyncify_data (mut i32) (i32.const 0))
+
+  ;; CHECK:      (memory $0 1 2)
+
+  ;; CHECK:      (memory $asyncify_memory 1 1)
+
+  ;; CHECK:      (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+
+  ;; CHECK:      (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+
+  ;; CHECK:      (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+
+  ;; CHECK:      (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+
+  ;; CHECK:      (export "asyncify_get_state" (func $asyncify_get_state))
+
+  ;; CHECK:      (func $liveness1 (param $live0 i32) (param $dead0 i32)
+  ;; CHECK-NEXT:  (local $live1 i32)
+  ;; CHECK-NEXT:  (local $dead1 i32)
+  ;; CHECK-NEXT:  (local $4 i32)
+  ;; CHECK-NEXT:  (local $5 i32)
+  ;; CHECK-NEXT:  (local $6 i32)
+  ;; CHECK-NEXT:  (local $7 i32)
+  ;; CHECK-NEXT:  (local $8 i32)
+  ;; CHECK-NEXT:  (local $9 i32)
+  ;; CHECK-NEXT:  (local $10 i32)
+  ;; CHECK-NEXT:  (local $11 i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (global.get $__asyncify_state)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (i32.store $asyncify_memory
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     (i32.add
+  ;; CHECK-NEXT:      (i32.load $asyncify_memory
+  ;; CHECK-NEXT:       (global.get $__asyncify_data)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const -8)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $10
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live0
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $live1
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory offset=4
+  ;; CHECK-NEXT:      (local.get $10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $8
+  ;; CHECK-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.eq
+  ;; CHECK-NEXT:        (global.get $__asyncify_state)
+  ;; CHECK-NEXT:        (i32.const 2)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (block
+  ;; CHECK-NEXT:        (i32.store $asyncify_memory
+  ;; CHECK-NEXT:         (global.get $__asyncify_data)
+  ;; CHECK-NEXT:         (i32.add
+  ;; CHECK-NEXT:          (i32.load $asyncify_memory
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (i32.const -4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (local.set $9
+  ;; CHECK-NEXT:         (i32.load $asyncify_memory
+  ;; CHECK-NEXT:          (i32.load $asyncify_memory
+  ;; CHECK-NEXT:           (global.get $__asyncify_data)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $4
+  ;; CHECK-NEXT:          (local.get $dead0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $4)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $5
+  ;; CHECK-NEXT:          (local.get $dead1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $5)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (if (result i32)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (global.get $__asyncify_state)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (i32.const 1)
+  ;; CHECK-NEXT:         (i32.eq
+  ;; CHECK-NEXT:          (local.get $9)
+  ;; CHECK-NEXT:          (i32.const 0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:         (if
+  ;; CHECK-NEXT:          (i32.eq
+  ;; CHECK-NEXT:           (global.get $__asyncify_state)
+  ;; CHECK-NEXT:           (i32.const 1)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:          (br $__asyncify_unwind
+  ;; CHECK-NEXT:           (i32.const 0)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (if
+  ;; CHECK-NEXT:        (i32.eq
+  ;; CHECK-NEXT:         (global.get $__asyncify_state)
+  ;; CHECK-NEXT:         (i32.const 0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (block
+  ;; CHECK-NEXT:         (local.set $6
+  ;; CHECK-NEXT:          (local.get $live0)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $6)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (local.set $7
+  ;; CHECK-NEXT:          (local.get $live1)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (local.get $7)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:       (nop)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (return)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (i32.load $asyncify_memory
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $11
+  ;; CHECK-NEXT:    (i32.load $asyncify_memory
+  ;; CHECK-NEXT:     (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory offset=4
+  ;; CHECK-NEXT:    (local.get $11)
+  ;; CHECK-NEXT:    (local.get $live1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.store $asyncify_memory
+  ;; CHECK-NEXT:    (global.get $__asyncify_data)
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (i32.load $asyncify_memory
+  ;; CHECK-NEXT:      (global.get $__asyncify_data)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; SIZE:      (global $__asyncify_state (mut i32) (i32.const 0))
+
+  ;; SIZE:      (global $__asyncify_data (mut i32) (i32.const 0))
+
+  ;; SIZE:      (memory $0 1 2)
+
+  ;; SIZE:      (memory $asyncify_memory 3 3)
+
+  ;; SIZE:      (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+
+  ;; SIZE:      (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+
+  ;; SIZE:      (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+
+  ;; SIZE:      (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+
+  ;; SIZE:      (export "asyncify_get_state" (func $asyncify_get_state))
+
+  ;; SIZE:      (func $liveness1 (param $live0 i32) (param $dead0 i32)
+  ;; SIZE-NEXT:  (local $live1 i32)
+  ;; SIZE-NEXT:  (local $dead1 i32)
+  ;; SIZE-NEXT:  (local $4 i32)
+  ;; SIZE-NEXT:  (local $5 i32)
+  ;; SIZE-NEXT:  (local $6 i32)
+  ;; SIZE-NEXT:  (local $7 i32)
+  ;; SIZE-NEXT:  (local $8 i32)
+  ;; SIZE-NEXT:  (local $9 i32)
+  ;; SIZE-NEXT:  (local $10 i32)
+  ;; SIZE-NEXT:  (local $11 i32)
+  ;; SIZE-NEXT:  (if
+  ;; SIZE-NEXT:   (i32.eq
+  ;; SIZE-NEXT:    (global.get $__asyncify_state)
+  ;; SIZE-NEXT:    (i32.const 2)
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:   (block
+  ;; SIZE-NEXT:    (i32.store $asyncify_memory
+  ;; SIZE-NEXT:     (global.get $__asyncify_data)
+  ;; SIZE-NEXT:     (i32.add
+  ;; SIZE-NEXT:      (i32.load $asyncify_memory
+  ;; SIZE-NEXT:       (global.get $__asyncify_data)
+  ;; SIZE-NEXT:      )
+  ;; SIZE-NEXT:      (i32.const -8)
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:    (local.set $10
+  ;; SIZE-NEXT:     (i32.load $asyncify_memory
+  ;; SIZE-NEXT:      (global.get $__asyncify_data)
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:    (local.set $live0
+  ;; SIZE-NEXT:     (i32.load $asyncify_memory
+  ;; SIZE-NEXT:      (local.get $10)
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:    (local.set $live1
+  ;; SIZE-NEXT:     (i32.load $asyncify_memory offset=4
+  ;; SIZE-NEXT:      (local.get $10)
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:  )
+  ;; SIZE-NEXT:  (local.set $8
+  ;; SIZE-NEXT:   (block $__asyncify_unwind (result i32)
+  ;; SIZE-NEXT:    (block
+  ;; SIZE-NEXT:     (block
+  ;; SIZE-NEXT:      (if
+  ;; SIZE-NEXT:       (i32.eq
+  ;; SIZE-NEXT:        (global.get $__asyncify_state)
+  ;; SIZE-NEXT:        (i32.const 2)
+  ;; SIZE-NEXT:       )
+  ;; SIZE-NEXT:       (block
+  ;; SIZE-NEXT:        (i32.store $asyncify_memory
+  ;; SIZE-NEXT:         (global.get $__asyncify_data)
+  ;; SIZE-NEXT:         (i32.add
+  ;; SIZE-NEXT:          (i32.load $asyncify_memory
+  ;; SIZE-NEXT:           (global.get $__asyncify_data)
+  ;; SIZE-NEXT:          )
+  ;; SIZE-NEXT:          (i32.const -4)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:        (local.set $9
+  ;; SIZE-NEXT:         (i32.load $asyncify_memory
+  ;; SIZE-NEXT:          (i32.load $asyncify_memory
+  ;; SIZE-NEXT:           (global.get $__asyncify_data)
+  ;; SIZE-NEXT:          )
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:       )
+  ;; SIZE-NEXT:      )
+  ;; SIZE-NEXT:      (block
+  ;; SIZE-NEXT:       (if
+  ;; SIZE-NEXT:        (i32.eq
+  ;; SIZE-NEXT:         (global.get $__asyncify_state)
+  ;; SIZE-NEXT:         (i32.const 0)
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:        (block
+  ;; SIZE-NEXT:         (local.set $4
+  ;; SIZE-NEXT:          (local.get $dead0)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (drop
+  ;; SIZE-NEXT:          (local.get $4)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (local.set $5
+  ;; SIZE-NEXT:          (local.get $dead1)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (drop
+  ;; SIZE-NEXT:          (local.get $5)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:       )
+  ;; SIZE-NEXT:       (nop)
+  ;; SIZE-NEXT:       (nop)
+  ;; SIZE-NEXT:       (nop)
+  ;; SIZE-NEXT:       (if
+  ;; SIZE-NEXT:        (if (result i32)
+  ;; SIZE-NEXT:         (i32.eq
+  ;; SIZE-NEXT:          (global.get $__asyncify_state)
+  ;; SIZE-NEXT:          (i32.const 0)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (i32.const 1)
+  ;; SIZE-NEXT:         (i32.eq
+  ;; SIZE-NEXT:          (local.get $9)
+  ;; SIZE-NEXT:          (i32.const 0)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:        (block
+  ;; SIZE-NEXT:         (call $import)
+  ;; SIZE-NEXT:         (if
+  ;; SIZE-NEXT:          (i32.eq
+  ;; SIZE-NEXT:           (global.get $__asyncify_state)
+  ;; SIZE-NEXT:           (i32.const 1)
+  ;; SIZE-NEXT:          )
+  ;; SIZE-NEXT:          (br $__asyncify_unwind
+  ;; SIZE-NEXT:           (i32.const 0)
+  ;; SIZE-NEXT:          )
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:       )
+  ;; SIZE-NEXT:       (if
+  ;; SIZE-NEXT:        (i32.eq
+  ;; SIZE-NEXT:         (global.get $__asyncify_state)
+  ;; SIZE-NEXT:         (i32.const 0)
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:        (block
+  ;; SIZE-NEXT:         (local.set $6
+  ;; SIZE-NEXT:          (local.get $live0)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (drop
+  ;; SIZE-NEXT:          (local.get $6)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (local.set $7
+  ;; SIZE-NEXT:          (local.get $live1)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:         (drop
+  ;; SIZE-NEXT:          (local.get $7)
+  ;; SIZE-NEXT:         )
+  ;; SIZE-NEXT:        )
+  ;; SIZE-NEXT:       )
+  ;; SIZE-NEXT:       (nop)
+  ;; SIZE-NEXT:       (nop)
+  ;; SIZE-NEXT:       (nop)
+  ;; SIZE-NEXT:      )
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:     (return)
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:  )
+  ;; SIZE-NEXT:  (block
+  ;; SIZE-NEXT:   (i32.store $asyncify_memory
+  ;; SIZE-NEXT:    (i32.load $asyncify_memory
+  ;; SIZE-NEXT:     (global.get $__asyncify_data)
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:    (local.get $8)
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:   (i32.store $asyncify_memory
+  ;; SIZE-NEXT:    (global.get $__asyncify_data)
+  ;; SIZE-NEXT:    (i32.add
+  ;; SIZE-NEXT:     (i32.load $asyncify_memory
+  ;; SIZE-NEXT:      (global.get $__asyncify_data)
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:     (i32.const 4)
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:  )
+  ;; SIZE-NEXT:  (block
+  ;; SIZE-NEXT:   (local.set $11
+  ;; SIZE-NEXT:    (i32.load $asyncify_memory
+  ;; SIZE-NEXT:     (global.get $__asyncify_data)
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:   (i32.store $asyncify_memory
+  ;; SIZE-NEXT:    (local.get $11)
+  ;; SIZE-NEXT:    (local.get $live0)
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:   (i32.store $asyncify_memory offset=4
+  ;; SIZE-NEXT:    (local.get $11)
+  ;; SIZE-NEXT:    (local.get $live1)
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:   (i32.store $asyncify_memory
+  ;; SIZE-NEXT:    (global.get $__asyncify_data)
+  ;; SIZE-NEXT:    (i32.add
+  ;; SIZE-NEXT:     (i32.load $asyncify_memory
+  ;; SIZE-NEXT:      (global.get $__asyncify_data)
+  ;; SIZE-NEXT:     )
+  ;; SIZE-NEXT:     (i32.const 8)
+  ;; SIZE-NEXT:    )
+  ;; SIZE-NEXT:   )
+  ;; SIZE-NEXT:  )
+  ;; SIZE-NEXT: )
+  (func $liveness1 (param $live0 i32) (param $dead0 i32)
+    (local $live1 i32)
+    (local $dead1 i32)
+    (drop (local.get $dead0))
+    (drop (local.get $dead1))
+    (call $import)
+    (drop (local.get $live0))
+    (drop (local.get $live1))
+  )
+)
+;; CHECK:      (func $asyncify_start_unwind (param $0 i32)
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $__asyncify_data
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_stop_unwind
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_start_rewind (param $0 i32)
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 2)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $__asyncify_data
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_stop_rewind
+;; CHECK-NEXT:  (global.set $__asyncify_state
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.gt_u
+;; CHECK-NEXT:    (i32.load $asyncify_memory
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.load $asyncify_memory offset=4
+;; CHECK-NEXT:     (global.get $__asyncify_data)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (unreachable)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $asyncify_get_state (result i32)
+;; CHECK-NEXT:  (global.get $__asyncify_state)
+;; CHECK-NEXT: )
+
+;; SIZE:      (func $asyncify_start_unwind (param $0 i32)
+;; SIZE-NEXT:  (global.set $__asyncify_state
+;; SIZE-NEXT:   (i32.const 1)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT:  (global.set $__asyncify_data
+;; SIZE-NEXT:   (local.get $0)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT:  (if
+;; SIZE-NEXT:   (i32.gt_u
+;; SIZE-NEXT:    (i32.load $asyncify_memory
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:    (i32.load $asyncify_memory offset=4
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:   )
+;; SIZE-NEXT:   (unreachable)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT: )
+
+;; SIZE:      (func $asyncify_stop_unwind
+;; SIZE-NEXT:  (global.set $__asyncify_state
+;; SIZE-NEXT:   (i32.const 0)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT:  (if
+;; SIZE-NEXT:   (i32.gt_u
+;; SIZE-NEXT:    (i32.load $asyncify_memory
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:    (i32.load $asyncify_memory offset=4
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:   )
+;; SIZE-NEXT:   (unreachable)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT: )
+
+;; SIZE:      (func $asyncify_start_rewind (param $0 i32)
+;; SIZE-NEXT:  (global.set $__asyncify_state
+;; SIZE-NEXT:   (i32.const 2)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT:  (global.set $__asyncify_data
+;; SIZE-NEXT:   (local.get $0)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT:  (if
+;; SIZE-NEXT:   (i32.gt_u
+;; SIZE-NEXT:    (i32.load $asyncify_memory
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:    (i32.load $asyncify_memory offset=4
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:   )
+;; SIZE-NEXT:   (unreachable)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT: )
+
+;; SIZE:      (func $asyncify_stop_rewind
+;; SIZE-NEXT:  (global.set $__asyncify_state
+;; SIZE-NEXT:   (i32.const 0)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT:  (if
+;; SIZE-NEXT:   (i32.gt_u
+;; SIZE-NEXT:    (i32.load $asyncify_memory
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:    (i32.load $asyncify_memory offset=4
+;; SIZE-NEXT:     (global.get $__asyncify_data)
+;; SIZE-NEXT:    )
+;; SIZE-NEXT:   )
+;; SIZE-NEXT:   (unreachable)
+;; SIZE-NEXT:  )
+;; SIZE-NEXT: )
+
+;; SIZE:      (func $asyncify_get_state (result i32)
+;; SIZE-NEXT:  (global.get $__asyncify_state)
+;; SIZE-NEXT: )
diff --git a/test/lit/passes/avoid-reinterprets.wast b/test/lit/passes/avoid-reinterprets.wast
index 4a6bb92..046812d 100644
--- a/test/lit/passes/avoid-reinterprets.wast
+++ b/test/lit/passes/avoid-reinterprets.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --avoid-reinterprets -S -o - | filecheck %s
 
@@ -230,7 +230,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (f32.reinterpret_i32
-  ;; CHECK-NEXT:    (block $block (result i32)
+  ;; CHECK-NEXT:    (block $a-name-avoids-fallthrough (result i32)
   ;; CHECK-NEXT:     (nop)
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
@@ -246,9 +246,11 @@
     )
     (drop
      (f32.reinterpret_i32
-      (block (result i32)
+      (block $a-name-avoids-fallthrough (result i32)
        (nop) ;; this would be removed by other opts, but in general, we can't
              ;; just look at the fallthrough, as we can't just remove code here
+             ;; (note that we need a name on the block, or else we would look at
+             ;; the fallthrough)
        (local.get $x)
       )
      )
diff --git a/test/lit/passes/avoid-reinterprets64.wast b/test/lit/passes/avoid-reinterprets64.wast
index 80950b3..7ddda24 100644
--- a/test/lit/passes/avoid-reinterprets64.wast
+++ b/test/lit/passes/avoid-reinterprets64.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --avoid-reinterprets --enable-memory64 -S -o - | filecheck %s
 
@@ -230,7 +230,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (f32.reinterpret_i32
-  ;; CHECK-NEXT:    (block $block (result i32)
+  ;; CHECK-NEXT:    (block $a-name-avoids-fallthrough (result i32)
   ;; CHECK-NEXT:     (nop)
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
@@ -246,9 +246,11 @@
     )
     (drop
      (f32.reinterpret_i32
-      (block (result i32)
+      (block $a-name-avoids-fallthrough (result i32)
        (nop) ;; this would be removed by other opts, but in general, we can't
              ;; just look at the fallthrough, as we can't just remove code here
+             ;; (note that we need a name on the block, or else we would look at
+             ;; the fallthrough)
        (local.get $x)
       )
      )
diff --git a/test/lit/passes/catch-pop-fixup-eh.wast b/test/lit/passes/catch-pop-fixup-eh.wast
index 6c7d718..813bb68 100644
--- a/test/lit/passes/catch-pop-fixup-eh.wast
+++ b/test/lit/passes/catch-pop-fixup-eh.wast
@@ -28,7 +28,7 @@
   ;; CHECK-NEXT:     (pop i32)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (throw $e-i32
-  ;; CHECK-NEXT:     (block $block (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (local.get $0)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
@@ -60,11 +60,11 @@
   ;; CHECK-NEXT:     (pop i32)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (throw $e-i32
-  ;; CHECK-NEXT:     (block $block (result i32)
-  ;; CHECK-NEXT:      (block $block0 (result i32)
-  ;; CHECK-NEXT:       (block $block1 (result i32)
-  ;; CHECK-NEXT:        (block $block2 (result i32)
-  ;; CHECK-NEXT:         (block $block3 (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (block (result i32)
+  ;; CHECK-NEXT:       (block (result i32)
+  ;; CHECK-NEXT:        (block (result i32)
+  ;; CHECK-NEXT:         (block (result i32)
   ;; CHECK-NEXT:          (local.get $0)
   ;; CHECK-NEXT:         )
   ;; CHECK-NEXT:        )
@@ -177,12 +177,10 @@
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (catch $e-i32
-  ;; CHECK-NEXT:    (block $block
-  ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (pop i32)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (call $helper)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (pop i32)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (call $helper)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -331,7 +329,7 @@
   ;; CHECK-NEXT:     (pop i32 f32)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (throw $e-i32
-  ;; CHECK-NEXT:     (block $block (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (local.set $x
   ;; CHECK-NEXT:       (local.get $1)
   ;; CHECK-NEXT:      )
@@ -357,7 +355,7 @@
   )
 
   ;; CHECK:      (func $pop-non-defaultable-type-within-block
-  ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
+  ;; CHECK-NEXT:  (local $0 (ref $struct.A))
   ;; CHECK-NEXT:  (try $try
   ;; CHECK-NEXT:   (do
   ;; CHECK-NEXT:    (nop)
@@ -367,10 +365,8 @@
   ;; CHECK-NEXT:     (pop (ref $struct.A))
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (throw $e-struct.A
-  ;; CHECK-NEXT:     (block $block (result (ref $struct.A))
-  ;; CHECK-NEXT:      (ref.as_non_null
-  ;; CHECK-NEXT:       (local.get $0)
-  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     (block (result (ref $struct.A))
+  ;; CHECK-NEXT:      (local.get $0)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index 5caadf1..b1790bd 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -9,22 +9,23 @@
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
   ;; CHECK:      (func $impossible-get (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct)
+  ;; CHECK-NEXT:     (local.get $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $impossible-get
+  (func $impossible-get (local $struct (ref null $struct))
     (drop
       ;; This type is never created, so a get is impossible, and we will trap
       ;; anyhow. So we can turn this into an unreachable (plus a drop of the
       ;; reference).
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -33,26 +34,24 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field i64) data))
   (type $struct (struct i64))
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct
-  ;; CHECK-NEXT:    (rtt.canon $struct)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     ;; The only place this type is created is with a default value, and so we
     ;; can optimize the later get into a constant (plus a drop of the ref).
     ;;
@@ -61,13 +60,11 @@
     ;; references escaping and being stored etc. - it just thinks at the type
     ;; level.)
     (drop
-      (struct.new_default_with_rtt $struct
-        (rtt.canon $struct)
-      )
+      (struct.new_default $struct)
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -76,38 +73,36 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field f32) data))
   (type $struct (struct f32))
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (f32.const 42)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f32.const 42)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     ;; The only place this type is created is with a constant value, and so we
     ;; can optimize the later get into a constant (plus a drop of the ref).
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (f32.const 42)
-        (rtt.canon $struct)
       )
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -116,32 +111,30 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field f32) data))
   (type $struct (struct f32))
-  ;; CHECK:      (type $f32_=>_none (func_subtype (param f32) func))
+  ;; CHECK:      (type $f32_ref?|$struct|_=>_none (func_subtype (param f32 (ref null $struct)) func))
 
-  ;; CHECK:      (func $test (type $f32_=>_none) (param $f f32)
+  ;; CHECK:      (func $test (type $f32_ref?|$struct|_=>_none) (param $f f32) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (local.get $f)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test (param $f f32)
+  (func $test (param $f f32) (param $struct (ref null $struct))
     ;; The value given is not a constant, and so we cannot optimize.
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (local.get $f)
-        (rtt.canon $struct)
       )
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -150,42 +143,42 @@
 ;; Create in one function, get in another. The 10 should be forwarded to the
 ;; get.
 (module
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
-
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -194,44 +187,44 @@
 ;; As before, but with the order of functions reversed to check for any ordering
 ;; issues.
 (module
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
-
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
 
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
 
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
   )
@@ -242,43 +235,39 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field f32) data))
   (type $struct (struct f32))
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (f32.const 42)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (f32.const 1337)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (f32.const 42)
-        (rtt.canon $struct)
       )
     )
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (f32.const 1337)
-        (rtt.canon $struct)
       )
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -288,47 +277,47 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) data))
   (type $struct (struct (mut f32)))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (f32.const 42)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (f32.const 42)
-        (rtt.canon $struct)
       )
     )
   )
-  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK:      (func $set (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (f32.const 1337)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $set
+  (func $set (param $struct (ref null $struct))
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (f32.const 1337)
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -339,52 +328,52 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) data))
   (type $struct (struct (mut f32)))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (f32.const 42)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (f32.const 42)
-        (rtt.canon $struct)
       )
     )
   )
-  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK:      (func $set (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (f32.const 42)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $set
+  (func $set (param $struct (ref null $struct))
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (f32.const 42) ;; The last testcase had 1337 here.
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f32.const 42)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -396,34 +385,34 @@
   (type $struct (struct (mut f32)))
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
-  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+  ;; CHECK:      (type $i32_ref?|$struct|_=>_none (func_subtype (param i32 (ref null $struct)) func))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (block (result f32)
   ;; CHECK-NEXT:     (nop)
   ;; CHECK-NEXT:     (f32.const 42)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         ;; Fall though a 42 via a block.
         (block (result f32)
           (nop)
           (f32.const 42)
         )
-        (rtt.canon $struct)
       )
     )
   )
-  ;; CHECK:      (func $set (type $i32_=>_none) (param $x i32)
+  ;; CHECK:      (func $set (type $i32_ref?|$struct|_=>_none) (param $x i32) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (if (result f32)
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (unreachable)
@@ -431,9 +420,9 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $set (param $x i32)
+  (func $set (param $x i32) (param $struct (ref null $struct))
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       ;; Fall though a 42 via an if.
       (if (result f32)
         (local.get $x)
@@ -442,22 +431,22 @@
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f32.const 42)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -465,40 +454,38 @@
 
 ;; Test a function reference instead of a number.
 (module
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (type $struct (struct_subtype (field funcref) data))
   (type $struct (struct funcref))
   ;; CHECK:      (elem declare func $test)
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (ref.func $test)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref $none_=>_none))
+  ;; CHECK-NEXT:   (block (result (ref $ref?|$struct|_=>_none))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (ref.func $test)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (ref.func $test)
-        (rtt.canon $struct)
       )
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -513,11 +500,9 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (i32.const 10)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
@@ -525,6 +510,7 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
@@ -534,12 +520,12 @@
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $test
     (drop
-      (struct.new_with_rtt $struct
-        (i32.const 10)
+      (struct.new $struct
         (unreachable)
       )
     )
@@ -563,39 +549,39 @@
 
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
+  ;; CHECK:      (type $ref?|$substruct|_=>_none (func_subtype (param (ref null $substruct)) func))
+
   ;; CHECK:      (type $substruct (struct_subtype (field i32) $struct))
   (type $substruct (struct_subtype i32 $struct))
 
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$substruct|_=>_none) (param $substruct (ref null $substruct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $substruct)
+  ;; CHECK-NEXT:     (local.get $substruct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $substruct (ref null $substruct))
     (drop
       (struct.get $substruct 0
-        (ref.null $substruct)
+        (local.get $substruct)
       )
     )
   )
@@ -608,51 +594,51 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
   (type $struct (struct (mut i32)))
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (type $ref?|$substruct|_=>_none (func_subtype (param (ref null $substruct)) func))
 
   ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) $struct))
   (type $substruct (struct_subtype (mut i32) $struct))
 
-  ;; CHECK:      (func $create (type $none_=>_none)
+  ;; CHECK:      (func $create (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (i32.const 10)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $create
+  (func $create (param $struct (ref null $struct))
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (i32.const 10)
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$substruct|_=>_none) (param $substruct (ref null $substruct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $substruct)
+  ;; CHECK-NEXT:      (local.get $substruct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $substruct (ref null $substruct))
     (drop
       (struct.get $substruct 0
-        (ref.null $substruct)
+        (local.get $substruct)
       )
     )
   )
@@ -671,40 +657,40 @@
 
   (type $struct (struct i32))
 
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $substruct
+  ;; CHECK-NEXT:   (struct.new $substruct
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (f64.const 3.14159)
-  ;; CHECK-NEXT:    (rtt.canon $substruct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $substruct
+      (struct.new $substruct
         (i32.const 10)
         (f64.const 3.14159)
-        (rtt.canon $substruct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -713,59 +699,57 @@
 ;; Subtyping: Create both a subtype and a supertype, with identical constants
 ;;            for the shared field, and get the supertype.
 (module
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
-
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
   ;; CHECK:      (type $substruct (struct_subtype (field i32) (field f64) $struct))
   (type $substruct (struct_subtype i32 f64 $struct))
 
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $substruct
+  ;; CHECK-NEXT:   (struct.new $substruct
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (f64.const 3.14159)
-  ;; CHECK-NEXT:    (rtt.canon $substruct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
     (drop
-      (struct.new_with_rtt $substruct
+      (struct.new $substruct
         (i32.const 10)
         (f64.const 3.14159)
-        (rtt.canon $substruct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -782,47 +766,45 @@
   ;; CHECK:      (type $substruct (struct_subtype (field i32) (field f64) $struct))
   (type $substruct (struct_subtype i32 f64 $struct))
 
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $substruct
+  ;; CHECK-NEXT:   (struct.new $substruct
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:    (f64.const 3.14159)
-  ;; CHECK-NEXT:    (rtt.canon $substruct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
     (drop
-      (struct.new_with_rtt $substruct
+      (struct.new $substruct
         (i32.const 20)
         (f64.const 3.14159)
-        (rtt.canon $substruct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -833,8 +815,6 @@
 ;;            shared between the types, but we only create the substruct with
 ;;            one value, so we can optimize.
 (module
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
-
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
 
   ;; CHECK:      (type $substruct (struct_subtype (field i32) (field f64) $struct))
@@ -842,52 +822,52 @@
 
   (type $struct (struct i32))
 
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $ref?|$substruct|_=>_none (func_subtype (param (ref null $substruct)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $substruct
+  ;; CHECK-NEXT:   (struct.new $substruct
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:    (f64.const 3.14159)
-  ;; CHECK-NEXT:    (rtt.canon $substruct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
     (drop
-      (struct.new_with_rtt $substruct
+      (struct.new $substruct
         (i32.const 20)
         (f64.const 3.14159)
-        (rtt.canon $substruct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$substruct|_=>_none) (param $substruct (ref null $substruct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $substruct)
+  ;; CHECK-NEXT:      (local.get $substruct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $substruct (ref null $substruct))
     (drop
       (struct.get $substruct 0
-        (ref.null $substruct)
+        (local.get $substruct)
       )
     )
   )
@@ -901,57 +881,55 @@
   ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
   (type $substruct (struct_subtype (mut i32) f64 $struct))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
-  ;; CHECK:      (func $create (type $none_=>_none)
+  ;; CHECK:      (type $ref?|$substruct|_=>_none (func_subtype (param (ref null $substruct)) func))
+
+  ;; CHECK:      (func $create (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (i32.const 10)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $substruct
+  ;; CHECK-NEXT:   (struct.new $substruct
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:    (f64.const 3.14159)
-  ;; CHECK-NEXT:    (rtt.canon $substruct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $create
+  (func $create (param $struct (ref null $struct))
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.const 10)
-        (rtt.canon $struct)
       )
     )
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (i32.const 10)
     )
     (drop
-      (struct.new_with_rtt $substruct
+      (struct.new $substruct
         (i32.const 20)
         (f64.const 3.14159)
-        (rtt.canon $substruct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$substruct|_=>_none) (param $substruct (ref null $substruct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $substruct 0
-  ;; CHECK-NEXT:    (ref.null $substruct)
+  ;; CHECK-NEXT:    (local.get $substruct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $substruct (ref null $substruct))
     (drop
       (struct.get $substruct 0
-        (ref.null $substruct)
+        (local.get $substruct)
       )
     )
   )
@@ -973,32 +951,32 @@
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
+  ;; CHECK:      (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none (func_subtype (param (ref null $struct1) (ref null $struct2) (ref null $struct3)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct3
+  ;; CHECK-NEXT:   (struct.new $struct3
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:    (f64.const 3.14159)
-  ;; CHECK-NEXT:    (ref.null any)
-  ;; CHECK-NEXT:    (rtt.canon $struct3)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct3
+      (struct.new $struct3
         (i32.const 20)
         (f64.const 3.14159)
-        (ref.null any)
-        (rtt.canon $struct3)
+        (ref.null none)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct1)
+  ;; CHECK-NEXT:      (local.get $struct1)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
@@ -1008,7 +986,7 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct2)
+  ;; CHECK-NEXT:      (local.get $struct2)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
@@ -1018,7 +996,7 @@
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct2)
+  ;; CHECK-NEXT:      (local.get $struct2)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 3.14159)
@@ -1028,7 +1006,7 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
@@ -1038,56 +1016,56 @@
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 3.14159)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result anyref)
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
     ;; Get field 0 from the $struct1. This can be optimized to a constant
     ;; since we only ever created an instance of struct3 with a constant there.
     (drop
       (struct.get $struct1 0
-        (ref.null $struct1)
+        (local.get $struct1)
       )
     )
     ;; Get both fields of $struct2.
     (drop
       (struct.get $struct2 0
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct2 1
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     ;; Get all 3 fields of $struct3
     (drop
       (struct.get $struct3 0
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 1
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 2
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
   )
@@ -1110,38 +1088,35 @@
 
   ;; CHECK:      (type $anyref_=>_none (func_subtype (param anyref) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none (func_subtype (param (ref null $struct1) (ref null $struct2) (ref null $struct3)) func))
 
   ;; CHECK:      (func $create (type $anyref_=>_none) (param $any anyref)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct1
+  ;; CHECK-NEXT:   (struct.new $struct1
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (i32.const 20)
-  ;; CHECK-NEXT:    (rtt.canon $struct1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct3
+  ;; CHECK-NEXT:   (struct.new $struct3
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (i32.const 999)
   ;; CHECK-NEXT:    (f64.const 2.71828)
   ;; CHECK-NEXT:    (f64.const 9.9999999)
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:    (local.get $any)
-  ;; CHECK-NEXT:    (rtt.canon $struct3)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create (param $any anyref)
     (drop
-      (struct.new_with_rtt $struct1
+      (struct.new $struct1
         (i32.const 10)
         (i32.const 20)
-        (rtt.canon $struct1)
       )
     )
     (drop
-      (struct.new_with_rtt $struct3
+      (struct.new $struct3
         (i32.const 10)
         (i32.const 999) ;; use a different value here
         (f64.const 2.71828)
@@ -1149,16 +1124,15 @@
         (ref.null any)
         (local.get $any) ;; use a non-constant value here, which can never be
                          ;; optimized.
-        (rtt.canon $struct3)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct1)
+  ;; CHECK-NEXT:      (local.get $struct1)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
@@ -1166,14 +1140,14 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct1 1
-  ;; CHECK-NEXT:    (ref.null $struct1)
+  ;; CHECK-NEXT:    (local.get $struct1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct2)
+  ;; CHECK-NEXT:      (local.get $struct2)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
@@ -1183,7 +1157,7 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct2)
+  ;; CHECK-NEXT:      (local.get $struct2)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 999)
@@ -1193,7 +1167,7 @@
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct2)
+  ;; CHECK-NEXT:      (local.get $struct2)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 2.71828)
@@ -1203,7 +1177,7 @@
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct2)
+  ;; CHECK-NEXT:      (local.get $struct2)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 9.9999999)
@@ -1213,7 +1187,7 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
@@ -1223,7 +1197,7 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 999)
@@ -1233,7 +1207,7 @@
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 2.71828)
@@ -1243,88 +1217,88 @@
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 9.9999999)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result anyref)
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct3 5
-  ;; CHECK-NEXT:    (ref.null $struct3)
+  ;; CHECK-NEXT:    (local.get $struct3)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
     ;; Get all the fields of all the structs.
     (drop
       (struct.get $struct1 0
-        (ref.null $struct1)
+        (local.get $struct1)
       )
     )
     (drop
       (struct.get $struct1 1
-        (ref.null $struct1)
+        (local.get $struct1)
       )
     )
     (drop
       (struct.get $struct2 0
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct2 1
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct2 2
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct2 3
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct3 0
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 1
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 2
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 3
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 4
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
     (drop
       (struct.get $struct3 5
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
   )
@@ -1337,94 +1311,90 @@
   (type $struct1 (struct (mut i32)))
   ;; CHECK:      (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1))
   (type $struct2 (struct_subtype (mut i32) f64 $struct1))
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
-
   ;; CHECK:      (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2))
   (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2))
 
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none (func_subtype (param (ref null $struct1) (ref null $struct2) (ref null $struct3)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct1
+  ;; CHECK-NEXT:   (struct.new $struct1
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct2
+  ;; CHECK-NEXT:   (struct.new $struct2
   ;; CHECK-NEXT:    (i32.const 9999)
   ;; CHECK-NEXT:    (f64.const 0)
-  ;; CHECK-NEXT:    (rtt.canon $struct2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct3
+  ;; CHECK-NEXT:   (struct.new $struct3
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (f64.const 0)
-  ;; CHECK-NEXT:    (ref.null any)
-  ;; CHECK-NEXT:    (rtt.canon $struct3)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct1
+      (struct.new $struct1
         (i32.const 10)
-        (rtt.canon $struct1)
       )
     )
     (drop
-      (struct.new_with_rtt $struct2
+      (struct.new $struct2
         (i32.const 9999) ;; use a different value here
         (f64.const 0)
-        (rtt.canon $struct2)
       )
     )
     (drop
-      (struct.new_with_rtt $struct3
+      (struct.new $struct3
         (i32.const 10)
         (f64.const 0)
         (ref.null any)
-        (rtt.canon $struct3)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct1 0
-  ;; CHECK-NEXT:    (ref.null $struct1)
+  ;; CHECK-NEXT:    (local.get $struct1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct2 0
-  ;; CHECK-NEXT:    (ref.null $struct2)
+  ;; CHECK-NEXT:    (local.get $struct2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct3)
+  ;; CHECK-NEXT:      (local.get $struct3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
     ;; Get field 0 in all the types.
     (drop
       (struct.get $struct1 0
-        (ref.null $struct1)
+        (local.get $struct1)
       )
     )
     (drop
       (struct.get $struct2 0
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct3 0
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
   )
@@ -1443,95 +1413,91 @@
   ;; CHECK:      (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2))
   (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct2|_=>_none (func_subtype (param (ref null $struct2)) func))
 
-  ;; CHECK:      (func $create (type $none_=>_none)
+  ;; CHECK:      (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none (func_subtype (param (ref null $struct1) (ref null $struct2) (ref null $struct3)) func))
+
+  ;; CHECK:      (func $create (type $ref?|$struct2|_=>_none) (param $struct2 (ref null $struct2))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct1
+  ;; CHECK-NEXT:   (struct.new $struct1
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $struct1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct2
+  ;; CHECK-NEXT:   (struct.new $struct2
   ;; CHECK-NEXT:    (i32.const 9999)
   ;; CHECK-NEXT:    (f64.const 0)
-  ;; CHECK-NEXT:    (rtt.canon $struct2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct2 0
-  ;; CHECK-NEXT:   (ref.null $struct2)
+  ;; CHECK-NEXT:   (local.get $struct2)
   ;; CHECK-NEXT:   (i32.const 9999)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct3
+  ;; CHECK-NEXT:   (struct.new $struct3
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (f64.const 0)
-  ;; CHECK-NEXT:    (ref.null any)
-  ;; CHECK-NEXT:    (rtt.canon $struct3)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $create
+  (func $create (param $struct2 (ref null $struct2))
     (drop
-      (struct.new_with_rtt $struct1
+      (struct.new $struct1
         (i32.const 10)
-        (rtt.canon $struct1)
       )
     )
     (drop
-      (struct.new_with_rtt $struct2
+      (struct.new $struct2
         (i32.const 9999) ;; use a different value here
         (f64.const 0)
-        (rtt.canon $struct2)
       )
     )
     (struct.set $struct2 0
-      (ref.null $struct2)
+      (local.get $struct2)
       (i32.const 9999) ;; use a different value here
       (f64.const 0)
     )
     (drop
-      (struct.new_with_rtt $struct3
+      (struct.new $struct3
         (i32.const 10)
         (f64.const 0)
         (ref.null any)
-        (rtt.canon $struct3)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct1|_ref?|$struct2|_ref?|$struct3|_=>_none) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct1 0
-  ;; CHECK-NEXT:    (ref.null $struct1)
+  ;; CHECK-NEXT:    (local.get $struct1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct2 0
-  ;; CHECK-NEXT:    (ref.null $struct2)
+  ;; CHECK-NEXT:    (local.get $struct2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct3 0
-  ;; CHECK-NEXT:    (ref.null $struct3)
+  ;; CHECK-NEXT:    (local.get $struct3)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3))
     ;; Get field 0 in all the types.
     (drop
       (struct.get $struct1 0
-        (ref.null $struct1)
+        (local.get $struct1)
       )
     )
     (drop
       (struct.get $struct2 0
-        (ref.null $struct2)
+        (local.get $struct2)
       )
     )
     (drop
       (struct.get $struct3 0
-        (ref.null $struct3)
+        (local.get $struct3)
       )
     )
   )
@@ -1545,9 +1511,11 @@
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+  ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (i32.eqz
   ;; CHECK-NEXT:     (i32.const 10)
   ;; CHECK-NEXT:    )
@@ -1557,33 +1525,31 @@
   ;; CHECK-NEXT:     (f64.const 2.71828)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 30)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $struct
+      (struct.new $struct
         (i32.eqz (i32.const 10)) ;; not a constant (as far as this pass knows)
         (f64.const 3.14159)
         (i32.const 20)
         (f64.abs (f64.const 2.71828)) ;; not a constant
         (i32.const 30)
-        (rtt.canon $struct)
       )
     )
   )
-  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK:      (func $get (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (f64.const 3.14159)
@@ -1593,7 +1559,7 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
@@ -1601,14 +1567,14 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 3
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 30)
@@ -1618,43 +1584,43 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 30)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $get
+  (func $get (param $struct (ref null $struct))
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     (drop
       (struct.get $struct 1
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     (drop
       (struct.get $struct 2
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     (drop
       (struct.get $struct 3
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     (drop
       (struct.get $struct 4
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     ;; Also test for multiple gets of the same field.
     (drop
       (struct.get $struct 4
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -1683,17 +1649,15 @@
 
   ;; CHECK:      (func $create (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $C
+  ;; CHECK-NEXT:   (struct.new $C
   ;; CHECK-NEXT:    (i32.const 10)
-  ;; CHECK-NEXT:    (rtt.canon $C)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $create
     (drop
-      (struct.new_with_rtt $C
+      (struct.new $C
         (i32.const 10)
-        (rtt.canon $C)
       )
     )
   )
@@ -1731,20 +1695,18 @@
   ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
   (type $struct (struct (mut i32)))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_ref?|$struct|_=>_none (func_subtype (param (ref null $struct) (ref null $struct)) func))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_ref?|$struct|_=>_none) (param $struct (ref null $struct)) (param $other (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct
-  ;; CHECK-NEXT:    (rtt.canon $struct)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 0)
@@ -1754,30 +1716,28 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $other)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct)) (param $other (ref null $struct))
     (drop
-      (struct.new_default_with_rtt $struct
-        (rtt.canon $struct)
-      )
+      (struct.new_default $struct)
     )
     ;; This copy does not actually introduce any new possible values, and so it
     ;; remains true that the only possible value is the default.
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $other)
       )
     )
   )
@@ -1788,48 +1748,44 @@
 (module
   ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) (field (mut i32)) data))
   (type $struct (struct (mut f32) (mut i32)))
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_ref?|$other|_=>_none (func_subtype (param (ref null $struct) (ref null $other)) func))
 
   ;; CHECK:      (type $other (struct_subtype (field (mut f64)) (field (mut i32)) data))
   (type $other (struct (mut f64) (mut i32)))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_ref?|$other|_=>_none) (param $struct (ref null $struct)) (param $other (ref null $other))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct
-  ;; CHECK-NEXT:    (rtt.canon $struct)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 1
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $other)
+  ;; CHECK-NEXT:     (local.get $other)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 1
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct)) (param $other (ref null $other))
     (drop
-      (struct.new_default_with_rtt $struct
-        (rtt.canon $struct)
-      )
+      (struct.new_default $struct)
     )
     ;; As this is not a copy, we cannot optimize struct.1's get lower down.
     (struct.set $struct 1
-      (ref.null $struct)
+      (local.get $struct)
       (struct.get $other 1
-        (ref.null $other)
+        (local.get $other)
       )
     )
     (drop
       (struct.get $struct 1
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -1840,20 +1796,18 @@
   ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) (field (mut i32)) data))
   (type $struct (struct (mut i32) (mut i32)))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct
-  ;; CHECK-NEXT:    (rtt.canon $struct)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 0)
@@ -1861,26 +1815,24 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     (drop
-      (struct.new_default_with_rtt $struct
-        (rtt.canon $struct)
-      )
+      (struct.new_default $struct)
     )
     ;; As this is not a copy, we cannot optimize struct.0's get lower down.
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (struct.get $struct 1
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -1890,12 +1842,12 @@
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (global $global i32 (i32.const 42))
   (global $global i32 (i32.const 42))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (global.get $global)
@@ -1905,14 +1857,14 @@
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (global.get $global)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     ;; An immutable global is the only thing written to this field, so we can
     ;; propagate the value to the struct.get and replace it with a global.get.
     (drop
@@ -1922,7 +1874,7 @@
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -1932,12 +1884,12 @@
   ;; CHECK:      (type $struct (struct_subtype (field i32) data))
   (type $struct (struct i32))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (global $global (mut i32) (i32.const 42))
   (global $global (mut i32) (i32.const 42))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (global.get $global)
@@ -1945,11 +1897,11 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     ;; As above, but the global is *not* immutable, so we cannot optimize.
     (drop
       (struct.new $struct
@@ -1958,7 +1910,7 @@
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -1968,33 +1920,33 @@
   ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
   (type $struct (struct (mut i32)))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (global $global i32 (i32.const 42))
   (global $global i32 (i32.const 42))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (global.get $global)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (global.get $global)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (ref.as_non_null
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (local.get $struct)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (global.get $global)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     (drop
       (struct.new $struct
         (global.get $global)
@@ -2004,12 +1956,12 @@
     ;; so that is fine. Also, the struct's field is now mutable as well to allow
     ;; that, and that also does not prevent optimization.
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (global.get $global)
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -2019,30 +1971,30 @@
   ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
   (type $struct (struct (mut i32)))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (global $global i32 (i32.const 42))
   (global $global i32 (i32.const 42))
   ;; CHECK:      (global $global-2 i32 (i32.const 1337))
   (global $global-2 i32 (i32.const 1337))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (global.get $global)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (global.get $global-2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     (drop
       (struct.new $struct
         (global.get $global)
@@ -2050,12 +2002,12 @@
     )
     ;; As above, but set a different global, which prevents optimization.
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (global.get $global-2)
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -2065,30 +2017,30 @@
   ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
   (type $struct (struct (mut i32)))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (global $global i32 (i32.const 42))
   (global $global i32 (i32.const 42))
   ;; CHECK:      (global $global-2 i32 (i32.const 1337))
   (global $global-2 i32 (i32.const 1337))
 
-  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (global.get $global)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:   (i32.const 1337)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test
+  (func $test (param $struct (ref null $struct))
     (drop
       (struct.new $struct
         (global.get $global)
@@ -2097,12 +2049,12 @@
     ;; As above, but set a constant, which means we are mixing constants with
     ;; globals, which prevents the optimization.
     (struct.set $struct 0
-      (ref.null $struct)
+      (local.get $struct)
       (i32.const 1337)
     )
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -2112,20 +2064,21 @@
   ;; Test a global type other than i32. Arrays of structs are a realistic case
   ;; as they are used to implement itables.
 
+  ;; CHECK:      (type $itable (array_subtype (ref $vtable) data))
+
   ;; CHECK:      (type $vtable (struct_subtype (field funcref) data))
   (type $vtable (struct funcref))
 
-  ;; CHECK:      (type $itable (array_subtype (ref $vtable) data))
   (type $itable (array (ref $vtable)))
 
   ;; CHECK:      (type $object (struct_subtype (field $itable (ref $itable)) data))
   (type $object (struct (field $itable (ref $itable))))
 
-  ;; CHECK:      (type $none_=>_funcref (func_subtype (result funcref) func))
+  ;; CHECK:      (type $ref?|$object|_=>_funcref (func_subtype (param (ref null $object)) (result funcref) func))
 
   ;; CHECK:      (global $global (ref $itable) (array.init_static $itable
   ;; CHECK-NEXT:  (struct.new $vtable
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null nofunc)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.new $vtable
   ;; CHECK-NEXT:   (ref.func $test)
@@ -2140,7 +2093,7 @@
     )
   ))
 
-  ;; CHECK:      (func $test (type $none_=>_funcref) (result funcref)
+  ;; CHECK:      (func $test (type $ref?|$object|_=>_funcref) (param $object (ref null $object)) (result funcref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $object
   ;; CHECK-NEXT:    (global.get $global)
@@ -2151,7 +2104,7 @@
   ;; CHECK-NEXT:    (block (result (ref $itable))
   ;; CHECK-NEXT:     (drop
   ;; CHECK-NEXT:      (ref.as_non_null
-  ;; CHECK-NEXT:       (ref.null $object)
+  ;; CHECK-NEXT:       (local.get $object)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (global.get $global)
@@ -2160,7 +2113,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $test (result funcref)
+  (func $test (param $object (ref null $object)) (result funcref)
     (drop
       (struct.new $object
         (global.get $global)
@@ -2175,11 +2128,10 @@
     (struct.get $vtable 0
       (array.get $itable
         (struct.get $object $itable
-          (ref.null $object)
+          (local.get $object)
         )
         (i32.const 1)
       )
     )
   )
 )
-
diff --git a/test/lit/passes/coalesce-locals-gc.wast b/test/lit/passes/coalesce-locals-gc.wast
index 52243eb..5947aae 100644
--- a/test/lit/passes/coalesce-locals-gc.wast
+++ b/test/lit/passes/coalesce-locals-gc.wast
@@ -1,17 +1,22 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --coalesce-locals -all -S -o - \
+;; RUN: wasm-opt %s --remove-unused-names --coalesce-locals -all -S -o - \
 ;; RUN:   | filecheck %s
 
+;; --remove-unused-names is run to avoid adding names to blocks. Block names
+;; can prevent non-nullable local validation (we emit named blocks in the binary
+;; format, if we need them, but never emit unnamed ones), which affects some
+;; testcases.
+
 (module
  ;; CHECK:      (type $array (array (mut i8)))
  (type $array (array (mut i8)))
- ;; CHECK:      (global $global (ref null $array) (ref.null $array))
+ ;; CHECK:      (global $global (ref null $array) (ref.null none))
  (global $global (ref null $array) (ref.null $array))
 
- ;; CHECK:      (func $test-dead-get-non-nullable (param $0 dataref)
+ ;; CHECK:      (func $test-dead-get-non-nullable (param $0 (ref data))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (block (result dataref)
+ ;; CHECK-NEXT:   (block (result (ref data))
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -61,4 +66,103 @@
    (local.get $1)
   )
  )
+
+ ;; CHECK:      (func $nn-dead
+ ;; CHECK-NEXT:  (local $0 funcref)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.func $nn-dead)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (block $inner
+ ;; CHECK-NEXT:   (local.set $0
+ ;; CHECK-NEXT:    (ref.func $nn-dead)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (br_if $inner
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.as_non_null
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $nn-dead
+  (local $x (ref func))
+  (local.set $x
+   (ref.func $nn-dead) ;; this will be removed, as it is not needed.
+  )
+  (block $inner
+   (local.set $x
+    (ref.func $nn-dead) ;; this is not enough for validation of the get, so we
+                        ;; will end up making the local nullable.
+   )
+   ;; refer to $inner to keep the name alive (see the next testcase)
+   (br_if $inner
+    (i32.const 1)
+   )
+  )
+  (drop
+   (local.get $x)
+  )
+ )
+
+ ;; CHECK:      (func $nn-dead-nameless
+ ;; CHECK-NEXT:  (local $0 (ref func))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.func $nn-dead)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (block
+ ;; CHECK-NEXT:   (local.set $0
+ ;; CHECK-NEXT:    (ref.func $nn-dead)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $nn-dead-nameless
+  (local $x (ref func))
+  (local.set $x
+   (ref.func $nn-dead)
+  )
+  ;; As above, but now the block has no name. Nameless blocks do not interfere
+  ;; with validation, so we can keep the local non-nullable.
+  (block
+   (local.set $x
+    (ref.func $nn-dead)
+   )
+  )
+  (drop
+   (local.get $x)
+  )
+ )
+
+ ;; CHECK:      (func $unreachable-get-null
+ ;; CHECK-NEXT:  (local $0 anyref)
+ ;; CHECK-NEXT:  (local $1 i31ref)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (block (result anyref)
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i31.new
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $unreachable-get-null
+  ;; Check that we don't replace the local.get $null with a ref.null, which
+  ;; would have a more precise type.
+  (local $null-any anyref)
+  (local $null-i31 i31ref)
+  (unreachable)
+  (drop
+   (local.get $null-any)
+  )
+  (drop
+   (local.get $null-i31)
+  )
+ )
 )
diff --git a/test/lit/passes/coalesce-locals-learning.wast b/test/lit/passes/coalesce-locals-learning.wast
index d51a412..b8871dd 100644
--- a/test/lit/passes/coalesce-locals-learning.wast
+++ b/test/lit/passes/coalesce-locals-learning.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --coalesce-locals-learning -S -o - | filecheck %s
 
diff --git a/test/lit/passes/coalesce-locals.wast b/test/lit/passes/coalesce-locals.wast
index 2cd5948..96947c0 100644
--- a/test/lit/passes/coalesce-locals.wast
+++ b/test/lit/passes/coalesce-locals.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --coalesce-locals -S -o - | filecheck %s
 
@@ -1966,12 +1966,10 @@
   )
   ;; CHECK:      (func $nop-in-unreachable
   ;; CHECK-NEXT:  (local $0 i32)
-  ;; CHECK-NEXT:  (block $block
-  ;; CHECK-NEXT:   (unreachable)
-  ;; CHECK-NEXT:   (i32.store
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $nop-in-unreachable
diff --git a/test/lit/passes/code-folding_enable-threads.wast b/test/lit/passes/code-folding_enable-threads.wast
index afbe5ef..a14f090 100644
--- a/test/lit/passes/code-folding_enable-threads.wast
+++ b/test/lit/passes/code-folding_enable-threads.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --code-folding --enable-threads -S -o - | filecheck %s
 
@@ -114,20 +114,18 @@
  ;; CHECK-NEXT:  (block $label$A
  ;; CHECK-NEXT:   (if
  ;; CHECK-NEXT:    (unreachable)
- ;; CHECK-NEXT:    (block $block
- ;; CHECK-NEXT:     (block $block0
- ;; CHECK-NEXT:      (block $label$B
- ;; CHECK-NEXT:       (if
+ ;; CHECK-NEXT:    (block
+ ;; CHECK-NEXT:     (block $label$B
+ ;; CHECK-NEXT:      (if
+ ;; CHECK-NEXT:       (unreachable)
+ ;; CHECK-NEXT:       (br_table $label$A $label$B
  ;; CHECK-NEXT:        (unreachable)
- ;; CHECK-NEXT:        (br_table $label$A $label$B
- ;; CHECK-NEXT:         (unreachable)
- ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (return)
  ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (return)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block2
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (block $label$C
  ;; CHECK-NEXT:      (if
  ;; CHECK-NEXT:       (unreachable)
@@ -177,14 +175,10 @@
  ;; CHECK-NEXT:   (block $label$A
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (unreachable)
- ;; CHECK-NEXT:     (block $block
- ;; CHECK-NEXT:      (block $block4
- ;; CHECK-NEXT:       (br $folding-inner0)
- ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block6
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (br $folding-inner0)
  ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (br $folding-inner0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -319,17 +313,13 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (if
  ;; CHECK-NEXT:      (global.get $global$0)
- ;; CHECK-NEXT:      (block $block
- ;; CHECK-NEXT:       (br $folding-inner0)
- ;; CHECK-NEXT:      )
+ ;; CHECK-NEXT:      (br $folding-inner0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (global.get $global$0)
- ;; CHECK-NEXT:     (block $block1
- ;; CHECK-NEXT:      (br $folding-inner0)
- ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (br $folding-inner0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
diff --git a/test/lit/passes/code-pushing-eh.wast b/test/lit/passes/code-pushing-eh.wast
index c428d75..294c768 100644
--- a/test/lit/passes/code-pushing-eh.wast
+++ b/test/lit/passes/code-pushing-eh.wast
@@ -60,7 +60,8 @@
   (func $cannot-push-past-throw
     (local $x i32)
     (block $out
-      ;; This local.set cannot be pushed down, because there is 'throw' below
+      ;; This local.set cannot be pushed down, because there is 'throw' below.
+      ;; This pass only pushes past conditional control flow atm.
       (local.set $x (i32.const 1))
       (throw $e (i32.const 0))
       (drop (i32.const 1))
@@ -322,4 +323,72 @@
       (drop (local.get $x))
     )
   )
+
+  ;; CHECK:      (func $can-push-past-conditional-throw (param $param i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (if
+  ;; CHECK-NEXT:    (local.get $param)
+  ;; CHECK-NEXT:    (throw $e
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $can-push-past-conditional-throw (param $param i32)
+    (local $x i32)
+    (block $block
+      ;; We can push past an if containing a throw. The if is conditional
+      ;; control flow, which is what we look for in this optimization, and a
+      ;; throw is like a break - it will jump out of the current block - so we
+      ;; can push the set past it, as the set is only needed in this block.
+      (local.set $x (i32.const 1))
+      (if
+        (local.get $param)
+        (throw $e (i32.const 0))
+      )
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $cannot-push-past-conditional-throw-extra-use (param $param i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (if
+  ;; CHECK-NEXT:    (local.get $param)
+  ;; CHECK-NEXT:    (throw $e
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $cannot-push-past-conditional-throw-extra-use (param $param i32)
+    (local $x i32)
+    ;; As above, but now there is another local.get outside of the block. That
+    ;; means the local.set cannot be pushed to a place it might not execute.
+    (block $block
+      (local.set $x (i32.const 1))
+      (if
+        (local.get $param)
+        (throw $e (i32.const 0))
+      )
+      (drop (local.get $x))
+    )
+    (drop (local.get $x))
+  )
 )
diff --git a/test/lit/passes/code-pushing-gc.wast b/test/lit/passes/code-pushing-gc.wast
new file mode 100644
index 0000000..9af0fc0
--- /dev/null
+++ b/test/lit/passes/code-pushing-gc.wast
@@ -0,0 +1,80 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --code-pushing -all -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (func $br_on
+  ;; CHECK-NEXT:  (local $x funcref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block $out (result (ref func))
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (br_on_func $out
+  ;; CHECK-NEXT:      (ref.func $br_on)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (ref.func $br_on)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.func $br_on)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $br_on
+    (local $x (ref null func))
+    (drop
+      (block $out (result (ref func))
+        ;; We can push the local.set past the br_on.
+        (local.set $x (ref.func $br_on))
+        (drop
+          (br_on_func $out
+            (ref.func $br_on)
+          )
+        )
+        (drop
+          (local.get $x)
+        )
+        (ref.func $br_on)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $br_on_no
+  ;; CHECK-NEXT:  (local $x funcref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block $out (result (ref func))
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (ref.func $br_on_no)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (br_on_func $out
+  ;; CHECK-NEXT:      (ref.func $br_on_no)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.func $br_on_no)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $br_on_no
+    (local $x (ref null func))
+    ;; We can't push here since the local.get is outside of the loop.
+    (drop
+      (block $out (result (ref func))
+        (local.set $x (ref.func $br_on_no))
+        (drop
+          (br_on_func $out
+            (ref.func $br_on_no)
+          )
+        )
+        (ref.func $br_on_no)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+)
diff --git a/test/lit/passes/code-pushing_ignore-implicit-traps.wast b/test/lit/passes/code-pushing_ignore-implicit-traps.wast
index a81cf45..a64390c 100644
--- a/test/lit/passes/code-pushing_ignore-implicit-traps.wast
+++ b/test/lit/passes/code-pushing_ignore-implicit-traps.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --code-pushing --ignore-implicit-traps -S -o - | filecheck %s
 
diff --git a/test/lit/passes/code-pushing_into_if.wast b/test/lit/passes/code-pushing_into_if.wast
new file mode 100644
index 0000000..06e64da
--- /dev/null
+++ b/test/lit/passes/code-pushing_into_if.wast
@@ -0,0 +1,906 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --code-pushing --enable-reference-types -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param i32 funcref) (result i32)))
+  (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param i32 funcref) (result i32)))
+
+  ;; CHECK:      (func $if-nop (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-nop (param $p i32)
+    (local $x i32)
+    ;; The set local is not used in any if arm; do nothing.
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (nop)
+    )
+  )
+
+  ;; CHECK:      (func $if-nop-nop (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-nop-nop (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (nop)
+      (nop) ;; add a nop here compared to the last testcase (no output change)
+    )
+  )
+
+  ;; CHECK:      (func $if-use (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use (param $p i32)
+    (local $x i32)
+    ;; The set local is used in one arm and nowhere else; push it there.
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $if-use-nop (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use-nop (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+      (nop) ;; add a nop here compared to the last testcase (no output change)
+    )
+  )
+
+  ;; CHECK:      (func $if-else-use (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-else-use (param $p i32)
+    (local $x i32)
+    ;; The set local is used in one arm and nowhere else; push it there.
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (nop)
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $unpushed-interference (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $y
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unpushed-interference (param $p i32)
+    (local $x i32)
+    (local $y i32)
+    (local.set $x (i32.const 1))
+    ;; This set is not pushed (as it is not used in the if) and it will then
+    ;; prevent the previous set of $x from being pushed, since we can't push a
+    ;; set of $x past a get of it.
+    (local.set $y (local.get $x))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $if-use-use (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use-use (param $p i32)
+    (local $x i32)
+    ;; The set local is used in both arms, so we can't do anything.
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $if-use-after (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use-after (param $p i32)
+    (local $x i32)
+    ;; The use after the if prevents optimization.
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+    )
+    (drop (local.get $x))
+  )
+
+  ;; CHECK:      (func $if-use-after-nop (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use-after-nop (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+      (nop) ;; add a nop here compared to the last testcase (no output change)
+    )
+    (drop (local.get $x))
+  )
+
+  ;; CHECK:      (func $if-else-use-after (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (nop)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-else-use-after (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (nop)
+      (drop (local.get $x)) ;; now the use in the if is in the else arm
+    )
+    (drop (local.get $x))
+  )
+
+  ;; CHECK:      (func $if-use-after-unreachable (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (return)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use-after-unreachable (param $p i32)
+    (local $x i32)
+    ;; A use after the if is ok as the other arm is unreachable.
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+      (return)
+    )
+    (drop (local.get $x))
+  )
+
+  ;; CHECK:      (func $if-use-after-unreachable-else (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (return)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-use-after-unreachable-else (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (if
+      (local.get $p)
+      (return) ;; as above, but with arms flipped
+      (drop (local.get $x))
+    )
+    (drop (local.get $x))
+  )
+
+  ;; CHECK:      (func $optimize-many (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (local $z i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $z
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $y
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-many (param $p i32)
+    (local $x i32)
+    (local $y i32)
+    (local $z i32)
+    ;; Multiple things we can push, to various arms.
+    (local.set $x (i32.const 1))
+    (local.set $y (i32.const 2))
+    (local.set $z (i32.const 3))
+    (if
+      (local.get $p)
+      (block
+        (drop (local.get $x))
+        (drop (local.get $z))
+      )
+      (drop (local.get $y))
+    )
+  )
+
+  ;; CHECK:      (func $past-other (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $t i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (local.get $t)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $past-other (param $p i32)
+    (local $x i32)
+    (local $t i32)
+    ;; We can push this past the drop after it.
+    (local.set $x (local.get $t))
+    (drop (i32.const 2))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $past-other-no (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $t i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (local.get $t)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $t
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $past-other-no (param $p i32)
+    (local $x i32)
+    (local $t i32)
+    ;; We cannot push this due to the tee, which interferes with us.
+    (local.set $x (local.get $t))
+    (drop (local.tee $t (i32.const 2)))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $past-condition-no (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $t i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (local.get $t)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.tee $t
+  ;; CHECK-NEXT:    (local.get $p)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $past-condition-no (param $p i32)
+    (local $x i32)
+    (local $t i32)
+    ;; We cannot push this due to the tee in the if condition.
+    (local.set $x (local.get $t))
+    (if
+      (local.tee $t (local.get $p))
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $past-condition-no-2
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $t i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (local.get $t)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $past-condition-no-2
+    (local $x i32)
+    (local $t i32)
+    ;; We cannot push this due to the read of $x in the if condition.
+    (local.set $x (local.get $t))
+    (if
+      (local.get $x)
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $past-condition-no-3 (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $t i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (local.get $t)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.tee $x
+  ;; CHECK-NEXT:    (local.get $p)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $past-condition-no-3 (param $p i32)
+    (local $x i32)
+    (local $t i32)
+    ;; We cannot push this due to the write of $x in the if condition.
+    (local.set $x (local.get $t))
+    (if
+      (local.tee $x (local.get $p))
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $if-condition-return (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (return)
+  ;; CHECK-NEXT:    (local.get $p)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $x
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-condition-return (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (if
+      (block (result i32)
+        (return) ;; This return does not prevent us from optimizing; if it
+                 ;; happens then we don't need the local.set to execute
+                 ;; anyhow.
+        (local.get $p)
+      )
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $if-condition-break-used (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block $out
+  ;; CHECK-NEXT:   (if
+  ;; CHECK-NEXT:    (block (result i32)
+  ;; CHECK-NEXT:     (br $out)
+  ;; CHECK-NEXT:     (local.get $p)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (return)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-condition-break-used (param $p i32)
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    ;; As above, but the return is replaced with a break. The break goes to a
+    ;; location with a use of the local, which prevents optimization.
+    (block $out
+      (if
+        (block (result i32)
+          (br $out)
+          (local.get $p)
+        )
+        (drop (local.get $x))
+      )
+      (return)
+    )
+    (drop (local.get $x))
+  )
+
+  ;; CHECK:      (func $one-push-prevents-another (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $y
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $one-push-prevents-another (param $p i32)
+    (local $x i32)
+    (local $y i32)
+    ;; We will push $y into one arm, and as a result both arms will have a get
+    ;; of $x, which prevents pushing $x.
+    (local.set $x (i32.const 1))
+    (local.set $y (local.get $x))
+    (if
+      (local.get $p)
+      (drop (local.get $x))
+      (drop (local.get $y))
+    )
+  )
+
+  ;; CHECK:      (func $one-push-prevents-another-flipped (param $p i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $y
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $one-push-prevents-another-flipped (param $p i32)
+    (local $x i32)
+    (local $y i32)
+    ;; As above but with if arms flipped. The result should be similar, with
+    ;; only $y pushed.
+    (local.set $x (i32.const 1))
+    (local.set $y (local.get $x))
+    (if
+      (local.get $p)
+      (drop (local.get $y))
+      (drop (local.get $x))
+    )
+  )
+
+  ;; CHECK:      (func $sink-call (param $p i32) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $temp
+  ;; CHECK-NEXT:     (call $call.without.effects
+  ;; CHECK-NEXT:      (i32.const 1234)
+  ;; CHECK-NEXT:      (ref.func $sink-call)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (return
+  ;; CHECK-NEXT:     (local.get $temp)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $sink-call (param $p i32) (result i32)
+    (local $temp i32)
+
+    ;; This local has a call, but the call is an intrinsic indicating no
+    ;; effects, so it is safe to sink into the if.
+    (local.set $temp
+      (call $call.without.effects
+        (i32.const 1234)
+        (ref.func $sink-call)
+      )
+    )
+    (if
+      (local.get $p)
+      (return
+        (local.get $temp)
+      )
+    )
+    (i32.const 0)
+  )
+
+  ;; CHECK:      (func $no-sink-call (param $p i32) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (call $call.without.effects
+  ;; CHECK-NEXT:    (i32.const 1234)
+  ;; CHECK-NEXT:    (ref.func $no-sink-call)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (local.get $temp)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.get $temp)
+  ;; CHECK-NEXT: )
+  (func $no-sink-call (param $p i32) (result i32)
+    (local $temp i32)
+
+    ;; As above, but now after the if we have a get of the temp local, so we
+    ;; cannot sink. This + the previous testcase show we scan for such local
+    ;; uses in exactly the right places.
+    (local.set $temp
+      (call $call.without.effects
+        (i32.const 1234)
+        (ref.func $no-sink-call)
+      )
+    )
+    (if
+      (local.get $p)
+      (return
+        (local.get $temp)
+      )
+    )
+    (local.get $temp) ;; this line changed.
+  )
+
+  ;; CHECK:      (func $no-sink-call-2 (param $p i32) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (call $call.without.effects
+  ;; CHECK-NEXT:    (i32.const 1234)
+  ;; CHECK-NEXT:    (ref.func $no-sink-call-2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (local.get $temp)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (local.get $temp)
+  ;; CHECK-NEXT: )
+  (func $no-sink-call-2 (param $p i32) (result i32)
+    (local $temp i32)
+
+    ;; As above, but add a nop before the final value. We still should not
+    ;; optimize.
+    (local.set $temp
+      (call $call.without.effects
+        (i32.const 1234)
+        (ref.func $no-sink-call-2)
+      )
+    )
+    (if
+      (local.get $p)
+      (return
+        (local.get $temp)
+      )
+    )
+    (nop) ;; this line was added.
+    (local.get $temp)
+  )
+
+  ;; CHECK:      (func $no-sink-call-3 (param $p i32) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (call $call.without.effects
+  ;; CHECK-NEXT:    (i32.const 1234)
+  ;; CHECK-NEXT:    (ref.func $no-sink-call-3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (local.get $temp)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $no-sink-call-3 (param $p i32) (result i32)
+    (local $temp i32)
+
+    ;; As above, but add a nop after the get of the local after the if. We still
+    ;; should not optimize.
+    (local.set $temp
+      (call $call.without.effects
+        (i32.const 1234)
+        (ref.func $no-sink-call-3)
+      )
+    )
+    (if
+      (local.get $p)
+      (return
+        (local.get $temp)
+      )
+    )
+    (nop)
+    (drop
+      (local.get $temp) ;; this get is now dropped.
+    )
+    (nop) ;; this line was added;
+    (i32.const 0) ;; this line was added.
+  )
+
+  ;; CHECK:      (func $sink-call-3 (param $p i32) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (local.set $temp
+  ;; CHECK-NEXT:     (call $call.without.effects
+  ;; CHECK-NEXT:      (i32.const 1234)
+  ;; CHECK-NEXT:      (ref.func $no-sink-call-3)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (return
+  ;; CHECK-NEXT:     (local.get $temp)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $sink-call-3 (param $p i32) (result i32)
+    (local $temp i32)
+
+    ;; As above, but stop reading the relevant local after the if, keeping the
+    ;; number of other items unchanged. This verifies the presence of multiple
+    ;; items is not a problem and we can optimize.
+    (local.set $temp
+      (call $call.without.effects
+        (i32.const 1234)
+        (ref.func $no-sink-call-3)
+      )
+    )
+    (if
+      (local.get $p)
+      (return
+        (local.get $temp)
+      )
+    )
+    (nop)
+    (drop
+      (local.get $p) ;; this get now reads $p
+    )
+    (nop)
+    (i32.const 0)
+  )
+
+  ;; CHECK:      (func $no-sink-call-sub (param $p i32) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local $other i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (call $call.without.effects
+  ;; CHECK-NEXT:    (local.tee $other
+  ;; CHECK-NEXT:     (i32.const 1234)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.func $no-sink-call)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $p)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (local.get $temp)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $no-sink-call-sub (param $p i32) (result i32)
+    (local $temp i32)
+    (local $other i32)
+
+    ;; The call has no effects, but one of the arguments to it does, so we
+    ;; cannot optimize.
+    (local.set $temp
+      (call $call.without.effects
+        (local.tee $other ;; an effect
+          (i32.const 1234)
+        )
+        (ref.func $no-sink-call)
+      )
+    )
+    (if
+      (local.get $p)
+      (return
+        (local.get $temp)
+      )
+    )
+    (i32.const 0)
+  )
+)
diff --git a/test/lit/passes/code-pushing_tnh.wast b/test/lit/passes/code-pushing_tnh.wast
new file mode 100644
index 0000000..9993c68
--- /dev/null
+++ b/test/lit/passes/code-pushing_tnh.wast
@@ -0,0 +1,48 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
+
+;; RUN: foreach %s %t wasm-opt --code-pushing -tnh -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $i32_i32_=>_none (func (param i32 i32)))
+
+  ;; CHECK:      (func $div (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (if
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (return)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.set $temp
+  ;; CHECK-NEXT:    (i32.div_u
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (local.get $temp)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $div (param $x i32) (param $y i32)
+    (local $temp i32)
+    (block $block
+      ;; This division might trap (if x is 0). But with tnh we can assume that
+      ;; won't happen, and push it past the condition.
+      (local.set $temp
+        (i32.div_u
+          (i32.const 1)
+          (local.get $x)
+        )
+      )
+      (if
+        (local.get $y)
+        (return)
+      )
+      (drop
+        (local.get $temp)
+      )
+    )
+  )
+)
+
diff --git a/test/lit/passes/const-hoisting.wast b/test/lit/passes/const-hoisting.wast
index 276cb80..f55ed4c 100644
--- a/test/lit/passes/const-hoisting.wast
+++ b/test/lit/passes/const-hoisting.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --const-hoisting -S -o - | filecheck %s
 
diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast
index 25494d1..a6cfb7a 100644
--- a/test/lit/passes/dae-gc-refine-params.wast
+++ b/test/lit/passes/dae-gc-refine-params.wast
@@ -3,27 +3,28 @@
 ;; RUN: wasm-opt %s -all --dae --nominal -S -o - | filecheck %s --check-prefix NOMNL
 
 (module
+ ;; CHECK:      (type ${} (struct ))
+
  ;; CHECK:      (type ${i32} (struct (field i32)))
  ;; NOMNL:      (type ${} (struct_subtype  data))
 
  ;; NOMNL:      (type ${i32} (struct_subtype (field i32) ${}))
  (type ${i32} (struct_subtype (field i32) ${}))
 
- ;; CHECK:      (type ${} (struct ))
  (type ${} (struct))
 
+ ;; CHECK:      (type ${f64} (struct (field f64)))
+
  ;; CHECK:      (type ${i32_i64} (struct (field i32) (field i64)))
+ ;; NOMNL:      (type ${f64} (struct_subtype (field f64) ${}))
+
  ;; NOMNL:      (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
  (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
 
- ;; CHECK:      (type ${i32_f32} (struct (field i32) (field f32)))
-
- ;; CHECK:      (type ${f64} (struct (field f64)))
- ;; NOMNL:      (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
-
- ;; NOMNL:      (type ${f64} (struct_subtype (field f64) ${}))
  (type ${f64} (struct_subtype (field f64) ${}))
 
+ ;; CHECK:      (type ${i32_f32} (struct (field i32) (field f32)))
+ ;; NOMNL:      (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
  (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
 
  ;; CHECK:      (func $call-various-params-no
@@ -230,7 +231,7 @@
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $2
- ;; CHECK-NEXT:    (ref.null ${})
+ ;; CHECK-NEXT:    (struct.new_default ${})
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $2)
@@ -256,7 +257,7 @@
  ;; NOMNL-NEXT:    (local.get $y)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (local.set $2
- ;; NOMNL-NEXT:    (ref.null ${})
+ ;; NOMNL-NEXT:    (struct.new_default ${})
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (drop
  ;; NOMNL-NEXT:    (local.get $2)
@@ -277,7 +278,7 @@
   ;; force us to do a fixup: the param will get the new type, and a new local
   ;; will stay at the old type, and we will use that local throughout the
   ;; function.
-  (local.set $x (ref.null ${}))
+  (local.set $x (struct.new ${}))
   (drop
    (local.get $x)
   )
@@ -310,7 +311,7 @@
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (block $block (result (ref null ${i32}))
+ ;; CHECK-NEXT:   (block (result (ref null ${i32}))
  ;; CHECK-NEXT:    (local.tee $x
  ;; CHECK-NEXT:     (call $get_null_{i32_i64})
  ;; CHECK-NEXT:    )
@@ -322,7 +323,7 @@
  ;; NOMNL-NEXT:   (local.get $x)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (drop
- ;; NOMNL-NEXT:   (block $block (result (ref null ${i32}))
+ ;; NOMNL-NEXT:   (block (result (ref null ${i32}))
  ;; NOMNL-NEXT:    (local.tee $x
  ;; NOMNL-NEXT:     (call $get_null_{i32_i64})
  ;; NOMNL-NEXT:    )
@@ -345,32 +346,32 @@
  ;; CHECK:      (func $call-various-params-null
  ;; CHECK-NEXT:  (call $various-params-null
  ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (ref.null ${i32})
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (call $get_null_{i32})
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $various-params-null
  ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (ref.null ${i32})
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (ref.null ${i32})
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $call-various-params-null (type $none_=>_none)
  ;; NOMNL-NEXT:  (call $various-params-null
  ;; NOMNL-NEXT:   (ref.as_non_null
- ;; NOMNL-NEXT:    (ref.null ${i32})
+ ;; NOMNL-NEXT:    (ref.null none)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (call $get_null_{i32})
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $various-params-null
  ;; NOMNL-NEXT:   (ref.as_non_null
- ;; NOMNL-NEXT:    (ref.null ${i32})
+ ;; NOMNL-NEXT:    (ref.null none)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (ref.as_non_null
- ;; NOMNL-NEXT:    (ref.null ${i32})
+ ;; NOMNL-NEXT:    (ref.null none)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
@@ -388,7 +389,7 @@
  )
  ;; This function is called in ways that allow us to make the first parameter
  ;; non-nullable.
- ;; CHECK:      (func $various-params-null (param $x (ref ${i32})) (param $y (ref null ${i32}))
+ ;; CHECK:      (func $various-params-null (param $x (ref none)) (param $y (ref null ${i32}))
  ;; CHECK-NEXT:  (local $temp i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
@@ -400,7 +401,7 @@
  ;; CHECK-NEXT:   (local.get $temp)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $various-params-null (type $ref|${i32}|_ref?|${i32}|_=>_none) (param $x (ref ${i32})) (param $y (ref null ${i32}))
+ ;; NOMNL:      (func $various-params-null (type $ref|none|_ref?|${i32}|_=>_none) (param $x (ref none)) (param $y (ref null ${i32}))
  ;; NOMNL-NEXT:  (local $temp i32)
  ;; NOMNL-NEXT:  (drop
  ;; NOMNL-NEXT:   (local.get $x)
@@ -466,11 +467,11 @@
  )
 
  ;; CHECK:      (func $unused-and-refinable
- ;; CHECK-NEXT:  (local $0 (ref null data))
+ ;; CHECK-NEXT:  (local $0 dataref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $unused-and-refinable (type $none_=>_none)
- ;; NOMNL-NEXT:  (local $0 (ref null data))
+ ;; NOMNL-NEXT:  (local $0 dataref)
  ;; NOMNL-NEXT:  (nop)
  ;; NOMNL-NEXT: )
  (func $unused-and-refinable (param $0 dataref)
@@ -498,25 +499,21 @@
  )
 
  ;; CHECK:      (func $non-nullable-fixup (param $0 (ref ${}))
- ;; CHECK-NEXT:  (local $1 (ref null data))
+ ;; CHECK-NEXT:  (local $1 dataref)
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $1
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.get $1)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $non-nullable-fixup (type $ref|${}|_=>_none) (param $0 (ref ${}))
- ;; NOMNL-NEXT:  (local $1 (ref null data))
+ ;; NOMNL-NEXT:  (local $1 dataref)
  ;; NOMNL-NEXT:  (local.set $1
  ;; NOMNL-NEXT:   (local.get $0)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $1
- ;; NOMNL-NEXT:   (ref.as_non_null
- ;; NOMNL-NEXT:    (local.get $1)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (local.get $1)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $non-nullable-fixup (param $0 dataref)
@@ -546,7 +543,7 @@
 
  ;; CHECK:      (func $call-update-null
  ;; CHECK-NEXT:  (call $update-null
- ;; CHECK-NEXT:   (ref.null ${})
+ ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $update-null
  ;; CHECK-NEXT:   (struct.new_default ${})
@@ -554,7 +551,7 @@
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $call-update-null (type $none_=>_none)
  ;; NOMNL-NEXT:  (call $update-null
- ;; NOMNL-NEXT:   (ref.null ${})
+ ;; NOMNL-NEXT:   (ref.null none)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $update-null
  ;; NOMNL-NEXT:   (struct.new_default ${})
@@ -589,10 +586,10 @@
  )
 
  ;; CHECK:      (func $get_null_{i32} (result (ref null ${i32}))
- ;; CHECK-NEXT:  (ref.null ${i32})
+ ;; CHECK-NEXT:  (ref.null none)
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $get_null_{i32} (type $none_=>_ref?|${i32}|) (result (ref null ${i32}))
- ;; NOMNL-NEXT:  (ref.null ${i32})
+ ;; NOMNL-NEXT:  (ref.null none)
  ;; NOMNL-NEXT: )
  (func $get_null_{i32} (result (ref null ${i32}))
   ;; Helper function that returns a null value of ${i32}. We use this instead of
@@ -601,20 +598,20 @@
  )
 
  ;; CHECK:      (func $get_null_{i32_i64} (result (ref null ${i32_i64}))
- ;; CHECK-NEXT:  (ref.null ${i32_i64})
+ ;; CHECK-NEXT:  (ref.null none)
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $get_null_{i32_i64} (type $none_=>_ref?|${i32_i64}|) (result (ref null ${i32_i64}))
- ;; NOMNL-NEXT:  (ref.null ${i32_i64})
+ ;; NOMNL-NEXT:  (ref.null none)
  ;; NOMNL-NEXT: )
  (func $get_null_{i32_i64} (result (ref null ${i32_i64}))
   (ref.null ${i32_i64})
  )
 
  ;; CHECK:      (func $get_null_{i32_f32} (result (ref null ${i32_f32}))
- ;; CHECK-NEXT:  (ref.null ${i32_f32})
+ ;; CHECK-NEXT:  (ref.null none)
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $get_null_{i32_f32} (type $none_=>_ref?|${i32_f32}|) (result (ref null ${i32_f32}))
- ;; NOMNL-NEXT:  (ref.null ${i32_f32})
+ ;; NOMNL-NEXT:  (ref.null none)
  ;; NOMNL-NEXT: )
  (func $get_null_{i32_f32} (result (ref null ${i32_f32}))
   (ref.null ${i32_f32})
diff --git a/test/lit/passes/dae-gc-refine-return.wast b/test/lit/passes/dae-gc-refine-return.wast
index a349fbd..90084b9 100644
--- a/test/lit/passes/dae-gc-refine-return.wast
+++ b/test/lit/passes/dae-gc-refine-return.wast
@@ -9,18 +9,16 @@
 
  ;; CHECK:      (type ${i32} (struct (field i32)))
 
- ;; CHECK:      (type ${i32_i64} (struct (field i32) (field i64)))
-
  ;; CHECK:      (type ${i32_f32} (struct (field i32) (field f32)))
  ;; NOMNL:      (type ${} (struct_subtype  data))
 
  ;; NOMNL:      (type ${i32} (struct_subtype (field i32) ${}))
 
- ;; NOMNL:      (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
-
  ;; NOMNL:      (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
  (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
 
+ ;; CHECK:      (type ${i32_i64} (struct (field i32) (field i64)))
+ ;; NOMNL:      (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
  (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
 
  (type ${i32} (struct_subtype (field i32) ${}))
@@ -82,47 +80,47 @@
  )
 
  ;; Refine the return type based on the value flowing out.
- ;; CHECK:      (func $refine-return-flow (result funcref)
+ ;; CHECK:      (func $refine-return-flow (result i31ref)
  ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local $func funcref)
+ ;; CHECK-NEXT:  (local $i31 i31ref)
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (call $refine-return-flow)
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (local.get $func)
+ ;; CHECK-NEXT:  (local.get $i31)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-flow (type $none_=>_funcref) (result funcref)
+ ;; NOMNL:      (func $refine-return-flow (type $none_=>_i31ref) (result i31ref)
  ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local $func funcref)
+ ;; NOMNL-NEXT:  (local $i31 i31ref)
  ;; NOMNL-NEXT:  (local.set $temp
  ;; NOMNL-NEXT:   (call $refine-return-flow)
  ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (local.get $func)
+ ;; NOMNL-NEXT:  (local.get $i31)
  ;; NOMNL-NEXT: )
  (func $refine-return-flow (result anyref)
   (local $temp anyref)
-  (local $func funcref)
+  (local $i31 (ref null i31))
 
   (local.set $temp (call $refine-return-flow))
 
-  (local.get $func)
+  (local.get $i31)
  )
- ;; CHECK:      (func $call-refine-return-flow (result funcref)
+ ;; CHECK:      (func $call-refine-return-flow (result i31ref)
  ;; CHECK-NEXT:  (local $temp anyref)
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (call $call-refine-return-flow)
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (if (result funcref)
+ ;; CHECK-NEXT:  (if (result i31ref)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (call $refine-return-flow)
  ;; CHECK-NEXT:   (call $refine-return-flow)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $call-refine-return-flow (type $none_=>_funcref) (result funcref)
+ ;; NOMNL:      (func $call-refine-return-flow (type $none_=>_i31ref) (result i31ref)
  ;; NOMNL-NEXT:  (local $temp anyref)
  ;; NOMNL-NEXT:  (local.set $temp
  ;; NOMNL-NEXT:   (call $call-refine-return-flow)
  ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (if (result funcref)
+ ;; NOMNL-NEXT:  (if (result i31ref)
  ;; NOMNL-NEXT:   (i32.const 1)
  ;; NOMNL-NEXT:   (call $refine-return-flow)
  ;; NOMNL-NEXT:   (call $refine-return-flow)
@@ -143,104 +141,104 @@
  )
 
  ;; Refine the return type based on a return.
- ;; CHECK:      (func $refine-return-return (result funcref)
+ ;; CHECK:      (func $refine-return-return (result i31ref)
  ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local $func funcref)
+ ;; CHECK-NEXT:  (local $i31 i31ref)
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (call $refine-return-return)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (return
- ;; CHECK-NEXT:   (local.get $func)
+ ;; CHECK-NEXT:   (local.get $i31)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-return (type $none_=>_funcref) (result funcref)
+ ;; NOMNL:      (func $refine-return-return (type $none_=>_i31ref) (result i31ref)
  ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local $func funcref)
+ ;; NOMNL-NEXT:  (local $i31 i31ref)
  ;; NOMNL-NEXT:  (local.set $temp
  ;; NOMNL-NEXT:   (call $refine-return-return)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (return
- ;; NOMNL-NEXT:   (local.get $func)
+ ;; NOMNL-NEXT:   (local.get $i31)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $refine-return-return (result anyref)
   (local $temp anyref)
-  (local $func funcref)
+  (local $i31 (ref null i31))
 
   (local.set $temp (call $refine-return-return))
 
-  (return (local.get $func))
+  (return (local.get $i31))
  )
 
  ;; Refine the return type based on multiple values.
- ;; CHECK:      (func $refine-return-many (result funcref)
+ ;; CHECK:      (func $refine-return-many (result i31ref)
  ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local $func funcref)
+ ;; CHECK-NEXT:  (local $i31 i31ref)
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (call $refine-return-many)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (local.get $func)
+ ;; CHECK-NEXT:    (local.get $i31)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (local.get $func)
+ ;; CHECK-NEXT:    (local.get $i31)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (local.get $func)
+ ;; CHECK-NEXT:  (local.get $i31)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-many (type $none_=>_funcref) (result funcref)
+ ;; NOMNL:      (func $refine-return-many (type $none_=>_i31ref) (result i31ref)
  ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local $func funcref)
+ ;; NOMNL-NEXT:  (local $i31 i31ref)
  ;; NOMNL-NEXT:  (local.set $temp
  ;; NOMNL-NEXT:   (call $refine-return-many)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (if
  ;; NOMNL-NEXT:   (i32.const 1)
  ;; NOMNL-NEXT:   (return
- ;; NOMNL-NEXT:    (local.get $func)
+ ;; NOMNL-NEXT:    (local.get $i31)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (if
  ;; NOMNL-NEXT:   (i32.const 2)
  ;; NOMNL-NEXT:   (return
- ;; NOMNL-NEXT:    (local.get $func)
+ ;; NOMNL-NEXT:    (local.get $i31)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (local.get $func)
+ ;; NOMNL-NEXT:  (local.get $i31)
  ;; NOMNL-NEXT: )
  (func $refine-return-many (result anyref)
   (local $temp anyref)
-  (local $func funcref)
+  (local $i31 (ref null i31))
 
   (local.set $temp (call $refine-return-many))
 
   (if
    (i32.const 1)
-   (return (local.get $func))
+   (return (local.get $i31))
   )
   (if
    (i32.const 2)
-   (return (local.get $func))
+   (return (local.get $i31))
   )
-  (local.get $func)
+  (local.get $i31)
  )
 
- ;; CHECK:      (func $refine-return-many-blocked (result anyref)
+ ;; CHECK:      (func $refine-return-many-lub (result eqref)
  ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local $func funcref)
- ;; CHECK-NEXT:  (local $data (ref null data))
+ ;; CHECK-NEXT:  (local $i31 i31ref)
+ ;; CHECK-NEXT:  (local $data dataref)
  ;; CHECK-NEXT:  (local.set $temp
- ;; CHECK-NEXT:   (call $refine-return-many-blocked)
+ ;; CHECK-NEXT:   (call $refine-return-many-lub)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (local.get $func)
+ ;; CHECK-NEXT:    (local.get $i31)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
@@ -249,19 +247,19 @@
  ;; CHECK-NEXT:    (local.get $data)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (local.get $func)
+ ;; CHECK-NEXT:  (local.get $i31)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-many-blocked (type $none_=>_anyref) (result anyref)
+ ;; NOMNL:      (func $refine-return-many-lub (type $none_=>_eqref) (result eqref)
  ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local $func funcref)
- ;; NOMNL-NEXT:  (local $data (ref null data))
+ ;; NOMNL-NEXT:  (local $i31 i31ref)
+ ;; NOMNL-NEXT:  (local $data dataref)
  ;; NOMNL-NEXT:  (local.set $temp
- ;; NOMNL-NEXT:   (call $refine-return-many-blocked)
+ ;; NOMNL-NEXT:   (call $refine-return-many-lub)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (if
  ;; NOMNL-NEXT:   (i32.const 1)
  ;; NOMNL-NEXT:   (return
- ;; NOMNL-NEXT:    (local.get $func)
+ ;; NOMNL-NEXT:    (local.get $i31)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (if
@@ -270,168 +268,118 @@
  ;; NOMNL-NEXT:    (local.get $data)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (local.get $func)
+ ;; NOMNL-NEXT:  (local.get $i31)
  ;; NOMNL-NEXT: )
- (func $refine-return-many-blocked (result anyref)
+ (func $refine-return-many-lub (result anyref)
   (local $temp anyref)
-  (local $func funcref)
+  (local $i31 (ref null i31))
   (local $data (ref null data))
 
-  (local.set $temp (call $refine-return-many-blocked))
+  (local.set $temp (call $refine-return-many-lub))
 
   (if
    (i32.const 1)
-   (return (local.get $func))
+   (return (local.get $i31))
   )
   (if
    (i32.const 2)
-   ;; The refined return value is blocked by this return.
+   ;; The refined return type has to be a supertype of data.
    (return (local.get $data))
   )
-  (local.get $func)
+  (local.get $i31)
  )
 
- ;; CHECK:      (func $refine-return-many-blocked-2 (result anyref)
+ ;; CHECK:      (func $refine-return-many-lub-2 (result eqref)
  ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local $func funcref)
- ;; CHECK-NEXT:  (local $data (ref null data))
+ ;; CHECK-NEXT:  (local $i31 i31ref)
+ ;; CHECK-NEXT:  (local $data dataref)
  ;; CHECK-NEXT:  (local.set $temp
- ;; CHECK-NEXT:   (call $refine-return-many-blocked-2)
+ ;; CHECK-NEXT:   (call $refine-return-many-lub-2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (local.get $func)
+ ;; CHECK-NEXT:    (local.get $i31)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (local.get $func)
+ ;; CHECK-NEXT:    (local.get $i31)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $data)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-many-blocked-2 (type $none_=>_anyref) (result anyref)
+ ;; NOMNL:      (func $refine-return-many-lub-2 (type $none_=>_eqref) (result eqref)
  ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local $func funcref)
- ;; NOMNL-NEXT:  (local $data (ref null data))
+ ;; NOMNL-NEXT:  (local $i31 i31ref)
+ ;; NOMNL-NEXT:  (local $data dataref)
  ;; NOMNL-NEXT:  (local.set $temp
- ;; NOMNL-NEXT:   (call $refine-return-many-blocked-2)
+ ;; NOMNL-NEXT:   (call $refine-return-many-lub-2)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (if
  ;; NOMNL-NEXT:   (i32.const 1)
  ;; NOMNL-NEXT:   (return
- ;; NOMNL-NEXT:    (local.get $func)
+ ;; NOMNL-NEXT:    (local.get $i31)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (if
  ;; NOMNL-NEXT:   (i32.const 2)
  ;; NOMNL-NEXT:   (return
- ;; NOMNL-NEXT:    (local.get $func)
+ ;; NOMNL-NEXT:    (local.get $i31)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.get $data)
  ;; NOMNL-NEXT: )
- (func $refine-return-many-blocked-2 (result anyref)
+ (func $refine-return-many-lub-2 (result anyref)
   (local $temp anyref)
-  (local $func funcref)
+  (local $i31 (ref null i31))
   (local $data (ref null data))
 
-  (local.set $temp (call $refine-return-many-blocked-2))
+  (local.set $temp (call $refine-return-many-lub-2))
 
   (if
    (i32.const 1)
-   (return (local.get $func))
+   (return (local.get $i31))
   )
   (if
    (i32.const 2)
-   (return (local.get $func))
+   (return (local.get $i31))
   )
-  ;; The refined return value is blocked by this value.
+  ;; The refined return type has to be a supertype of data.
   (local.get $data)
  )
 
- ;; CHECK:      (func $refine-return-many-middle (result (ref null ${i32}))
- ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local ${i32_i64} (ref null ${i32_i64}))
- ;; CHECK-NEXT:  (local ${i32_f32} (ref null ${i32_f32}))
- ;; CHECK-NEXT:  (local.set $temp
- ;; CHECK-NEXT:   (call $refine-return-many-middle)
- ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (if
- ;; CHECK-NEXT:   (i32.const 1)
- ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (local.get ${i32_i64})
- ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (return
- ;; CHECK-NEXT:   (local.get ${i32_f32})
- ;; CHECK-NEXT:  )
- ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-many-middle (type $none_=>_ref?|${i32}|) (result (ref null ${i32}))
- ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local ${i32_i64} (ref null ${i32_i64}))
- ;; NOMNL-NEXT:  (local ${i32_f32} (ref null ${i32_f32}))
- ;; NOMNL-NEXT:  (local.set $temp
- ;; NOMNL-NEXT:   (call $refine-return-many-middle)
- ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (if
- ;; NOMNL-NEXT:   (i32.const 1)
- ;; NOMNL-NEXT:   (return
- ;; NOMNL-NEXT:    (local.get ${i32_i64})
- ;; NOMNL-NEXT:   )
- ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (return
- ;; NOMNL-NEXT:   (local.get ${i32_f32})
- ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT: )
- (func $refine-return-many-middle (result anyref)
-  (local $temp anyref)
-  (local ${i32_i64} (ref null ${i32_i64}))
-  (local ${i32_f32} (ref null ${i32_f32}))
-
-  (local.set $temp (call $refine-return-many-middle))
-
-  ;; Return two different struct types, with an LUB that is not equal to either
-  ;; of them.
-  (if
-   (i32.const 1)
-   (return (local.get ${i32_i64}))
-  )
-  (return (local.get ${i32_f32}))
- )
-
  ;; We can refine the return types of tuples.
- ;; CHECK:      (func $refine-return-tuple (result funcref i32)
+ ;; CHECK:      (func $refine-return-tuple (result i31ref i32)
  ;; CHECK-NEXT:  (local $temp anyref)
- ;; CHECK-NEXT:  (local $func funcref)
+ ;; CHECK-NEXT:  (local $i31 i31ref)
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (tuple.extract 0
  ;; CHECK-NEXT:    (call $refine-return-tuple)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (tuple.make
- ;; CHECK-NEXT:   (local.get $func)
+ ;; CHECK-NEXT:   (local.get $i31)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $refine-return-tuple (type $none_=>_funcref_i32) (result funcref i32)
+ ;; NOMNL:      (func $refine-return-tuple (type $none_=>_i31ref_i32) (result i31ref i32)
  ;; NOMNL-NEXT:  (local $temp anyref)
- ;; NOMNL-NEXT:  (local $func funcref)
+ ;; NOMNL-NEXT:  (local $i31 i31ref)
  ;; NOMNL-NEXT:  (local.set $temp
  ;; NOMNL-NEXT:   (tuple.extract 0
  ;; NOMNL-NEXT:    (call $refine-return-tuple)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (tuple.make
- ;; NOMNL-NEXT:   (local.get $func)
+ ;; NOMNL-NEXT:   (local.get $i31)
  ;; NOMNL-NEXT:   (i32.const 1)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $refine-return-tuple (result anyref i32)
   (local $temp anyref)
-  (local $func funcref)
+  (local $i31 (ref null i31))
 
   (local.set $temp
    (tuple.extract 0
@@ -440,7 +388,7 @@
   )
 
   (tuple.make
-   (local.get $func)
+   (local.get $i31)
    (i32.const 1)
   )
  )
@@ -638,20 +586,20 @@
  )
  ;; CHECK:      (func $tail-caller-call_ref-yes (result (ref ${}))
  ;; CHECK-NEXT:  (local $return_{} (ref null $return_{}))
- ;; CHECK-NEXT:  (return_call_ref
+ ;; CHECK-NEXT:  (return_call_ref $return_{}
  ;; CHECK-NEXT:   (local.get $return_{})
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $tail-caller-call_ref-yes (type $return_{}) (result (ref ${}))
  ;; NOMNL-NEXT:  (local $return_{} (ref null $return_{}))
- ;; NOMNL-NEXT:  (return_call_ref
+ ;; NOMNL-NEXT:  (return_call_ref $return_{}
  ;; NOMNL-NEXT:   (local.get $return_{})
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $tail-caller-call_ref-yes (result anyref)
   (local $return_{} (ref null $return_{}))
 
-  (return_call_ref (local.get $return_{}))
+  (return_call_ref $return_{} (local.get $return_{}))
  )
  ;; CHECK:      (func $tail-caller-call_ref-no (result anyref)
  ;; CHECK-NEXT:  (local $any anyref)
@@ -662,7 +610,7 @@
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (return_call_ref
+ ;; CHECK-NEXT:  (return_call_ref $return_{}
  ;; CHECK-NEXT:   (local.get $return_{})
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
@@ -675,7 +623,7 @@
  ;; NOMNL-NEXT:    (local.get $any)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (return_call_ref
+ ;; NOMNL-NEXT:  (return_call_ref $return_{}
  ;; NOMNL-NEXT:   (local.get $return_{})
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
@@ -686,18 +634,28 @@
   (if (i32.const 1)
    (return (local.get $any))
   )
-  (return_call_ref (local.get $return_{}))
+  (return_call_ref $return_{} (local.get $return_{}))
  )
- ;; CHECK:      (func $tail-caller-call_ref-unreachable
- ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK:      (func $tail-caller-call_ref-unreachable (result anyref)
+ ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
+ ;; CHECK-NEXT:   (drop
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $tail-caller-call_ref-unreachable (type $none_=>_none)
- ;; NOMNL-NEXT:  (unreachable)
+ ;; NOMNL:      (func $tail-caller-call_ref-unreachable (type $none_=>_anyref) (result anyref)
+ ;; NOMNL-NEXT:  (block ;; (replaces something unreachable we can't emit)
+ ;; NOMNL-NEXT:   (drop
+ ;; NOMNL-NEXT:    (unreachable)
+ ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (unreachable)
+ ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $tail-caller-call_ref-unreachable (result anyref)
   ;; An unreachable means there is no function signature to even look at. We
   ;; should not hit an assertion on such things.
-  (return_call_ref (unreachable))
+  (return_call_ref $return_{} (unreachable))
  )
  ;; CHECK:      (func $tail-call-caller-call_ref
  ;; CHECK-NEXT:  (drop
@@ -706,7 +664,9 @@
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $tail-caller-call_ref-no)
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (call $tail-caller-call_ref-unreachable)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $tail-caller-call_ref-unreachable)
+ ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $tail-call-caller-call_ref (type $none_=>_none)
  ;; NOMNL-NEXT:  (drop
@@ -715,7 +675,9 @@
  ;; NOMNL-NEXT:  (drop
  ;; NOMNL-NEXT:   (call $tail-caller-call_ref-no)
  ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT:  (call $tail-caller-call_ref-unreachable)
+ ;; NOMNL-NEXT:  (drop
+ ;; NOMNL-NEXT:   (call $tail-caller-call_ref-unreachable)
+ ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $tail-call-caller-call_ref
   (drop
@@ -738,7 +700,7 @@
  ;; CHECK-NEXT:     (struct.new_default ${i32_f32})
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (return
- ;; CHECK-NEXT:     (ref.null ${i32})
+ ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (return
@@ -755,7 +717,7 @@
  ;; NOMNL-NEXT:     (struct.new_default ${i32_f32})
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:    (return
- ;; NOMNL-NEXT:     (ref.null ${i32})
+ ;; NOMNL-NEXT:     (ref.null none)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (return
diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast
index 1d12150..d985035 100644
--- a/test/lit/passes/dae-gc.wast
+++ b/test/lit/passes/dae-gc.wast
@@ -21,13 +21,11 @@
   )
  )
  ;; CHECK:      (func $bar
- ;; CHECK-NEXT:  (local $0 (ref null i31))
+ ;; CHECK-NEXT:  (local $0 i31ref)
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.tee $0
- ;; CHECK-NEXT:     (i31.new
- ;; CHECK-NEXT:      (i32.const 2)
- ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:   (local.tee $0
+ ;; CHECK-NEXT:    (i31.new
+ ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -36,13 +34,11 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $bar (type $none_=>_none)
- ;; NOMNL-NEXT:  (local $0 (ref null i31))
+ ;; NOMNL-NEXT:  (local $0 i31ref)
  ;; NOMNL-NEXT:  (drop
- ;; NOMNL-NEXT:   (ref.as_non_null
- ;; NOMNL-NEXT:    (local.tee $0
- ;; NOMNL-NEXT:     (i31.new
- ;; NOMNL-NEXT:      (i32.const 2)
- ;; NOMNL-NEXT:     )
+ ;; NOMNL-NEXT:   (local.tee $0
+ ;; NOMNL-NEXT:    (i31.new
+ ;; NOMNL-NEXT:     (i32.const 2)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
@@ -69,30 +65,28 @@
    (unreachable)
   )
  )
- ;; a function that gets an rtt that is never used. we cannot create a local for
- ;; that parameter, as it is not defaultable, so do not remove the parameter.
- ;; CHECK:      (func $get-rtt (param $0 (rtt ${}))
+ ;; A function that gets a non-nullable reference that is never used. We can
+ ;; still create a non-nullable local for that parameter.
+ ;; CHECK:      (func $get-nonnull
+ ;; CHECK-NEXT:  (local $0 (ref ${}))
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $get-rtt (type $rtt_${}_=>_none) (param $0 (rtt ${}))
+ ;; NOMNL:      (func $get-nonnull (type $none_=>_none)
+ ;; NOMNL-NEXT:  (local $0 (ref ${}))
  ;; NOMNL-NEXT:  (nop)
  ;; NOMNL-NEXT: )
- (func $get-rtt (param $0 (rtt ${}))
+ (func $get-nonnull (param $0 (ref ${}))
   (nop)
  )
- ;; CHECK:      (func $send-rtt
- ;; CHECK-NEXT:  (call $get-rtt
- ;; CHECK-NEXT:   (rtt.canon ${})
- ;; CHECK-NEXT:  )
+ ;; CHECK:      (func $send-nonnull
+ ;; CHECK-NEXT:  (call $get-nonnull)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $send-rtt (type $none_=>_none)
- ;; NOMNL-NEXT:  (call $get-rtt
- ;; NOMNL-NEXT:   (rtt.canon ${})
- ;; NOMNL-NEXT:  )
+ ;; NOMNL:      (func $send-nonnull (type $none_=>_none)
+ ;; NOMNL-NEXT:  (call $get-nonnull)
  ;; NOMNL-NEXT: )
- (func $send-rtt
-  (call $get-rtt
-   (rtt.canon ${})
+ (func $send-nonnull
+  (call $get-nonnull
+   (struct.new ${})
   )
  )
 )
@@ -100,15 +94,13 @@
 ;; Test ref.func and ref.null optimization of constant parameter values.
 (module
  ;; CHECK:      (func $foo (param $0 (ref $none_=>_none))
- ;; CHECK-NEXT:  (local $1 (ref null $none_=>_none))
+ ;; CHECK-NEXT:  (local $1 (ref $none_=>_none))
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (ref.func $a)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
- ;; CHECK-NEXT:    (ref.as_non_null
- ;; CHECK-NEXT:     (local.get $1)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $0)
@@ -116,15 +108,13 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $foo (type $ref|none_->_none|_=>_none) (param $0 (ref $none_=>_none))
- ;; NOMNL-NEXT:  (local $1 (ref null $none_=>_none))
+ ;; NOMNL-NEXT:  (local $1 (ref $none_=>_none))
  ;; NOMNL-NEXT:  (local.set $1
  ;; NOMNL-NEXT:   (ref.func $a)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (block
  ;; NOMNL-NEXT:   (drop
- ;; NOMNL-NEXT:    (ref.as_non_null
- ;; NOMNL-NEXT:     (local.get $1)
- ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (local.get $1)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (drop
  ;; NOMNL-NEXT:    (local.get $0)
@@ -166,10 +156,10 @@
   )
  )
 
- ;; CHECK:      (func $bar (param $0 (ref null $none_=>_none))
+ ;; CHECK:      (func $bar (param $0 i31ref)
  ;; CHECK-NEXT:  (local $1 anyref)
  ;; CHECK-NEXT:  (local.set $1
- ;; CHECK-NEXT:   (ref.null func)
+ ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
@@ -180,10 +170,10 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $bar (type $ref?|none_->_none|_=>_none) (param $0 (ref null $none_=>_none))
+ ;; NOMNL:      (func $bar (type $i31ref_=>_none) (param $0 i31ref)
  ;; NOMNL-NEXT:  (local $1 anyref)
  ;; NOMNL-NEXT:  (local.set $1
- ;; NOMNL-NEXT:   (ref.null func)
+ ;; NOMNL-NEXT:   (ref.null none)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (block
  ;; NOMNL-NEXT:   (drop
@@ -202,31 +192,35 @@
 
  ;; CHECK:      (func $call-bar
  ;; CHECK-NEXT:  (call $bar
- ;; CHECK-NEXT:   (ref.null $none_=>_none)
+ ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $bar
- ;; CHECK-NEXT:   (ref.func $a)
+ ;; CHECK-NEXT:   (i31.new
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $call-bar (type $none_=>_none)
  ;; NOMNL-NEXT:  (call $bar
- ;; NOMNL-NEXT:   (ref.null $none_=>_none)
+ ;; NOMNL-NEXT:   (ref.null none)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $bar
- ;; NOMNL-NEXT:   (ref.func $a)
+ ;; NOMNL-NEXT:   (i31.new
+ ;; NOMNL-NEXT:    (i32.const 0)
+ ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $call-bar
   ;; Call with nulls. Mixing nulls is fine as they all have the same value, and
-  ;; we can optimize. However, mixing a null with a reference stops us in the
-  ;; second param.
+  ;; we can optimize (to the LUB of the nulls). However, mixing a null with a
+  ;; reference stops us in the second param.
   (call $bar
-   (ref.null func)
-   (ref.null func)
+   (ref.null i31)
+   (ref.null data)
   )
   (call $bar
    (ref.null any)
-   (ref.func $a)
+   (i31.new (i32.const 0))
   )
  )
 
diff --git a/test/lit/passes/dae-optimizing.wast b/test/lit/passes/dae-optimizing.wast
index 6b9e610..908543e 100644
--- a/test/lit/passes/dae-optimizing.wast
+++ b/test/lit/passes/dae-optimizing.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --dae-optimizing -S -o - | filecheck %s
 
diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast
index 41655d8..4ca0528 100644
--- a/test/lit/passes/dae_all-features.wast
+++ b/test/lit/passes/dae_all-features.wast
@@ -1,17 +1,25 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --dae --all-features -S -o - | filecheck %s
 
 (module
+
   ;; CHECK:      (type $none_=>_none (func))
 
   ;; CHECK:      (type $i32_=>_none (func (param i32)))
 
   ;; CHECK:      (type $none_=>_i32 (func (result i32)))
 
+  ;; CHECK:      (type $none_=>_f64 (func (result f64)))
+
   ;; CHECK:      (type $f64_=>_none (func (param f64)))
 
+  ;; CHECK:      (import "a" "b" (func $get-i32 (result i32)))
+  (import "a" "b" (func $get-i32 (result i32)))
+  ;; CHECK:      (import "a" "c" (func $get-f64 (result f64)))
+  (import "a" "c" (func $get-f64 (result f64)))
+
   ;; CHECK:      (table $0 2 2 funcref)
 
   ;; CHECK:      (elem (i32.const 0) $a9 $c8)
@@ -172,11 +180,11 @@
   )
   ;; CHECK:      (func $b6
   ;; CHECK-NEXT:  (call $a6
-  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (call $get-i32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $b6
-    (call $a6 (unreachable) (f64.const 3.14159))
+    (call $a6 (call $get-i32) (f64.const 3.14159))
   )
   ;; CHECK:      (func $a7 (param $0 f64)
   ;; CHECK-NEXT:  (local $1 i32)
@@ -198,11 +206,11 @@
   )
   ;; CHECK:      (func $b7
   ;; CHECK-NEXT:  (call $a7
-  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (call $get-f64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $b7
-    (call $a7 (i32.const 1) (unreachable))
+    (call $a7 (i32.const 1) (call $get-f64))
   )
   ;; CHECK:      (func $a8 (param $x i32)
   ;; CHECK-NEXT:  (nop)
@@ -479,10 +487,10 @@
 ;; CHECK-NEXT:  (ref.func $0)
 ;; CHECK-NEXT: )
 (module
- ;; CHECK:      (type $none_=>_none (func))
-
  ;; CHECK:      (type $i64 (func (param i64)))
  (type $i64 (func (param i64)))
+ ;; CHECK:      (type $none_=>_none (func))
+
  ;; CHECK:      (global $global$0 (ref $i64) (ref.func $0))
  (global $global$0 (ref $i64) (ref.func $0))
  ;; CHECK:      (export "even" (func $1))
@@ -496,13 +504,13 @@
   (unreachable)
  )
  ;; CHECK:      (func $1
- ;; CHECK-NEXT:  (call_ref
+ ;; CHECK-NEXT:  (call_ref $i64
  ;; CHECK-NEXT:   (i64.const 0)
  ;; CHECK-NEXT:   (global.get $global$0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $1
-  (call_ref
+  (call_ref $i64
    (i64.const 0)
    (global.get $global$0)
   )
@@ -523,7 +531,7 @@
  ;; CHECK:      (type $none_=>_none (func))
 
  ;; CHECK:      (func $0
- ;; CHECK-NEXT:  (local $0 (ref null i31))
+ ;; CHECK-NEXT:  (local $0 i31ref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $0 (param $x i31ref)
diff --git a/test/lit/passes/dae_tnh.wast b/test/lit/passes/dae_tnh.wast
index fc9270c..e816315 100644
--- a/test/lit/passes/dae_tnh.wast
+++ b/test/lit/passes/dae_tnh.wast
@@ -32,3 +32,89 @@
     )
   )
 )
+
+(module
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (func $caller
+  ;; CHECK-NEXT:  (call $target
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $caller
+    ;; Removing this parameter would require the type of the call to change from
+    ;; unreachable to none. We don't handle such complexity and ignore such
+    ;; cases.
+    (call $target
+      (unreachable)
+    )
+  )
+
+  ;; CHECK:      (func $target (param $0 i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $target (param i32)
+  )
+)
+
+;; As above, but use a return_call. We can optimize that, since return_calls
+;; have type unreachable anyhow, and the optimization would not change the type.
+(module
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (func $caller
+  ;; CHECK-NEXT:  (return_call $target)
+  ;; CHECK-NEXT: )
+  (func $caller
+    (return_call $target
+      (unreachable)
+    )
+  )
+
+  ;; CHECK:      (func $target
+  ;; CHECK-NEXT:  (local $0 i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $target (param i32)
+  )
+)
+
+(module
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (func $target (param $0 i32)
+  ;; CHECK-NEXT:  (local $1 f64)
+  ;; CHECK-NEXT:  (local.set $1
+  ;; CHECK-NEXT:   (f64.const 4.2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $target (param $used i32) (param $unused f64)
+    ;; One parameter is used, and one is not.
+    (drop
+      (local.get $used)
+    )
+  )
+
+  ;; CHECK:      (func $caller
+  ;; CHECK-NEXT:  (call $target
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $caller
+    ;; There is an unreachable parameter, and as in the cases above, we can't
+    ;; remove it as it would change the type. But it isn't the param we want to
+    ;; remove here, so we can optimize: we'll remove the other param, and leave
+    ;; the unreachable, and the type does not change.
+    (call $target
+      (unreachable)
+      (f64.const 4.2)
+    )
+  )
+)
diff --git a/test/lit/passes/dce_all-features.wast b/test/lit/passes/dce_all-features.wast
index 58bdbf5..03b90cc 100644
--- a/test/lit/passes/dce_all-features.wast
+++ b/test/lit/passes/dce_all-features.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --dce --all-features -S -o - | filecheck %s
 
@@ -911,7 +911,7 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
- ;; CHECK-NEXT:       (block $block (result i64)
+ ;; CHECK-NEXT:       (block (result i64)
  ;; CHECK-NEXT:        (local.set $2
  ;; CHECK-NEXT:         (local.get $var$0)
  ;; CHECK-NEXT:        )
@@ -956,7 +956,7 @@
  )
  ;; CHECK:      (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (param $var$0 i32) (result i32)
  ;; CHECK-NEXT:  (block $label$0
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
@@ -980,7 +980,7 @@
  )
  ;; CHECK:      (func $br-with-unreachable-value-should-not-give-a-block-a-value (param $var$0 i32) (result i32)
  ;; CHECK-NEXT:  (block $label$0 (result i32)
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (br_if $label$0
  ;; CHECK-NEXT:      (i32.const 8)
@@ -1150,7 +1150,7 @@
  ;; CHECK-NEXT:     (i64.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (if
- ;; CHECK-NEXT:     (block $block (result i32)
+ ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $replace-with-unreachable-affects-parent
  ;; CHECK-NEXT:       (f32.const 1)
  ;; CHECK-NEXT:       (i64.const -15917430362925035)
@@ -1375,7 +1375,7 @@
   )
   ;; CHECK:      (func $note-loss-of-non-control-flow-children
   ;; CHECK-NEXT:  (block $out
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
diff --git a/test/lit/passes/dce_vacuum_remove-unused-names.wast b/test/lit/passes/dce_vacuum_remove-unused-names.wast
index 13d0800..96a3d8f 100644
--- a/test/lit/passes/dce_vacuum_remove-unused-names.wast
+++ b/test/lit/passes/dce_vacuum_remove-unused-names.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --dce --vacuum --remove-unused-names -S -o - | filecheck %s
 
diff --git a/test/lit/passes/dealign.wast b/test/lit/passes/dealign.wast
index 7af8965..ba07da1 100644
--- a/test/lit/passes/dealign.wast
+++ b/test/lit/passes/dealign.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --dealign -S -o - | filecheck %s
 
diff --git a/test/lit/passes/dealign64.wast b/test/lit/passes/dealign64.wast
index 28f80da..64f0825 100644
--- a/test/lit/passes/dealign64.wast
+++ b/test/lit/passes/dealign64.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --dealign --enable-memory64 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/denan.wast b/test/lit/passes/denan.wast
index a808df4..f92570f 100644
--- a/test/lit/passes/denan.wast
+++ b/test/lit/passes/denan.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --denan -S -o - | filecheck %s
 
@@ -248,6 +248,7 @@
   )
 
 )
+
 ;; CHECK:      (func $deNan32_0 (param $0 f32) (result f32)
 ;; CHECK-NEXT:  (if (result f32)
 ;; CHECK-NEXT:   (f32.eq
diff --git a/test/lit/passes/directize_all-features.wast b/test/lit/passes/directize_all-features.wast
index 4c1400c..51e2dab 100644
--- a/test/lit/passes/directize_all-features.wast
+++ b/test/lit/passes/directize_all-features.wast
@@ -1,13 +1,16 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
-;; RUN: foreach %s %t wasm-opt --directize --all-features -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --directize                                                 --all-features -S -o - | filecheck %s --check-prefix=CHECK
+;; RUN: foreach %s %t wasm-opt --directize --pass-arg=directize-initial-contents-immutable --all-features -S -o - | filecheck %s --check-prefix=IMMUT
 
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
 
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo)
 
@@ -16,6 +19,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   ;; helper function
   (unreachable)
@@ -27,6 +35,12 @@
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -38,12 +52,17 @@
 
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (type $i32_=>_i32 (func (param i32) (result i32)))
 
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (type $i32_=>_i32 (func (param i32) (result i32)))
+
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  ;; CHECK:      (table $1 5 5 funcref)
+ ;; IMMUT:      (table $1 5 5 funcref)
  (table $1 5 5 funcref)
  (elem (table $0) (i32.const 1) func $dummy)
  (elem (table $1) (i32.const 1) func $f)
@@ -54,12 +73,22 @@
  ;; CHECK:      (func $dummy (param $0 i32) (result i32)
  ;; CHECK-NEXT:  (local.get $0)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem $0 (table $0) (i32.const 1) func $dummy)
+
+ ;; IMMUT:      (elem $1 (table $1) (i32.const 1) func $f)
+
+ ;; IMMUT:      (func $dummy (param $0 i32) (result i32)
+ ;; IMMUT-NEXT:  (local.get $0)
+ ;; IMMUT-NEXT: )
  (func $dummy (param i32) (result i32)
   (local.get 0)
  )
  ;; CHECK:      (func $f (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $f (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $f (param i32) (param i32)
   (unreachable)
  )
@@ -69,6 +98,12 @@
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $g (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $f
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $g (param $x i32) (param $y i32)
   (call_indirect $1 (type $ii)
    (local.get $x)
@@ -81,10 +116,13 @@
 ;; at table edges
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  ;; CHECK:      (table $1 5 5 funcref)
+ ;; IMMUT:      (table $1 5 5 funcref)
  (table $1 5 5 funcref)
  (elem (table $1) (i32.const 4) func $foo)
  ;; CHECK:      (elem (table $1) (i32.const 4) func $foo)
@@ -92,6 +130,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (table $1) (i32.const 4) func $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -101,6 +144,12 @@
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect $1 (type $ii)
    (local.get $x)
@@ -112,8 +161,10 @@
 
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 0) $foo)
  ;; CHECK:      (elem (i32.const 0) $foo)
@@ -121,6 +172,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 0) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -130,6 +186,12 @@
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -141,10 +203,13 @@
 
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  ;; CHECK:      (table $1 5 5 funcref)
+ ;; IMMUT:      (table $1 5 5 funcref)
  (table $1 5 5 funcref)
  (elem (i32.const 0) $foo $foo $foo $foo $foo)
  (elem (table $1) (i32.const 0) func $foo $foo $foo $foo $foo)
@@ -155,6 +220,13 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem $0 (table $0) (i32.const 0) func $foo $foo $foo $foo $foo)
+
+ ;; IMMUT:      (elem $1 (table $1) (i32.const 0) func $foo $foo $foo $foo $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -164,6 +236,12 @@
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect $1 (type $ii)
    (local.get $x)
@@ -173,11 +251,13 @@
  )
 )
 
-;; imported table
+;; imported table. only optimizable in the immutable case.
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (import "env" "table" (table $table 5 5 funcref))
+ ;; IMMUT:      (import "env" "table" (table $table 5 5 funcref))
  (import "env" "table" (table $table 5 5 funcref))
  (elem (i32.const 1) $foo)
  ;; CHECK:      (elem (i32.const 1) $foo)
@@ -185,6 +265,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -195,6 +280,12 @@
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -202,22 +293,55 @@
    (i32.const 1)
   )
  )
+
+ ;; CHECK:      (func $out-of-bounds (param $x i32) (param $y i32)
+ ;; CHECK-NEXT:  (call_indirect $table (type $ii)
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (i32.const 999)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $out-of-bounds (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call_indirect $table (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (i32.const 999)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
+ (func $out-of-bounds (param $x i32) (param $y i32)
+  ;; The index here, 999, is out of bounds. We can't optimize that even in the
+  ;; immutable case, since we only assume the initial contents in the table are
+  ;; immutable, and so something might be written to offset 999 later.
+  (call_indirect (type $ii)
+   (local.get $x)
+   (local.get $y)
+   (i32.const 999)
+  )
+ )
 )
 
-;; exported table
+;; exported table. only optimizable in the immutable case.
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  ;; CHECK:      (elem (i32.const 1) $foo)
 
  ;; CHECK:      (export "tab" (table $0))
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (export "tab" (table $0))
  (export "tab" (table $0))
  (elem (i32.const 1) $foo)
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -228,6 +352,12 @@
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -240,10 +370,14 @@
 ;; non-constant table offset
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (global $g (mut i32) (i32.const 1))
 
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (global $g (mut i32) (i32.const 1))
+
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (global $g (mut i32) (i32.const 1))
  (elem (global.get $g) $foo)
@@ -252,6 +386,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (global.get $g) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -262,6 +401,13 @@
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (i32.const 1)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -273,12 +419,17 @@
 
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (global $g (mut i32) (i32.const 1))
 
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (global $g (mut i32) (i32.const 1))
+
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  ;; CHECK:      (table $1 5 5 funcref)
+ ;; IMMUT:      (table $1 5 5 funcref)
  (table $1 5 5 funcref)
  (global $g (mut i32) (i32.const 1))
  (elem (table $1) (global.get $g) func $foo)
@@ -287,6 +438,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (table $1) (global.get $g) func $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -297,6 +453,13 @@
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (call_indirect $1 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (i32.const 1)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect $1 (type $ii)
    (local.get $x)
@@ -309,10 +472,14 @@
 ;; non-constant call index
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
 
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
+
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo)
  ;; CHECK:      (elem (i32.const 1) $foo)
@@ -320,6 +487,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -330,6 +502,13 @@
  ;; CHECK-NEXT:   (local.get $z)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (local.get $z)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32) (param $z i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -342,8 +521,10 @@
 ;; bad index
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo)
  ;; CHECK:      (elem (i32.const 1) $foo)
@@ -351,6 +532,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -365,6 +551,17 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (block
+ ;; IMMUT-NEXT:   (drop
+ ;; IMMUT-NEXT:    (local.get $x)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (drop
+ ;; IMMUT-NEXT:    (local.get $y)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -377,8 +574,10 @@
 ;; missing index
 (module
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo)
  ;; CHECK:      (elem (i32.const 1) $foo)
@@ -386,6 +585,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -400,6 +604,17 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (block
+ ;; IMMUT-NEXT:   (drop
+ ;; IMMUT-NEXT:    (local.get $x)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (drop
+ ;; IMMUT-NEXT:    (local.get $y)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -414,8 +629,12 @@
  ;; CHECK:      (type $i32_=>_none (func (param i32)))
 
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $i32_=>_none (func (param i32)))
+
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo)
  ;; CHECK:      (elem (i32.const 1) $foo)
@@ -423,6 +642,11 @@
  ;; CHECK:      (func $foo (param $0 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32)
   (unreachable)
  )
@@ -437,6 +661,17 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (block
+ ;; IMMUT-NEXT:   (drop
+ ;; IMMUT-NEXT:    (local.get $x)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (drop
+ ;; IMMUT-NEXT:    (local.get $y)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (call_indirect (type $ii)
    (local.get $x)
@@ -453,6 +688,11 @@
  ;; CHECK:      (func $foo (param $0 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (type $i32_=>_none (func (param i32)))
+
+ ;; IMMUT:      (func $foo (param $0 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32)
   (unreachable)
  )
@@ -464,17 +704,26 @@
  ;; CHECK:      (type $none_=>_none (func))
 
  ;; CHECK:      (table $0 8 8 funcref)
+ ;; IMMUT:      (type $none_=>_none (func))
+
+ ;; IMMUT:      (table $0 8 8 funcref)
  (table $0 8 8 funcref)
  ;; CHECK:      (func $0
- ;; CHECK-NEXT:  (block $block
- ;; CHECK-NEXT:   (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $0
+ ;; IMMUT-NEXT:  (nop)
+ ;; IMMUT-NEXT:  (block
+ ;; IMMUT-NEXT:   (block
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $0
   (block ;; the type of this block will change
    (nop)
@@ -487,8 +736,10 @@
 
 (module ;; indirect tail call
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo)
  ;; CHECK:      (elem (i32.const 1) $foo)
@@ -496,6 +747,11 @@
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo)
+
+ ;; IMMUT:      (func $foo (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo (param i32) (param i32)
   (unreachable)
  )
@@ -505,6 +761,12 @@
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (return_call $foo
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $bar (param $x i32) (param $y i32)
   (return_call_indirect (type $ii)
    (local.get $x)
@@ -518,8 +780,19 @@
  ;; CHECK:      (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
 
  ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
+
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
  (type $ii (func (param i32 i32)))
+
+ (type $none (func))
+
+ ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
  ;; CHECK:      (table $0 5 5 funcref)
+ ;; IMMUT:      (type $i32_=>_none (func (param i32)))
+
+ ;; IMMUT:      (table $0 5 5 funcref)
  (table $0 5 5 funcref)
  (elem (i32.const 1) $foo1 $foo2)
  ;; CHECK:      (elem (i32.const 1) $foo1 $foo2)
@@ -527,12 +800,20 @@
  ;; CHECK:      (func $foo1 (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 1) $foo1 $foo2)
+
+ ;; IMMUT:      (func $foo1 (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo1 (param i32) (param i32)
   (unreachable)
  )
  ;; CHECK:      (func $foo2 (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $foo2 (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo2 (param i32) (param i32)
   (unreachable)
  )
@@ -557,6 +838,27 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (local $3 i32)
+ ;; IMMUT-NEXT:  (local $4 i32)
+ ;; IMMUT-NEXT:  (local.set $3
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (local.set $4
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (if
+ ;; IMMUT-NEXT:   (local.get $z)
+ ;; IMMUT-NEXT:   (call $foo1
+ ;; IMMUT-NEXT:    (local.get $3)
+ ;; IMMUT-NEXT:    (local.get $4)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (call $foo2
+ ;; IMMUT-NEXT:    (local.get $3)
+ ;; IMMUT-NEXT:    (local.get $4)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select (param $x i32) (param $y i32) (param $z i32)
   ;; Test we can optimize a call_indirect whose index is a select between two
   ;; constants. We can emit an if and two direct calls for that.
@@ -581,6 +883,17 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-bad-1 (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (local.get $z)
+ ;; IMMUT-NEXT:    (i32.const 2)
+ ;; IMMUT-NEXT:    (local.get $z)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-bad-1 (param $x i32) (param $y i32) (param $z i32)
   ;; As above but one select arm is not constant.
   (call_indirect (type $ii)
@@ -604,6 +917,17 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-bad-2 (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (i32.const 2)
+ ;; IMMUT-NEXT:    (local.get $z)
+ ;; IMMUT-NEXT:    (local.get $z)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-bad-2 (param $x i32) (param $y i32) (param $z i32)
   ;; As above but the other select arm is not constant.
   (call_indirect (type $ii)
@@ -627,23 +951,31 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $z)
- ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (drop
- ;; CHECK-NEXT:      (local.get $3)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (drop
- ;; CHECK-NEXT:      (local.get $4)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (unreachable)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (call $foo2
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:    (local.get $4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-out-of-range (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (local $3 i32)
+ ;; IMMUT-NEXT:  (local $4 i32)
+ ;; IMMUT-NEXT:  (local.set $3
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (local.set $4
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (if
+ ;; IMMUT-NEXT:   (local.get $z)
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:   (call $foo2
+ ;; IMMUT-NEXT:    (local.get $3)
+ ;; IMMUT-NEXT:    (local.get $4)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-out-of-range (param $x i32) (param $y i32) (param $z i32)
   ;; Both are constants, but one is out of range for the table, and there is no
   ;; valid function to call there; emit an unreachable.
@@ -668,30 +1000,25 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $z)
- ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (drop
- ;; CHECK-NEXT:      (local.get $3)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (drop
- ;; CHECK-NEXT:      (local.get $4)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (unreachable)
- ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (drop
- ;; CHECK-NEXT:      (local.get $3)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (drop
- ;; CHECK-NEXT:      (local.get $4)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (unreachable)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-both-out-of-range (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (local $3 i32)
+ ;; IMMUT-NEXT:  (local $4 i32)
+ ;; IMMUT-NEXT:  (local.set $3
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (local.set $4
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (if
+ ;; IMMUT-NEXT:   (local.get $z)
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-both-out-of-range (param $x i32) (param $y i32) (param $z i32)
   ;; Both are constants, and both are out of range for the table.
   (call_indirect (type $ii)
@@ -715,6 +1042,17 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-unreachable-operand (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (unreachable)
+ ;; IMMUT-NEXT:    (i32.const 2)
+ ;; IMMUT-NEXT:    (local.get $z)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-unreachable-operand (param $x i32) (param $y i32) (param $z i32)
   ;; One operand is unreachable.
   (call_indirect (type $ii)
@@ -738,6 +1076,17 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-unreachable-condition (param $x i32) (param $y i32) (param $z i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (i32.const 1)
+ ;; IMMUT-NEXT:    (i32.const 2)
+ ;; IMMUT-NEXT:    (unreachable)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-unreachable-condition (param $x i32) (param $y i32) (param $z i32)
   ;; The condition is unreachable. We should not even create any vars here, and
   ;; just not do anything.
@@ -751,6 +1100,31 @@
    )
   )
  )
+ ;; CHECK:      (func $select-bad-type (param $z i32)
+ ;; CHECK-NEXT:  (if
+ ;; CHECK-NEXT:   (local.get $z)
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-bad-type (param $z i32)
+ ;; IMMUT-NEXT:  (if
+ ;; IMMUT-NEXT:   (local.get $z)
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
+ (func $select-bad-type (param $z i32)
+  ;; The type here is $none, which does not match the functions at indexes 1 and
+  ;; 2, so we know they will trap and can emit unreachables.
+  (call_indirect (type $none)
+   (select
+    (i32.const 1)
+    (i32.const 2)
+    (local.get $z)
+   )
+  )
+ )
 )
 
 (module
@@ -761,6 +1135,13 @@
  ;; CHECK:      (type $none_=>_none (func))
 
  ;; CHECK:      (table $0 15 15 funcref)
+ ;; IMMUT:      (type $F (func (param (ref func))))
+
+ ;; IMMUT:      (type $i32_=>_none (func (param i32)))
+
+ ;; IMMUT:      (type $none_=>_none (func))
+
+ ;; IMMUT:      (table $0 15 15 funcref)
  (table $0 15 15 funcref)
  (type $F (func (param (ref func))))
  (elem (i32.const 10) $foo-ref $foo-ref)
@@ -772,33 +1153,52 @@
  ;; CHECK:      (func $foo-ref (param $0 (ref func))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem (i32.const 10) $foo-ref $foo-ref)
+
+ ;; IMMUT:      (elem declare func $select-non-nullable)
+
+ ;; IMMUT:      (func $foo-ref (param $0 (ref func))
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
  (func $foo-ref (param (ref func))
   ;; helper function
   (unreachable)
  )
 
  ;; CHECK:      (func $select-non-nullable (param $x i32)
- ;; CHECK-NEXT:  (local $1 (ref null $i32_=>_none))
+ ;; CHECK-NEXT:  (local $1 (ref $i32_=>_none))
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (ref.func $select-non-nullable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (call $foo-ref
- ;; CHECK-NEXT:    (ref.as_non_null
- ;; CHECK-NEXT:     (local.get $1)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (call $foo-ref
- ;; CHECK-NEXT:    (ref.as_non_null
- ;; CHECK-NEXT:     (local.get $1)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-non-nullable (param $x i32)
+ ;; IMMUT-NEXT:  (local $1 (ref $i32_=>_none))
+ ;; IMMUT-NEXT:  (local.set $1
+ ;; IMMUT-NEXT:   (ref.func $select-non-nullable)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (if
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (call $foo-ref
+ ;; IMMUT-NEXT:    (local.get $1)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (call $foo-ref
+ ;; IMMUT-NEXT:    (local.get $1)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-non-nullable (param $x i32)
   ;; Test we can handle a non-nullable value when optimizing a select, during
-  ;; which we place values in locals.
+  ;; which we place values in locals. The local can remain non-nullable since it
+  ;; dominates the uses.
   (call_indirect (type $F)
    (ref.func $select-non-nullable)
    (select
@@ -819,6 +1219,16 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-non-nullable-unreachable-condition
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $F)
+ ;; IMMUT-NEXT:   (ref.func $select-non-nullable)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (i32.const 10)
+ ;; IMMUT-NEXT:    (i32.const 11)
+ ;; IMMUT-NEXT:    (unreachable)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-non-nullable-unreachable-condition
   ;; Test we ignore an unreachable condition and don't make any changes at all
   ;; to the code (in particular, we shouldn't add any vars).
@@ -832,6 +1242,42 @@
   )
  )
 
+ ;; CHECK:      (func $select-non-nullable-unreachable-arm
+ ;; CHECK-NEXT:  (call_indirect $0 (type $F)
+ ;; CHECK-NEXT:   (ref.func $select-non-nullable)
+ ;; CHECK-NEXT:   (select
+ ;; CHECK-NEXT:    (f32.const 3.141590118408203)
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-non-nullable-unreachable-arm
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $F)
+ ;; IMMUT-NEXT:   (ref.func $select-non-nullable)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (f32.const 3.141590118408203)
+ ;; IMMUT-NEXT:    (unreachable)
+ ;; IMMUT-NEXT:    (i32.const 1)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
+ (func $select-non-nullable-unreachable-arm
+  ;; Test we ignore an unreachable arm and don't make any changes at all
+  ;; to the code (in particular, we shouldn't add any vars).
+  (call_indirect (type $F)
+   (ref.func $select-non-nullable)
+   (select
+    ;; Note how the type here is not even an i32, so we must not even try to
+    ;; look into the select's values at all - the select is unreachable and we
+    ;; should give up on optimizing.
+    (f32.const 3.14159)
+    (unreachable)
+    (i32.const 1)
+   )
+  )
+ )
+
  ;; CHECK:      (func $select-non-nullable-unreachable-arg (param $x i32)
  ;; CHECK-NEXT:  (call_indirect $0 (type $F)
  ;; CHECK-NEXT:   (unreachable)
@@ -842,6 +1288,16 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $select-non-nullable-unreachable-arg (param $x i32)
+ ;; IMMUT-NEXT:  (call_indirect $0 (type $F)
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:   (select
+ ;; IMMUT-NEXT:    (i32.const 10)
+ ;; IMMUT-NEXT:    (i32.const 11)
+ ;; IMMUT-NEXT:    (local.get $x)
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $select-non-nullable-unreachable-arg (param $x i32)
   ;; Test we ignore an unreachable argument and don't make any changes at all
   ;; to the code (in particular, we shouldn't add any vars).
@@ -859,18 +1315,23 @@
 ;; A table.set prevents optimization.
 (module
  ;; CHECK:      (type $v (func))
+ ;; IMMUT:      (type $v (func))
  (type $v (func))
 
  ;; CHECK:      (table $has-set 5 5 funcref)
+ ;; IMMUT:      (table $has-set 5 5 funcref)
  (table $has-set 5 5 funcref)
 
  ;; CHECK:      (table $no-set 5 5 funcref)
+ ;; IMMUT:      (table $no-set 5 5 funcref)
  (table $no-set 5 5 funcref)
 
  ;; CHECK:      (elem $0 (table $has-set) (i32.const 1) func $foo)
+ ;; IMMUT:      (elem $0 (table $has-set) (i32.const 1) func $foo)
  (elem $0 (table $has-set) (i32.const 1) $foo)
 
  ;; CHECK:      (elem $1 (table $no-set) (i32.const 1) func $foo)
+ ;; IMMUT:      (elem $1 (table $no-set) (i32.const 1) func $foo)
  (elem $1 (table $no-set) (i32.const 1) $foo)
 
  ;; CHECK:      (func $foo
@@ -879,6 +1340,12 @@
  ;; CHECK-NEXT:   (ref.func $foo)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $foo
+ ;; IMMUT-NEXT:  (table.set $has-set
+ ;; IMMUT-NEXT:   (i32.const 1)
+ ;; IMMUT-NEXT:   (ref.func $foo)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
  (func $foo
   ;; Technically this set writes the same value as is already there, but the
   ;; analysis will give up on optimizing when it sees any set to a table.
@@ -894,8 +1361,13 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $foo)
  ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar
+ ;; IMMUT-NEXT:  (call $foo)
+ ;; IMMUT-NEXT:  (call $foo)
+ ;; IMMUT-NEXT: )
  (func $bar
-  ;; We can't optimize this one, but we can the one after it.
+  ;; We can't optimize this one, but we can the one after it. (But we can
+  ;; optimize both in the immutable case.)
   (call_indirect $has-set (type $v)
    (i32.const 1)
   )
@@ -904,3 +1376,141 @@
   )
  )
 )
+
+;; An imported table with a non-contiguous range in the initial contents.
+(module
+ ;; CHECK:      (type $ii (func (param i32 i32)))
+ ;; IMMUT:      (type $ii (func (param i32 i32)))
+ (type $ii (func (param i32 i32)))
+
+ ;; CHECK:      (import "env" "table" (table $table 5 5 funcref))
+ ;; IMMUT:      (import "env" "table" (table $table 5 5 funcref))
+ (import "env" "table" (table $table 5 5 funcref))
+ (elem (i32.const 1) $foo1)
+ (elem (i32.const 3) $foo2)
+
+ ;; CHECK:      (elem $0 (i32.const 1) $foo1)
+
+ ;; CHECK:      (elem $1 (i32.const 3) $foo2)
+
+ ;; CHECK:      (func $foo1 (param $0 i32) (param $1 i32)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ ;; IMMUT:      (elem $0 (i32.const 1) $foo1)
+
+ ;; IMMUT:      (elem $1 (i32.const 3) $foo2)
+
+ ;; IMMUT:      (func $foo1 (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
+ (func $foo1 (param i32) (param i32)
+  (unreachable)
+ )
+ ;; CHECK:      (func $foo2 (param $0 i32) (param $1 i32)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $foo2 (param $0 i32) (param $1 i32)
+ ;; IMMUT-NEXT:  (unreachable)
+ ;; IMMUT-NEXT: )
+ (func $foo2 (param i32) (param i32)
+  (unreachable)
+ )
+
+ ;; CHECK:      (func $bar (param $x i32) (param $y i32)
+ ;; CHECK-NEXT:  (call_indirect $table (type $ii)
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (i32.const 0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (call_indirect $table (type $ii)
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (call_indirect $table (type $ii)
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (call_indirect $table (type $ii)
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (i32.const 3)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (call_indirect $table (type $ii)
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (i32.const 4)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; IMMUT:      (func $bar (param $x i32) (param $y i32)
+ ;; IMMUT-NEXT:  (block
+ ;; IMMUT-NEXT:   (block
+ ;; IMMUT-NEXT:    (drop
+ ;; IMMUT-NEXT:     (local.get $x)
+ ;; IMMUT-NEXT:    )
+ ;; IMMUT-NEXT:    (drop
+ ;; IMMUT-NEXT:     (local.get $y)
+ ;; IMMUT-NEXT:    )
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (call $foo1
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (block
+ ;; IMMUT-NEXT:   (block
+ ;; IMMUT-NEXT:    (drop
+ ;; IMMUT-NEXT:     (local.get $x)
+ ;; IMMUT-NEXT:    )
+ ;; IMMUT-NEXT:    (drop
+ ;; IMMUT-NEXT:     (local.get $y)
+ ;; IMMUT-NEXT:    )
+ ;; IMMUT-NEXT:   )
+ ;; IMMUT-NEXT:   (unreachable)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (call $foo2
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT:  (call_indirect $table (type $ii)
+ ;; IMMUT-NEXT:   (local.get $x)
+ ;; IMMUT-NEXT:   (local.get $y)
+ ;; IMMUT-NEXT:   (i32.const 4)
+ ;; IMMUT-NEXT:  )
+ ;; IMMUT-NEXT: )
+ (func $bar (param $x i32) (param $y i32)
+  ;; When assuming the initial contents are immutable, we can optimize some
+  ;; of these cases. 0 and 2 are offsets that are known to contain a null, so
+  ;; they will trap, and 1 and 3 contain known contents we can do a direct call
+  ;; to. 4 is out of bounds so we cannot optimize there. (And in all of these,
+  ;; we cannot optimize anything in the non-immutable case, since the table is
+  ;; imported.)
+  (call_indirect (type $ii)
+   (local.get $x)
+   (local.get $y)
+   (i32.const 0)
+  )
+  (call_indirect (type $ii)
+   (local.get $x)
+   (local.get $y)
+   (i32.const 1)
+  )
+  (call_indirect (type $ii)
+   (local.get $x)
+   (local.get $y)
+   (i32.const 2)
+  )
+  (call_indirect (type $ii)
+   (local.get $x)
+   (local.get $y)
+   (i32.const 3)
+  )
+  (call_indirect (type $ii)
+   (local.get $x)
+   (local.get $y)
+   (i32.const 4)
+  )
+ )
+)
diff --git a/test/lit/passes/flatten.wast b/test/lit/passes/flatten.wast
index e07c8fa..0446f3c 100644
--- a/test/lit/passes/flatten.wast
+++ b/test/lit/passes/flatten.wast
@@ -5,14 +5,12 @@
  ;; CHECK:      (type $simplefunc (func))
  (type $simplefunc (func))
  ;; CHECK:      (func $0 (param $0 (ref $simplefunc)) (result (ref $simplefunc))
- ;; CHECK-NEXT:  (local $1 (ref null $simplefunc))
+ ;; CHECK-NEXT:  (local $1 (ref $simplefunc))
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (return
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.get $1)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $0 (param $0 (ref $simplefunc)) (result (ref $simplefunc))
diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast
index cafd78a..7daf009 100644
--- a/test/lit/passes/flatten_all-features.wast
+++ b/test/lit/passes/flatten_all-features.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --all-features -S -o - | filecheck %s
 
@@ -64,7 +64,7 @@
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
- ;; CHECK-NEXT:  (block $block
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $0
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
@@ -94,7 +94,7 @@
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
- ;; CHECK-NEXT:  (block $block
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $0
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
@@ -128,7 +128,7 @@
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
- ;; CHECK-NEXT:  (block $block
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $0
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
@@ -136,7 +136,7 @@
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (block $block0
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
@@ -173,7 +173,7 @@
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (local $7 i32)
- ;; CHECK-NEXT:  (block $block
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $x
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
@@ -187,7 +187,7 @@
  ;; CHECK-NEXT:  (local.set $3
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (block $block1
+ ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $x
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
@@ -237,8 +237,8 @@
  ;; CHECK-NEXT:  (local $7 i32)
  ;; CHECK-NEXT:  (local $8 i32)
  ;; CHECK-NEXT:  (local $9 i32)
- ;; CHECK-NEXT:  (block $block
- ;; CHECK-NEXT:   (block $block2
+ ;; CHECK-NEXT:  (block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
@@ -252,7 +252,7 @@
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block3
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
@@ -314,7 +314,7 @@
  ;; CHECK-NEXT:  (local $10 i32)
  ;; CHECK-NEXT:  (block $outer
  ;; CHECK-NEXT:   (block $inner
- ;; CHECK-NEXT:    (block $block
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $1
  ;; CHECK-NEXT:      (i32.const -1)
  ;; CHECK-NEXT:     )
@@ -332,7 +332,7 @@
  ;; CHECK-NEXT:    (local.set $4
  ;; CHECK-NEXT:     (local.get $3)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block4
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $2
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
@@ -2479,7 +2479,7 @@
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (unreachable)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 2)
   ;; CHECK-NEXT:    )
@@ -2999,7 +2999,7 @@
   ;; CHECK-NEXT:  (local $20 i32)
   ;; CHECK-NEXT:  (local $21 i32)
   ;; CHECK-NEXT:  (local $22 i32)
-  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (local.set $7
   ;; CHECK-NEXT:     (local.get $12)
@@ -3012,7 +3012,7 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (local.get $8)
-  ;; CHECK-NEXT:     (block $block44
+  ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (block $label$78
   ;; CHECK-NEXT:       (local.set $430
   ;; CHECK-NEXT:        (i32.const 0)
@@ -3025,7 +3025,7 @@
   ;; CHECK-NEXT:       (local.get $9)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (block $block45
+  ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (block $label$79
   ;; CHECK-NEXT:       (local.set $10
   ;; CHECK-NEXT:        (local.get $9)
@@ -3161,7 +3161,7 @@
   ;; CHECK-NEXT:  (local $3 i32)
   ;; CHECK-NEXT:  (local $4 i32)
   ;; CHECK-NEXT:  (local $5 i32)
-  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (block $label$0
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 16)
@@ -3206,7 +3206,7 @@
   ;; CHECK-NEXT:  (local $5 i32)
   ;; CHECK-NEXT:  (local $6 i32)
   ;; CHECK-NEXT:  (block $label$0
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (local.get $0)
   ;; CHECK-NEXT:    )
@@ -3291,7 +3291,7 @@
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (local.get $1)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (block $block
+  ;; CHECK-NEXT:    (block
   ;; CHECK-NEXT:     (local.set $3
   ;; CHECK-NEXT:      (i32.const -16)
   ;; CHECK-NEXT:     )
@@ -3416,55 +3416,59 @@
   ;; value type, we need the value to be set into two locals: one with the outer
   ;; block's type, and one with its value type.
   ;; CHECK:      (func $subtype (result anyref)
-  ;; CHECK-NEXT:  (local $0 anyref)
+  ;; CHECK-NEXT:  (local $0 eqref)
   ;; CHECK-NEXT:  (local $1 anyref)
-  ;; CHECK-NEXT:  (local $2 anyref)
-  ;; CHECK-NEXT:  (local $3 anyref)
-  ;; CHECK-NEXT:  (local $4 anyref)
-  ;; CHECK-NEXT:  (local $5 anyref)
-  ;; CHECK-NEXT:  (local $6 anyref)
+  ;; CHECK-NEXT:  (local $2 nullref)
+  ;; CHECK-NEXT:  (local $3 nullref)
+  ;; CHECK-NEXT:  (local $4 eqref)
+  ;; CHECK-NEXT:  (local $5 eqref)
+  ;; CHECK-NEXT:  (local $6 eqref)
+  ;; CHECK-NEXT:  (local $7 anyref)
   ;; CHECK-NEXT:  (block $label0
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (local.set $1
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $2
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (br_if $label0
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.set $2
-  ;; CHECK-NEXT:     (local.get $1)
+  ;; CHECK-NEXT:    (local.set $3
+  ;; CHECK-NEXT:     (local.get $2)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $0
-  ;; CHECK-NEXT:     (local.get $2)
+  ;; CHECK-NEXT:     (local.get $3)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.set $3
+  ;; CHECK-NEXT:    (local.set $4
   ;; CHECK-NEXT:     (local.get $0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.set $4
-  ;; CHECK-NEXT:     (local.get $3)
+  ;; CHECK-NEXT:    (local.set $5
+  ;; CHECK-NEXT:     (local.get $4)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (local.set $5
-  ;; CHECK-NEXT:    (local.get $4)
+  ;; CHECK-NEXT:   (local.set $6
+  ;; CHECK-NEXT:    (local.get $5)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.set $1
-  ;; CHECK-NEXT:    (local.get $5)
+  ;; CHECK-NEXT:    (local.get $6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $6
+  ;; CHECK-NEXT:  (local.set $7
   ;; CHECK-NEXT:   (local.get $1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (return
-  ;; CHECK-NEXT:   (local.get $6)
+  ;; CHECK-NEXT:   (local.get $7)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $subtype (result anyref)
-    (local $0 externref)
+    (local $0 eqref)
     (block $label0 (result anyref)
-      (block (result externref)
+      (block (result eqref)
         (local.tee $0
           (br_if $label0
-            (ref.null extern)
+            (ref.null eq)
             (i32.const 0)
           )
         )
@@ -3509,21 +3513,18 @@
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (module
- ;; CHECK:      (type $none_=>_none (func))
  (type $none_=>_none (func))
  ;; CHECK:      (type $none_=>_funcref (func (result funcref)))
 
  ;; CHECK:      (func $0 (result funcref)
- ;; CHECK-NEXT:  (local $0 (ref null $none_=>_none))
+ ;; CHECK-NEXT:  (local $0 (ref nofunc))
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (ref.null $none_=>_none)
+ ;; CHECK-NEXT:    (ref.null nofunc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (return
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.get $0)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $0 (result funcref)
diff --git a/test/lit/passes/flatten_dfo_O3_enable-threads.wast b/test/lit/passes/flatten_dfo_O3_enable-threads.wast
index 297f9c2..0ad494d 100644
--- a/test/lit/passes/flatten_dfo_O3_enable-threads.wast
+++ b/test/lit/passes/flatten_dfo_O3_enable-threads.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --dfo -O3 --enable-threads -S -o - | filecheck %s
 
diff --git a/test/lit/passes/flatten_i64-to-i32-lowering.wast b/test/lit/passes/flatten_i64-to-i32-lowering.wast
index e507ae8..20dd4e4 100644
--- a/test/lit/passes/flatten_i64-to-i32-lowering.wast
+++ b/test/lit/passes/flatten_i64-to-i32-lowering.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --i64-to-i32-lowering -S -o - | filecheck %s
 
diff --git a/test/lit/passes/flatten_rereloop.wast b/test/lit/passes/flatten_rereloop.wast
index 436fc93..090497e 100644
--- a/test/lit/passes/flatten_rereloop.wast
+++ b/test/lit/passes/flatten_rereloop.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --rereloop -S -o - | filecheck %s
 
@@ -446,7 +446,7 @@
  ;; CHECK-NEXT:  (local $2 f32)
  ;; CHECK-NEXT:  (local $3 f32)
  ;; CHECK-NEXT:  (local $4 i32)
- ;; CHECK-NEXT:  (block $block$7$break
+ ;; CHECK-NEXT:  (block $block$6$break
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (if
@@ -456,17 +456,15 @@
  ;; CHECK-NEXT:      (local.set $0
  ;; CHECK-NEXT:       (f32.const 9223372036854775808)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block
- ;; CHECK-NEXT:       (local.set $1
- ;; CHECK-NEXT:        (local.get $0)
- ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (local.set $2
- ;; CHECK-NEXT:        (local.get $1)
- ;; CHECK-NEXT:       )
+ ;; CHECK-NEXT:      (local.set $1
+ ;; CHECK-NEXT:       (local.get $0)
+ ;; CHECK-NEXT:      )
+ ;; CHECK-NEXT:      (local.set $2
+ ;; CHECK-NEXT:       (local.get $1)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (br $block$7$break)
+ ;; CHECK-NEXT:      (br $block$6$break)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
@@ -474,7 +472,7 @@
  ;; CHECK-NEXT:      (f32.const 65505)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (br $block$7$break)
+ ;; CHECK-NEXT:      (br $block$6$break)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
@@ -533,7 +531,7 @@
  ;; CHECK-NEXT:    (i32.const -23)
  ;; CHECK-NEXT:    (br $block$2$break)
  ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (block $block$6$break
+ ;; CHECK-NEXT:     (block $block$5$break
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (block $switch$4$leave
@@ -548,7 +546,7 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (block
- ;; CHECK-NEXT:        (br $block$6$break)
+ ;; CHECK-NEXT:        (br $block$5$break)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
@@ -601,10 +599,8 @@
  ;; CHECK:      (func $trivial2
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (block
- ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (call $trivial)
- ;; CHECK-NEXT:    (call $trivial)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (call $trivial)
+ ;; CHECK-NEXT:   (call $trivial)
  ;; CHECK-NEXT:   (return)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
@@ -800,7 +796,7 @@
  ;; CHECK-NEXT:  (local $8 i32)
  ;; CHECK-NEXT:  (local $9 i32)
  ;; CHECK-NEXT:  (local $10 i32)
- ;; CHECK-NEXT:  (block $block$21$break
+ ;; CHECK-NEXT:  (block $block$17$break
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $4
  ;; CHECK-NEXT:     (local.get $x)
@@ -820,11 +816,11 @@
  ;; CHECK-NEXT:      (br $shape$2$continue)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (br $block$21$break)
+ ;; CHECK-NEXT:    (br $block$17$break)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
- ;; CHECK-NEXT:   (block $block$24$break
+ ;; CHECK-NEXT:   (block $block$19$break
  ;; CHECK-NEXT:    (loop $shape$4$continue
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (call $trivial)
@@ -841,12 +837,12 @@
  ;; CHECK-NEXT:     (if
  ;; CHECK-NEXT:      (local.get $7)
  ;; CHECK-NEXT:      (br $shape$4$continue)
- ;; CHECK-NEXT:      (br $block$24$break)
+ ;; CHECK-NEXT:      (br $block$19$break)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (block $block$2$break
+ ;; CHECK-NEXT:    (block $block$26$break
  ;; CHECK-NEXT:     (loop $shape$6$continue
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (call $trivial)
@@ -863,7 +859,7 @@
  ;; CHECK-NEXT:      (if
  ;; CHECK-NEXT:       (local.get $9)
  ;; CHECK-NEXT:       (br $shape$6$continue)
- ;; CHECK-NEXT:       (br $block$2$break)
+ ;; CHECK-NEXT:       (br $block$26$break)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
@@ -971,7 +967,7 @@
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (local $7 i32)
- ;; CHECK-NEXT:  (block $block$4$break
+ ;; CHECK-NEXT:  (block $block$2$break
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $3
  ;; CHECK-NEXT:     (local.get $x)
@@ -985,14 +981,14 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (if
  ;; CHECK-NEXT:    (local.get $4)
- ;; CHECK-NEXT:    (br $block$4$break)
+ ;; CHECK-NEXT:    (br $block$2$break)
  ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (block $block$2$break
+ ;; CHECK-NEXT:     (block $block$30$break
  ;; CHECK-NEXT:      (call $unreachable
  ;; CHECK-NEXT:       (i32.const 5)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (block
- ;; CHECK-NEXT:       (br $block$2$break)
+ ;; CHECK-NEXT:       (br $block$30$break)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
@@ -1106,11 +1102,11 @@
  )
  ;; CHECK:      (func $empty-blocks (param $x i32)
  ;; CHECK-NEXT:  (local $1 i32)
- ;; CHECK-NEXT:  (block $block$2$break
+ ;; CHECK-NEXT:  (block $block$3$break
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (br $block$2$break)
+ ;; CHECK-NEXT:    (br $block$3$break)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
@@ -1145,7 +1141,7 @@
  ;; CHECK-NEXT:  (local $14 i32)
  ;; CHECK-NEXT:  (local $15 i32)
  ;; CHECK-NEXT:  (local $16 i32)
- ;; CHECK-NEXT:  (block $block$4$break
+ ;; CHECK-NEXT:  (block $block$3$break
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (call $before-and-after
@@ -1175,19 +1171,19 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (if
  ;; CHECK-NEXT:    (local.get $7)
- ;; CHECK-NEXT:    (br $block$4$break)
+ ;; CHECK-NEXT:    (br $block$3$break)
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (call $before-and-after
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (br $block$4$break)
+ ;; CHECK-NEXT:      (br $block$3$break)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
- ;; CHECK-NEXT:   (block $block$8$break
+ ;; CHECK-NEXT:   (block $block$7$break
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (call $before-and-after
@@ -1202,11 +1198,11 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (br $block$8$break)
+ ;; CHECK-NEXT:     (br $block$7$break)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (block $block$10$break
+ ;; CHECK-NEXT:    (block $block$8$break
  ;; CHECK-NEXT:     (loop $shape$4$continue
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (call $before-and-after
@@ -1225,29 +1221,27 @@
  ;; CHECK-NEXT:      (if
  ;; CHECK-NEXT:       (local.get $9)
  ;; CHECK-NEXT:       (br $shape$4$continue)
- ;; CHECK-NEXT:       (br $block$10$break)
+ ;; CHECK-NEXT:       (br $block$8$break)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (block $block$11$break
+ ;; CHECK-NEXT:     (block $block$10$break
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (call $before-and-after
  ;; CHECK-NEXT:        (i32.const 10)
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block
- ;; CHECK-NEXT:        (call $before-and-after
- ;; CHECK-NEXT:         (i32.const 11)
- ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (local.set $10
- ;; CHECK-NEXT:         (local.get $x)
- ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (local.set $3
- ;; CHECK-NEXT:         (local.get $10)
- ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (local.set $11
- ;; CHECK-NEXT:         (local.get $3)
- ;; CHECK-NEXT:        )
+ ;; CHECK-NEXT:       (call $before-and-after
+ ;; CHECK-NEXT:        (i32.const 11)
+ ;; CHECK-NEXT:       )
+ ;; CHECK-NEXT:       (local.set $10
+ ;; CHECK-NEXT:        (local.get $x)
+ ;; CHECK-NEXT:       )
+ ;; CHECK-NEXT:       (local.set $3
+ ;; CHECK-NEXT:        (local.get $10)
+ ;; CHECK-NEXT:       )
+ ;; CHECK-NEXT:       (local.set $11
+ ;; CHECK-NEXT:        (local.get $3)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (if
@@ -1257,14 +1251,14 @@
  ;; CHECK-NEXT:         (i32.const 12)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (block
- ;; CHECK-NEXT:         (br $block$11$break)
+ ;; CHECK-NEXT:         (br $block$10$break)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (br $block$11$break)
+ ;; CHECK-NEXT:       (br $block$10$break)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (block $block$15$break
+ ;; CHECK-NEXT:      (block $block$13$break
  ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (call $before-and-after
  ;; CHECK-NEXT:         (i32.const 13)
@@ -1286,7 +1280,7 @@
  ;; CHECK-NEXT:          (i32.const 14)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (block
- ;; CHECK-NEXT:          (br $block$15$break)
+ ;; CHECK-NEXT:          (br $block$13$break)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (block
@@ -1294,13 +1288,13 @@
  ;; CHECK-NEXT:          (i32.const 15)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (block
- ;; CHECK-NEXT:          (br $block$15$break)
+ ;; CHECK-NEXT:          (br $block$13$break)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (block
- ;; CHECK-NEXT:       (block $block$21$break
+ ;; CHECK-NEXT:       (block $block$16$break
  ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (local.set $14
  ;; CHECK-NEXT:          (local.get $x)
@@ -1319,14 +1313,14 @@
  ;; CHECK-NEXT:           (i32.const 16)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (block
- ;; CHECK-NEXT:           (br $block$21$break)
+ ;; CHECK-NEXT:           (br $block$16$break)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (br $block$21$break)
+ ;; CHECK-NEXT:         (br $block$16$break)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (block
- ;; CHECK-NEXT:        (block $block$28$break
+ ;; CHECK-NEXT:        (block $block$19$break
  ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (block
@@ -1354,11 +1348,11 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (block
- ;; CHECK-NEXT:          (br $block$28$break)
+ ;; CHECK-NEXT:          (br $block$19$break)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (block
- ;; CHECK-NEXT:         (block $block$30$break
+ ;; CHECK-NEXT:         (block $block$21$break
  ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (call $before-and-after
  ;; CHECK-NEXT:            (i32.const 23)
@@ -1368,7 +1362,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (block
- ;; CHECK-NEXT:           (br $block$30$break)
+ ;; CHECK-NEXT:           (br $block$21$break)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (block
@@ -1704,7 +1698,7 @@
  )
  ;; CHECK:      (func $no-return
  ;; CHECK-NEXT:  (local $0 i32)
- ;; CHECK-NEXT:  (block $block$6$break
+ ;; CHECK-NEXT:  (block $block$4$break
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (if
@@ -1714,7 +1708,7 @@
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (br $block$6$break)
+ ;; CHECK-NEXT:      (br $block$4$break)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
@@ -1722,7 +1716,7 @@
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (br $block$6$break)
+ ;; CHECK-NEXT:      (br $block$4$break)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
@@ -1757,8 +1751,8 @@
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (local $7 i32)
- ;; CHECK-NEXT:  (block $block$3$break
- ;; CHECK-NEXT:   (block $block$4$break
+ ;; CHECK-NEXT:  (block $block$2$break
+ ;; CHECK-NEXT:   (block $block$12$break
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (call $if-br-wat
  ;; CHECK-NEXT:      (i32.const 0)
@@ -1780,7 +1774,7 @@
  ;; CHECK-NEXT:       (i32.const 1)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (block
- ;; CHECK-NEXT:       (br $block$4$break)
+ ;; CHECK-NEXT:       (br $block$12$break)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
@@ -1797,8 +1791,8 @@
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (if
  ;; CHECK-NEXT:       (local.get $6)
- ;; CHECK-NEXT:       (br $block$3$break)
- ;; CHECK-NEXT:       (br $block$4$break)
+ ;; CHECK-NEXT:       (br $block$2$break)
+ ;; CHECK-NEXT:       (br $block$12$break)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
@@ -1808,7 +1802,7 @@
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
- ;; CHECK-NEXT:     (br $block$3$break)
+ ;; CHECK-NEXT:     (br $block$2$break)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -1898,9 +1892,9 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (block $switch$1$leave
  ;; CHECK-NEXT:    (block $switch$1$default
- ;; CHECK-NEXT:     (block $switch$1$case$4
- ;; CHECK-NEXT:      (block $switch$1$case$5
- ;; CHECK-NEXT:       (br_table $switch$1$case$5 $switch$1$case$4 $switch$1$default
+ ;; CHECK-NEXT:     (block $switch$1$case$3
+ ;; CHECK-NEXT:      (block $switch$1$case$4
+ ;; CHECK-NEXT:       (br_table $switch$1$case$4 $switch$1$case$3 $switch$1$default
  ;; CHECK-NEXT:        (local.get $5)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
diff --git a/test/lit/passes/flatten_simplify-locals-nonesting_dfo_O3.wast b/test/lit/passes/flatten_simplify-locals-nonesting_dfo_O3.wast
index e8f3eaf..9d2789f 100644
--- a/test/lit/passes/flatten_simplify-locals-nonesting_dfo_O3.wast
+++ b/test/lit/passes/flatten_simplify-locals-nonesting_dfo_O3.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --simplify-locals-nonesting --dfo -O3 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast b/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast
index dbc1a17..3a4b026 100644
--- a/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast
+++ b/test/lit/passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --simplify-locals-nonesting --souperify-single-use --enable-threads -S -o - | filecheck %s
 
@@ -144,7 +144,7 @@
   ;; CHECK-NEXT:   (if
   ;; CHECK-NEXT:    (local.get $8)
   ;; CHECK-NEXT:    (block
-  ;; CHECK-NEXT:     (block $block
+  ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (nop)
   ;; CHECK-NEXT:      (nop)
   ;; CHECK-NEXT:      (nop)
@@ -1322,7 +1322,7 @@
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:     (block
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (local.set $x
   ;; CHECK-NEXT:        (i32.const 1)
   ;; CHECK-NEXT:       )
@@ -1379,7 +1379,7 @@
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:     (block
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (local.set $x
   ;; CHECK-NEXT:        (i32.const 1)
   ;; CHECK-NEXT:       )
@@ -1435,7 +1435,7 @@
   ;; CHECK-NEXT:     (if
   ;; CHECK-NEXT:      (local.get $x)
   ;; CHECK-NEXT:      (block
-  ;; CHECK-NEXT:       (block $block
+  ;; CHECK-NEXT:       (block
   ;; CHECK-NEXT:        (local.set $x
   ;; CHECK-NEXT:         (i32.const 1)
   ;; CHECK-NEXT:        )
@@ -1502,7 +1502,7 @@
   ;; CHECK-NEXT:     (if
   ;; CHECK-NEXT:      (local.get $x)
   ;; CHECK-NEXT:      (block
-  ;; CHECK-NEXT:       (block $block
+  ;; CHECK-NEXT:       (block
   ;; CHECK-NEXT:        (local.set $x
   ;; CHECK-NEXT:         (i32.const 1)
   ;; CHECK-NEXT:        )
@@ -1571,7 +1571,7 @@
   ;; CHECK-NEXT:     (nop)
   ;; CHECK-NEXT:     (if
   ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (local.set $x
   ;; CHECK-NEXT:        (i32.const 1)
   ;; CHECK-NEXT:       )
@@ -1645,7 +1645,7 @@
   ;; CHECK-NEXT:         (if
   ;; CHECK-NEXT:          (local.get $0)
   ;; CHECK-NEXT:          (block
-  ;; CHECK-NEXT:           (block $block
+  ;; CHECK-NEXT:           (block
   ;; CHECK-NEXT:            (local.set $1
   ;; CHECK-NEXT:             (i32.const -8531)
   ;; CHECK-NEXT:            )
@@ -1655,7 +1655,7 @@
   ;; CHECK-NEXT:           (unreachable)
   ;; CHECK-NEXT:          )
   ;; CHECK-NEXT:          (block
-  ;; CHECK-NEXT:           (block $block3
+  ;; CHECK-NEXT:           (block
   ;; CHECK-NEXT:            (local.set $1
   ;; CHECK-NEXT:             (i32.const -8531)
   ;; CHECK-NEXT:            )
@@ -1742,7 +1742,7 @@
   ;; CHECK:      (func $in-unreachable-operations (param $x i32) (param $y i32) (result i32)
   ;; CHECK-NEXT:  (local $2 i32)
   ;; CHECK-NEXT:  (local $3 i32)
-  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:   (block
@@ -3599,7 +3599,7 @@
   ;; CHECK-NEXT:      (unreachable)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (block
   ;; CHECK-NEXT:        (loop $label$3
   ;; CHECK-NEXT:         (block $label$4
@@ -4429,7 +4429,7 @@
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (block $block
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (nop)
  ;; CHECK-NEXT:       (local.set $var$2
  ;; CHECK-NEXT:        (i32.add
diff --git a/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast b/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast
index f28bd95..05731df 100644
--- a/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast
+++ b/test/lit/passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --flatten --simplify-locals-nonesting --souperify --enable-threads -S -o - | filecheck %s
 
@@ -144,7 +144,7 @@
   ;; CHECK-NEXT:   (if
   ;; CHECK-NEXT:    (local.get $8)
   ;; CHECK-NEXT:    (block
-  ;; CHECK-NEXT:     (block $block
+  ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (nop)
   ;; CHECK-NEXT:      (nop)
   ;; CHECK-NEXT:      (nop)
@@ -1390,7 +1390,7 @@
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:     (block
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (local.set $x
   ;; CHECK-NEXT:        (i32.const 1)
   ;; CHECK-NEXT:       )
@@ -1447,7 +1447,7 @@
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:     (block
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (local.set $x
   ;; CHECK-NEXT:        (i32.const 1)
   ;; CHECK-NEXT:       )
@@ -1503,7 +1503,7 @@
   ;; CHECK-NEXT:     (if
   ;; CHECK-NEXT:      (local.get $x)
   ;; CHECK-NEXT:      (block
-  ;; CHECK-NEXT:       (block $block
+  ;; CHECK-NEXT:       (block
   ;; CHECK-NEXT:        (local.set $x
   ;; CHECK-NEXT:         (i32.const 1)
   ;; CHECK-NEXT:        )
@@ -1570,7 +1570,7 @@
   ;; CHECK-NEXT:     (if
   ;; CHECK-NEXT:      (local.get $x)
   ;; CHECK-NEXT:      (block
-  ;; CHECK-NEXT:       (block $block
+  ;; CHECK-NEXT:       (block
   ;; CHECK-NEXT:        (local.set $x
   ;; CHECK-NEXT:         (i32.const 1)
   ;; CHECK-NEXT:        )
@@ -1639,7 +1639,7 @@
   ;; CHECK-NEXT:     (nop)
   ;; CHECK-NEXT:     (if
   ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (local.set $x
   ;; CHECK-NEXT:        (i32.const 1)
   ;; CHECK-NEXT:       )
@@ -1713,7 +1713,7 @@
   ;; CHECK-NEXT:         (if
   ;; CHECK-NEXT:          (local.get $0)
   ;; CHECK-NEXT:          (block
-  ;; CHECK-NEXT:           (block $block
+  ;; CHECK-NEXT:           (block
   ;; CHECK-NEXT:            (local.set $1
   ;; CHECK-NEXT:             (i32.const -8531)
   ;; CHECK-NEXT:            )
@@ -1723,7 +1723,7 @@
   ;; CHECK-NEXT:           (unreachable)
   ;; CHECK-NEXT:          )
   ;; CHECK-NEXT:          (block
-  ;; CHECK-NEXT:           (block $block3
+  ;; CHECK-NEXT:           (block
   ;; CHECK-NEXT:            (local.set $1
   ;; CHECK-NEXT:             (i32.const -8531)
   ;; CHECK-NEXT:            )
@@ -1810,7 +1810,7 @@
   ;; CHECK:      (func $in-unreachable-operations (param $x i32) (param $y i32) (result i32)
   ;; CHECK-NEXT:  (local $2 i32)
   ;; CHECK-NEXT:  (local $3 i32)
-  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:   (block
@@ -3667,7 +3667,7 @@
   ;; CHECK-NEXT:      (unreachable)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block
-  ;; CHECK-NEXT:      (block $block
+  ;; CHECK-NEXT:      (block
   ;; CHECK-NEXT:       (block
   ;; CHECK-NEXT:        (loop $label$3
   ;; CHECK-NEXT:         (block $label$4
@@ -4497,7 +4497,7 @@
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:     (block
- ;; CHECK-NEXT:      (block $block
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (nop)
  ;; CHECK-NEXT:       (local.set $var$2
  ;; CHECK-NEXT:        (i32.add
diff --git a/test/lit/passes/fpcast-emu.wast b/test/lit/passes/fpcast-emu.wast
index 8c4fb72..b40881e 100644
--- a/test/lit/passes/fpcast-emu.wast
+++ b/test/lit/passes/fpcast-emu.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --fpcast-emu -S -o - | filecheck %s
 
diff --git a/test/lit/passes/generate-dyncalls_all-features.wast b/test/lit/passes/generate-dyncalls_all-features.wast
index a234d12..5f29a36 100644
--- a/test/lit/passes/generate-dyncalls_all-features.wast
+++ b/test/lit/passes/generate-dyncalls_all-features.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --generate-dyncalls --all-features -S -o - | filecheck %s
 
diff --git a/test/lit/passes/generate-i64-dyncalls.wast b/test/lit/passes/generate-i64-dyncalls.wast
index 80f873c..8f5e602 100644
--- a/test/lit/passes/generate-i64-dyncalls.wast
+++ b/test/lit/passes/generate-i64-dyncalls.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --generate-i64-dyncalls -S -o - | filecheck %s
 
diff --git a/test/lit/passes/global-effects.wast b/test/lit/passes/global-effects.wast
new file mode 100644
index 0000000..4207ae0
--- /dev/null
+++ b/test/lit/passes/global-effects.wast
@@ -0,0 +1,323 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Run without global effects, and run with, and also run with but discard them
+;; first (to check that discard works; that should be the same as without).
+
+;; RUN: foreach %s %t wasm-opt -all                                                    --vacuum -S -o - | filecheck %s --check-prefix WITHOUT
+;; RUN: foreach %s %t wasm-opt -all --generate-global-effects                          --vacuum -S -o - | filecheck %s --check-prefix INCLUDE
+;; RUN: foreach %s %t wasm-opt -all --generate-global-effects --discard-global-effects --vacuum -S -o - | filecheck %s --check-prefix DISCARD
+
+(module
+  ;; WITHOUT:      (type $none_=>_none (func))
+
+  ;; WITHOUT:      (type $none_=>_i32 (func (result i32)))
+
+  ;; WITHOUT:      (type $i32_=>_none (func (param i32)))
+
+  ;; WITHOUT:      (tag $tag (param))
+  ;; INCLUDE:      (type $none_=>_none (func))
+
+  ;; INCLUDE:      (type $none_=>_i32 (func (result i32)))
+
+  ;; INCLUDE:      (type $i32_=>_none (func (param i32)))
+
+  ;; INCLUDE:      (tag $tag (param))
+  ;; DISCARD:      (type $none_=>_none (func))
+
+  ;; DISCARD:      (type $none_=>_i32 (func (result i32)))
+
+  ;; DISCARD:      (type $i32_=>_none (func (param i32)))
+
+  ;; DISCARD:      (tag $tag (param))
+  (tag $tag)
+
+  ;; WITHOUT:      (func $main
+  ;; WITHOUT-NEXT:  (call $nop)
+  ;; WITHOUT-NEXT:  (call $unreachable)
+  ;; WITHOUT-NEXT:  (call $call-nop)
+  ;; WITHOUT-NEXT:  (call $call-unreachable)
+  ;; WITHOUT-NEXT:  (drop
+  ;; WITHOUT-NEXT:   (call $unimportant-effects)
+  ;; WITHOUT-NEXT:  )
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $main
+  ;; INCLUDE-NEXT:  (call $unreachable)
+  ;; INCLUDE-NEXT:  (call $call-nop)
+  ;; INCLUDE-NEXT:  (call $call-unreachable)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $main
+  ;; DISCARD-NEXT:  (call $nop)
+  ;; DISCARD-NEXT:  (call $unreachable)
+  ;; DISCARD-NEXT:  (call $call-nop)
+  ;; DISCARD-NEXT:  (call $call-unreachable)
+  ;; DISCARD-NEXT:  (drop
+  ;; DISCARD-NEXT:   (call $unimportant-effects)
+  ;; DISCARD-NEXT:  )
+  ;; DISCARD-NEXT: )
+  (func $main
+    ;; Calling a function with no effects can be optimized away in INCLUDE (but
+    ;; not WITHOUT or DISCARD, where the global effect info is not available).
+    (call $nop)
+    ;; Calling a function with effects cannot.
+    (call $unreachable)
+    ;; Calling something that calls something with no effects can be optimized
+    ;; away in principle, but atm we don't look that far, so this is not
+    ;; optimized.
+    (call $call-nop)
+    ;; Calling something that calls something with effects cannot.
+    (call $call-unreachable)
+    ;; Calling something that only has unimportant effects can be optimized
+    ;; (see below for details).
+    (drop
+      (call $unimportant-effects)
+    )
+  )
+
+  ;; WITHOUT:      (func $cycle
+  ;; WITHOUT-NEXT:  (call $cycle)
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $cycle
+  ;; INCLUDE-NEXT:  (call $cycle)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $cycle
+  ;; DISCARD-NEXT:  (call $cycle)
+  ;; DISCARD-NEXT: )
+  (func $cycle
+    ;; Calling a function with no effects in a cycle cannot be optimized out -
+    ;; this must keep hanging forever.
+    (call $cycle)
+  )
+
+  ;; WITHOUT:      (func $nop
+  ;; WITHOUT-NEXT:  (nop)
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $nop
+  ;; INCLUDE-NEXT:  (nop)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $nop
+  ;; DISCARD-NEXT:  (nop)
+  ;; DISCARD-NEXT: )
+  (func $nop
+    (nop)
+  )
+
+  ;; WITHOUT:      (func $unreachable
+  ;; WITHOUT-NEXT:  (unreachable)
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $unreachable
+  ;; INCLUDE-NEXT:  (unreachable)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $unreachable
+  ;; DISCARD-NEXT:  (unreachable)
+  ;; DISCARD-NEXT: )
+  (func $unreachable
+    (unreachable)
+  )
+
+  ;; WITHOUT:      (func $call-nop
+  ;; WITHOUT-NEXT:  (call $nop)
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $call-nop
+  ;; INCLUDE-NEXT:  (nop)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $call-nop
+  ;; DISCARD-NEXT:  (call $nop)
+  ;; DISCARD-NEXT: )
+  (func $call-nop
+    ;; This call to a nop can be optimized out, as above, in INCLUDE.
+    (call $nop)
+  )
+
+  ;; WITHOUT:      (func $call-unreachable
+  ;; WITHOUT-NEXT:  (call $unreachable)
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $call-unreachable
+  ;; INCLUDE-NEXT:  (call $unreachable)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $call-unreachable
+  ;; DISCARD-NEXT:  (call $unreachable)
+  ;; DISCARD-NEXT: )
+  (func $call-unreachable
+    (call $unreachable)
+  )
+
+  ;; WITHOUT:      (func $unimportant-effects (result i32)
+  ;; WITHOUT-NEXT:  (local $x i32)
+  ;; WITHOUT-NEXT:  (local.set $x
+  ;; WITHOUT-NEXT:   (i32.const 100)
+  ;; WITHOUT-NEXT:  )
+  ;; WITHOUT-NEXT:  (return
+  ;; WITHOUT-NEXT:   (local.get $x)
+  ;; WITHOUT-NEXT:  )
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $unimportant-effects (result i32)
+  ;; INCLUDE-NEXT:  (local $x i32)
+  ;; INCLUDE-NEXT:  (local.set $x
+  ;; INCLUDE-NEXT:   (i32.const 100)
+  ;; INCLUDE-NEXT:  )
+  ;; INCLUDE-NEXT:  (return
+  ;; INCLUDE-NEXT:   (local.get $x)
+  ;; INCLUDE-NEXT:  )
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $unimportant-effects (result i32)
+  ;; DISCARD-NEXT:  (local $x i32)
+  ;; DISCARD-NEXT:  (local.set $x
+  ;; DISCARD-NEXT:   (i32.const 100)
+  ;; DISCARD-NEXT:  )
+  ;; DISCARD-NEXT:  (return
+  ;; DISCARD-NEXT:   (local.get $x)
+  ;; DISCARD-NEXT:  )
+  ;; DISCARD-NEXT: )
+  (func $unimportant-effects (result i32)
+    (local $x i32)
+    ;; Operations on locals should not prevent optimization, as when we return
+    ;; from the function they no longer matter.
+    (local.set $x
+      (i32.const 100)
+    )
+    ;; A return is an effect that no longer matters once we exit the function.
+    (return
+      (local.get $x)
+    )
+  )
+
+  ;; WITHOUT:      (func $call-throw-and-catch
+  ;; WITHOUT-NEXT:  (try $try
+  ;; WITHOUT-NEXT:   (do
+  ;; WITHOUT-NEXT:    (call $throw)
+  ;; WITHOUT-NEXT:   )
+  ;; WITHOUT-NEXT:   (catch_all
+  ;; WITHOUT-NEXT:    (nop)
+  ;; WITHOUT-NEXT:   )
+  ;; WITHOUT-NEXT:  )
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $call-throw-and-catch
+  ;; INCLUDE-NEXT:  (nop)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $call-throw-and-catch
+  ;; DISCARD-NEXT:  (try $try
+  ;; DISCARD-NEXT:   (do
+  ;; DISCARD-NEXT:    (call $throw)
+  ;; DISCARD-NEXT:   )
+  ;; DISCARD-NEXT:   (catch_all
+  ;; DISCARD-NEXT:    (nop)
+  ;; DISCARD-NEXT:   )
+  ;; DISCARD-NEXT:  )
+  ;; DISCARD-NEXT: )
+  (func $call-throw-and-catch
+    (try
+      (do
+        ;; This call cannot be optimized out, as the target throws. However, the
+        ;; entire try-catch can be, since the call's only effect is to throw,
+        ;; and the catch_all catches that.
+        (call $throw)
+      )
+      (catch_all)
+    )
+  )
+
+  ;; WITHOUT:      (func $call-unreachable-and-catch
+  ;; WITHOUT-NEXT:  (try $try
+  ;; WITHOUT-NEXT:   (do
+  ;; WITHOUT-NEXT:    (call $unreachable)
+  ;; WITHOUT-NEXT:   )
+  ;; WITHOUT-NEXT:   (catch_all
+  ;; WITHOUT-NEXT:    (nop)
+  ;; WITHOUT-NEXT:   )
+  ;; WITHOUT-NEXT:  )
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $call-unreachable-and-catch
+  ;; INCLUDE-NEXT:  (call $unreachable)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $call-unreachable-and-catch
+  ;; DISCARD-NEXT:  (try $try
+  ;; DISCARD-NEXT:   (do
+  ;; DISCARD-NEXT:    (call $unreachable)
+  ;; DISCARD-NEXT:   )
+  ;; DISCARD-NEXT:   (catch_all
+  ;; DISCARD-NEXT:    (nop)
+  ;; DISCARD-NEXT:   )
+  ;; DISCARD-NEXT:  )
+  ;; DISCARD-NEXT: )
+  (func $call-unreachable-and-catch
+    (try
+      (do
+        ;; This call has a non-throw effect. We can optimize away the try-catch
+        ;; (since no exception can be thrown anyhow), but we must leave the
+        ;; call.
+        (call $unreachable)
+      )
+      (catch_all)
+    )
+  )
+
+  ;; WITHOUT:      (func $call-throw-or-unreachable-and-catch (param $x i32)
+  ;; WITHOUT-NEXT:  (try $try
+  ;; WITHOUT-NEXT:   (do
+  ;; WITHOUT-NEXT:    (if
+  ;; WITHOUT-NEXT:     (local.get $x)
+  ;; WITHOUT-NEXT:     (call $throw)
+  ;; WITHOUT-NEXT:     (call $unreachable)
+  ;; WITHOUT-NEXT:    )
+  ;; WITHOUT-NEXT:   )
+  ;; WITHOUT-NEXT:   (catch_all
+  ;; WITHOUT-NEXT:    (nop)
+  ;; WITHOUT-NEXT:   )
+  ;; WITHOUT-NEXT:  )
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $call-throw-or-unreachable-and-catch (param $x i32)
+  ;; INCLUDE-NEXT:  (try $try
+  ;; INCLUDE-NEXT:   (do
+  ;; INCLUDE-NEXT:    (if
+  ;; INCLUDE-NEXT:     (local.get $x)
+  ;; INCLUDE-NEXT:     (call $throw)
+  ;; INCLUDE-NEXT:     (call $unreachable)
+  ;; INCLUDE-NEXT:    )
+  ;; INCLUDE-NEXT:   )
+  ;; INCLUDE-NEXT:   (catch_all
+  ;; INCLUDE-NEXT:    (nop)
+  ;; INCLUDE-NEXT:   )
+  ;; INCLUDE-NEXT:  )
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $call-throw-or-unreachable-and-catch (param $x i32)
+  ;; DISCARD-NEXT:  (try $try
+  ;; DISCARD-NEXT:   (do
+  ;; DISCARD-NEXT:    (if
+  ;; DISCARD-NEXT:     (local.get $x)
+  ;; DISCARD-NEXT:     (call $throw)
+  ;; DISCARD-NEXT:     (call $unreachable)
+  ;; DISCARD-NEXT:    )
+  ;; DISCARD-NEXT:   )
+  ;; DISCARD-NEXT:   (catch_all
+  ;; DISCARD-NEXT:    (nop)
+  ;; DISCARD-NEXT:   )
+  ;; DISCARD-NEXT:  )
+  ;; DISCARD-NEXT: )
+  (func $call-throw-or-unreachable-and-catch (param $x i32)
+    ;; This try-catch-all's body will either call a throw or an unreachable.
+    ;; Since we have both possible effects, we cannot optimize anything here.
+    (try
+      (do
+        (if
+          (local.get $x)
+          (call $throw)
+          (call $unreachable)
+        )
+      )
+      (catch_all)
+    )
+  )
+
+  ;; WITHOUT:      (func $throw
+  ;; WITHOUT-NEXT:  (throw $tag)
+  ;; WITHOUT-NEXT: )
+  ;; INCLUDE:      (func $throw
+  ;; INCLUDE-NEXT:  (throw $tag)
+  ;; INCLUDE-NEXT: )
+  ;; DISCARD:      (func $throw
+  ;; DISCARD-NEXT:  (throw $tag)
+  ;; DISCARD-NEXT: )
+  (func $throw
+    (throw $tag)
+  )
+)
diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast
index 633d88e..645b170 100644
--- a/test/lit/passes/global-refining.wast
+++ b/test/lit/passes/global-refining.wast
@@ -6,40 +6,42 @@
   ;; a null, so we have nothing concrete to improve with (though we could use
   ;; the type of the null perhaps, TODO). The second is a ref.func which lets
   ;; us refine.
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
-
-  ;; CHECK:      (global $func-null-init (mut anyref) (ref.null func))
-  (global $func-null-init (mut anyref) (ref.null func))
-  ;; CHECK:      (global $func-func-init (mut (ref $none_=>_none)) (ref.func $foo))
-  (global $func-func-init (mut anyref) (ref.func $foo))
-  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK:      (type $foo_t (func_subtype func))
+  (type $foo_t (func))
+
+  ;; CHECK:      (global $func-null-init (mut funcref) (ref.null nofunc))
+  (global $func-null-init (mut funcref) (ref.null $foo_t))
+  ;; CHECK:      (global $func-func-init (mut (ref $foo_t)) (ref.func $foo))
+  (global $func-func-init (mut funcref) (ref.func $foo))
+  ;; CHECK:      (func $foo (type $foo_t)
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT: )
-  (func $foo)
+  (func $foo (type $foo_t))
 )
 
 (module
   ;; Globals with later assignments of null. The global with a function in its
   ;; init will update the null to allow it to refine.
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $foo_t (func_subtype func))
+  (type $foo_t (func))
 
-  ;; CHECK:      (global $func-null-init (mut anyref) (ref.null func))
-  (global $func-null-init (mut anyref) (ref.null func))
-  ;; CHECK:      (global $func-func-init (mut (ref null $none_=>_none)) (ref.func $foo))
-  (global $func-func-init (mut anyref) (ref.func $foo))
+  ;; CHECK:      (global $func-null-init (mut funcref) (ref.null nofunc))
+  (global $func-null-init (mut funcref) (ref.null $foo_t))
+  ;; CHECK:      (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo))
+  (global $func-func-init (mut funcref) (ref.func $foo))
 
-  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK:      (func $foo (type $foo_t)
   ;; CHECK-NEXT:  (global.set $func-null-init
-  ;; CHECK-NEXT:   (ref.null any)
+  ;; CHECK-NEXT:   (ref.null nofunc)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (global.set $func-func-init
-  ;; CHECK-NEXT:   (ref.null $none_=>_none)
+  ;; CHECK-NEXT:   (ref.null nofunc)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $foo
-   (global.set $func-null-init (ref.null any))
-   (global.set $func-func-init (ref.null any))
+  (func $foo (type $foo_t)
+   (global.set $func-null-init (ref.null func))
+   (global.set $func-func-init (ref.null $foo_t))
   )
 )
 
@@ -49,10 +51,10 @@
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
-  ;; CHECK:      (global $func-null-init (mut (ref null $none_=>_none)) (ref.null $none_=>_none))
-  (global $func-null-init (mut anyref) (ref.null func))
+  ;; CHECK:      (global $func-null-init (mut (ref null $none_=>_none)) (ref.null nofunc))
+  (global $func-null-init (mut funcref) (ref.null func))
   ;; CHECK:      (global $func-func-init (mut (ref $none_=>_none)) (ref.func $foo))
-  (global $func-func-init (mut anyref) (ref.func $foo))
+  (global $func-func-init (mut funcref) (ref.func $foo))
 
   ;; CHECK:      (elem declare func $foo)
 
@@ -76,44 +78,38 @@
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
-  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+  (type $array (array i8))
 
-  ;; CHECK:      (global $global (mut funcref) (ref.null func))
+  ;; CHECK:      (global $global (mut eqref) (ref.null none))
   (global $global (mut anyref) (ref.null any))
 
-  ;; CHECK:      (elem declare func $bar $foo)
-
   ;; CHECK:      (func $foo (type $none_=>_none)
   ;; CHECK-NEXT:  (global.set $global
-  ;; CHECK-NEXT:   (ref.func $foo)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (global.set $global
-  ;; CHECK-NEXT:   (ref.func $bar)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (global.set $global
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (global.set $global
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (global.set $global
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $foo
-   (global.set $global (ref.func $foo))
-   (global.set $global (ref.func $bar))
-   (global.set $global (ref.null func))
-   ;; These nulls will be updated.
+   (global.set $global (i31.new (i32.const 0)))
+   (global.set $global (struct.new_default $struct))
    (global.set $global (ref.null eq))
-   (global.set $global (ref.null data))
-  )
-
-  ;; CHECK:      (func $bar (type $i32_=>_none) (param $x i32)
-  ;; CHECK-NEXT:  (nop)
-  ;; CHECK-NEXT: )
-  (func $bar (param $x i32)
-    ;; A function with a different signature, whose reference is also assigned
-    ;; to the global.
+   ;; These nulls will be updated.
+   (global.set $global (ref.null i31))
+   (global.set $global (ref.null $array))
   )
 )
diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast
new file mode 100644
index 0000000..66ca43d
--- /dev/null
+++ b/test/lit/passes/gsi.wast
@@ -0,0 +1,1178 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --nominal --gsi -all -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; A non-reference global does not confuse us.
+  ;; CHECK:      (global $global-other i32 (i32.const 123456))
+  (global $global-other i32 (i32.const 123456))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    ;; We can infer that this get can reference either $global1 or $global2,
+    ;; and nothing else (aside from a null), and can emit a select between
+    ;; those values.
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; As above, but now the field is mutable, so we cannot optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; Just one global.
+(module
+  ;; CHECK:      (type $struct1 (struct_subtype (field i32) data))
+  (type $struct1 (struct i32))
+
+  ;; CHECK:      (type $struct2 (struct_subtype (field i32) data))
+  (type $struct2 (struct i32))
+
+
+  ;; CHECK:      (type $ref?|$struct1|_ref?|$struct2|_=>_none (func_subtype (param (ref null $struct1) (ref null $struct2)) func))
+
+  ;; CHECK:      (import "a" "b" (global $imported i32))
+  (import "a" "b" (global $imported i32))
+
+  ;; CHECK:      (global $global1 (ref $struct1) (struct.new $struct1
+  ;; CHECK-NEXT:  (global.get $imported)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct1) (struct.new $struct1
+    (global.get $imported)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct2) (struct.new $struct2
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct2) (struct.new $struct2
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (func $test1 (type $ref?|$struct1|_ref?|$struct2|_=>_none) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct1 0
+  ;; CHECK-NEXT:    (block (result (ref $struct1))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (ref.as_non_null
+  ;; CHECK-NEXT:       (local.get $struct1)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct2 0
+  ;; CHECK-NEXT:    (block (result (ref $struct2))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (ref.as_non_null
+  ;; CHECK-NEXT:       (local.get $struct2)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test1 (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2))
+    ;; We can infer that this get must reference $global1 and make the reference
+    ;; point to that. Note that we do not infer the value of 42 here, but leave
+    ;; it for other passes to do.
+    (drop
+      (struct.get $struct1 0
+        (local.get $struct1)
+      )
+    )
+    ;; Even though the value here is not known at compile time - it reads an
+    ;; imported global - we can still infer that we are reading from $global2.
+    (drop
+      (struct.get $struct2 0
+        (local.get $struct2)
+      )
+    )
+  )
+)
+
+;; Three globals. For now, we do not optimize here.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global3 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 99999)
+  ;; CHECK-NEXT: ))
+  (global $global3 (ref $struct) (struct.new $struct
+    (i32.const 99999)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; Three globals, as above, but now two agree on their values. We can optimize
+;; by comparing to the one that has a single value.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global3 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global3 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; As above, but move the different value of the three to the middle.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global3 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global3 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; As above, but move the different value of the three to the end.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global3 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global3 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; Four values, two pairs of equal ones. We do not optimize this.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global3 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global3 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global4 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global4 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; Four values, three equal and one unique. We can optimize this with a single
+;; comparison on the unique one.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global3 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global3 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global4 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global4 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; A struct.new inside a function stops us from optimizing.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.new $struct
+        (i32.const 1)
+      )
+    )
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; We ignore imports, as we assume a closed world, but that might change in the
+;; future. For now, we will optimize here.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (import "a" "b" (global $global-import (ref $struct)))
+  (import "a" "b" (global $global-import (ref $struct)))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; A struct.new in a non-toplevel position in a global stops us from
+;; optimizing.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $tuple (struct_subtype (field anyref) (field anyref) data))
+  (type $tuple (struct anyref anyref))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global-tuple (ref $tuple) (struct.new $tuple
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.const 999999)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (ref.null none)
+  ;; CHECK-NEXT: ))
+  (global $global-tuple (ref $tuple) (struct.new $tuple
+    (struct.new $struct
+      (i32.const 999999)
+    )
+    (ref.null any)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; As above, but remove the struct.new in a nested position, while keeping all
+;; the other stuff in the above test. Now we should optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $tuple (struct_subtype (field anyref) (field anyref) data))
+  (type $tuple (struct anyref anyref))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (global $global-tuple (ref $tuple) (struct.new $tuple
+  ;; CHECK-NEXT:  (ref.null none)
+  ;; CHECK-NEXT:  (ref.null none)
+  ;; CHECK-NEXT: ))
+  (global $global-tuple (ref $tuple) (struct.new $tuple
+    (ref.null any)
+    (ref.null any)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; When one of the globals is mutable, we cannot optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (mut (ref $struct)) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (mut (ref $struct)) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; A subtype is not optimizable, which prevents $struct from being optimized.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct_subtype i32 data))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (type $sub-struct (struct_subtype (field i32) $struct))
+  (type $sub-struct (struct_subtype i32 $struct))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $sub-struct
+  ;; CHECK-NEXT:    (i32.const 999999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.new $sub-struct
+        (i32.const 999999)
+      )
+    )
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; A *super*-type is not optimizable, but that does not block us, and we can
+;; optimize.
+(module
+  ;; CHECK:      (type $super-struct (struct_subtype (field i32) data))
+  (type $super-struct (struct_subtype i32 data))
+
+  ;; CHECK:      (type $struct (struct_subtype (field i32) $super-struct))
+  (type $struct (struct_subtype i32 $super-struct))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $super-struct
+  ;; CHECK-NEXT:    (i32.const 999999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.new $super-struct
+        (i32.const 999999)
+      )
+    )
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; One global for each of the type and the subtype. The optimization will pick
+;; between their 2 values.
+(module
+  ;; CHECK:      (type $super-struct (struct_subtype (field i32) data))
+  (type $super-struct (struct_subtype i32 data))
+
+  ;; CHECK:      (type $struct (struct_subtype (field i32) $super-struct))
+  (type $struct (struct_subtype i32 $super-struct))
+
+  ;; CHECK:      (type $ref?|$struct|_ref?|$super-struct|_=>_none (func_subtype (param (ref null $struct) (ref null $super-struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $super-struct) (struct.new $super-struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $super-struct) (struct.new $super-struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_ref?|$super-struct|_=>_none) (param $struct (ref null $struct)) (param $super-struct (ref null $super-struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (block (result (ref $struct))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (ref.as_non_null
+  ;; CHECK-NEXT:       (local.get $struct)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $super-struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct)) (param $super-struct (ref null $super-struct))
+    ;; The first has just one global, which we switch the reference to, while
+    ;; the second will consider the struct and sub-struct, find 2 possible
+    ;; values, and optimize.
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+    (drop
+      (struct.get $super-struct 0
+        (local.get $super-struct)
+      )
+    )
+  )
+)
+
+;; One global has a non-constant field, so we cannot optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.add
+  ;; CHECK-NEXT:   (i32.const 41)
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.add
+      (i32.const 41)
+      (i32.const 1)
+    )
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 1337)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
+
+;; One global each for two subtypes of a common supertype, and one for the
+;; supertype.
+(module
+  ;; CHECK:      (type $super-struct (struct_subtype (field i32) data))
+  (type $super-struct (struct_subtype i32 data))
+
+  ;; CHECK:      (type $struct1 (struct_subtype (field i32) (field f32) $super-struct))
+  (type $struct1 (struct_subtype i32 f32 $super-struct))
+
+  ;; CHECK:      (type $struct2 (struct_subtype (field i32) (field f64) $super-struct))
+  (type $struct2 (struct_subtype i32 f64 $super-struct))
+
+
+  ;; CHECK:      (type $ref?|$super-struct|_ref?|$struct1|_ref?|$struct2|_=>_none (func_subtype (param (ref null $super-struct) (ref null $struct1) (ref null $struct2)) func))
+
+  ;; CHECK:      (global $global0 (ref $super-struct) (struct.new $super-struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global0 (ref $super-struct) (struct.new $super-struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global1 (ref $struct1) (struct.new $struct1
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT:  (f32.const 3.141590118408203)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct1) (struct.new $struct1
+    (i32.const 1337)
+    (f32.const 3.14159)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct2) (struct.new $struct2
+  ;; CHECK-NEXT:  (i32.const 99999)
+  ;; CHECK-NEXT:  (f64.const 2.71828)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct2) (struct.new $struct2
+    (i32.const 99999)
+    (f64.const 2.71828)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$super-struct|_ref?|$struct1|_ref?|$struct2|_=>_none) (param $super-struct (ref null $super-struct)) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $super-struct 0
+  ;; CHECK-NEXT:    (local.get $super-struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct1 0
+  ;; CHECK-NEXT:    (block (result (ref $struct1))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (ref.as_non_null
+  ;; CHECK-NEXT:       (local.get $struct1)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct2 0
+  ;; CHECK-NEXT:    (block (result (ref $struct2))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (ref.as_non_null
+  ;; CHECK-NEXT:       (local.get $struct2)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $super-struct (ref null $super-struct)) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2))
+    ;; This has three possible values due to the two children, so we do not
+    ;; optimize.
+    (drop
+      (struct.get $super-struct 0
+        (local.get $super-struct)
+      )
+    )
+    ;; These each have one possible value, which we can switch the references
+    ;; to.
+    (drop
+      (struct.get $struct1 0
+        (local.get $struct1)
+      )
+    )
+    (drop
+      (struct.get $struct2 0
+        (local.get $struct2)
+      )
+    )
+  )
+)
+
+;; As above, but now the subtypes each have 2 values, and we can optimize.
+(module
+  ;; CHECK:      (type $super-struct (struct_subtype (field i32) data))
+  (type $super-struct (struct_subtype i32 data))
+
+  ;; CHECK:      (type $struct1 (struct_subtype (field i32) (field f32) $super-struct))
+  (type $struct1 (struct_subtype i32 f32 $super-struct))
+
+  ;; CHECK:      (type $struct2 (struct_subtype (field i32) (field f64) $super-struct))
+  (type $struct2 (struct_subtype i32 f64 $super-struct))
+
+
+  ;; CHECK:      (type $ref?|$super-struct|_ref?|$struct1|_ref?|$struct2|_=>_none (func_subtype (param (ref null $super-struct) (ref null $struct1) (ref null $struct2)) func))
+
+  ;; CHECK:      (global $global0 (ref $super-struct) (struct.new $super-struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global0 (ref $super-struct) (struct.new $super-struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global1 (ref $struct1) (struct.new $struct1
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT:  (f32.const 3.141590118408203)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct1) (struct.new $struct1
+    (i32.const 1337)
+    (f32.const 3.14159)
+  ))
+
+  ;; CHECK:      (global $global1b (ref $struct1) (struct.new $struct1
+  ;; CHECK-NEXT:  (i32.const 1338)
+  ;; CHECK-NEXT:  (f32.const 3.141590118408203)
+  ;; CHECK-NEXT: ))
+  (global $global1b (ref $struct1) (struct.new $struct1
+    (i32.const 1338)
+    (f32.const 3.14159)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct2) (struct.new $struct2
+  ;; CHECK-NEXT:  (i32.const 99999)
+  ;; CHECK-NEXT:  (f64.const 2.71828)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct2) (struct.new $struct2
+    (i32.const 99999)
+    (f64.const 2.71828)
+  ))
+
+  ;; CHECK:      (global $global2b (ref $struct2) (struct.new $struct2
+  ;; CHECK-NEXT:  (i32.const 99998)
+  ;; CHECK-NEXT:  (f64.const 2.71828)
+  ;; CHECK-NEXT: ))
+  (global $global2b (ref $struct2) (struct.new $struct2
+    (i32.const 99998)
+    (f64.const 2.71828)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$super-struct|_ref?|$struct1|_ref?|$struct2|_=>_none) (param $super-struct (ref null $super-struct)) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $super-struct 0
+  ;; CHECK-NEXT:    (local.get $super-struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (i32.const 1338)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 99999)
+  ;; CHECK-NEXT:    (i32.const 99998)
+  ;; CHECK-NEXT:    (ref.eq
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct2)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (global.get $global2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $super-struct (ref null $super-struct)) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2))
+    ;; This still cannot be optimized.
+    (drop
+      (struct.get $super-struct 0
+        (local.get $super-struct)
+      )
+    )
+    ;; These can be optimized, and will be different from one another.
+    (drop
+      (struct.get $struct1 0
+        (local.get $struct1)
+      )
+    )
+    (drop
+      (struct.get $struct2 0
+        (local.get $struct2)
+      )
+    )
+  )
+)
+
+;; Multiple globals, but all the same value, so we do not even need a select and
+;; can just apply the value.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
+
+  ;; CHECK:      (global $global1 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global1 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (global $global2 (ref $struct) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $global2 (ref $struct) (struct.new $struct
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (param $struct (ref null $struct))
+    (drop
+      (struct.get $struct 0
+        (local.get $struct)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/gsi_vacuum_precompute.wast b/test/lit/passes/gsi_vacuum_precompute.wast
new file mode 100644
index 0000000..856499b
--- /dev/null
+++ b/test/lit/passes/gsi_vacuum_precompute.wast
@@ -0,0 +1,99 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --nominal --gsi --vacuum --precompute -tnh -all -S -o - | filecheck %s
+
+;; Test a common pattern in j2wasm where itables are differentiated by type, but
+;; vtables are not. For example, the vtable might be "hashable" and provide a
+;; method "getHash()" which all itables implement by an instance of that vtable.
+;; Despite all those vtables having the same type, we can infer things because
+;; those vtables are created as part of the immutable itables in global vars.
+;; The specific optimizations used to achieve that are:
+;;
+;;  * --gsi : Infers that a reference to a particular itable type must be a
+;;            global.get of the single itable defined using that type (since no
+;;            other object of that type is ever created).
+;;  * --vacuum : Cleans up some dropped stuff to enable the subsequent pass.
+;;  * --precompute : Represents immutable globals in a way that we can start
+;;                   from a global.get of an itable, get a vtable, and get a
+;;                   field in the vtable, and we end up optimizing to produce
+;;                   the final value in that vtable.
+;;  * -tnh : This is needed for vacuum to successfully remove the dropped stuff
+;;           that could prevent later opts.
+
+(module
+ ;; CHECK:      (type $vtable (struct_subtype (field funcref) data))
+
+ ;; CHECK:      (type $itable1 (struct_subtype (field (ref $vtable)) data))
+ (type $itable1 (struct_subtype (field (ref $vtable)) data))
+ ;; CHECK:      (type $itable2 (struct_subtype (field (ref $vtable)) data))
+ (type $itable2 (struct_subtype (field (ref $vtable)) data))
+ (type $vtable (struct_subtype (field funcref) data))
+
+ ;; Two $vtable instances are created, in separate enclosing objects.
+
+ ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+ ;; CHECK:      (type $ref|$itable1|_=>_funcref (func_subtype (param (ref $itable1)) (result funcref) func))
+
+ ;; CHECK:      (type $ref|$itable2|_=>_funcref (func_subtype (param (ref $itable2)) (result funcref) func))
+
+ ;; CHECK:      (global $itable1 (ref $itable1) (struct.new $itable1
+ ;; CHECK-NEXT:  (struct.new $vtable
+ ;; CHECK-NEXT:   (ref.func $func1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: ))
+ (global $itable1 (ref $itable1) (struct.new $itable1
+  (struct.new $vtable
+   (ref.func $func1)
+  )
+ ))
+
+ ;; CHECK:      (global $itable2 (ref $itable2) (struct.new $itable2
+ ;; CHECK-NEXT:  (struct.new $vtable
+ ;; CHECK-NEXT:   (ref.func $func2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: ))
+ (global $itable2 (ref $itable2) (struct.new $itable2
+  (struct.new $vtable
+   (ref.func $func2)
+  )
+ ))
+
+ ;; CHECK:      (elem declare func $func1 $func2)
+
+ ;; CHECK:      (export "test-A" (func $test-A))
+
+ ;; CHECK:      (export "test-B" (func $test-B))
+
+ ;; CHECK:      (func $test-A (type $ref|$itable1|_=>_funcref) (param $ref (ref $itable1)) (result funcref)
+ ;; CHECK-NEXT:  (ref.func $func1)
+ ;; CHECK-NEXT: )
+ (func $test-A (export "test-A") (param $ref (ref $itable1)) (result funcref)
+  (struct.get $vtable 0    ;; this is a reference to $func1
+   (struct.get $itable1 0  ;; this is the sub-object in the global $itable1
+    (local.get $ref)       ;; this can be inferred to be the global $itable1
+   )
+  )
+ )
+
+ ;; CHECK:      (func $test-B (type $ref|$itable2|_=>_funcref) (param $ref (ref $itable2)) (result funcref)
+ ;; CHECK-NEXT:  (ref.func $func2)
+ ;; CHECK-NEXT: )
+ (func $test-B (export "test-B") (param $ref (ref $itable2)) (result funcref)
+  (struct.get $vtable 0    ;; this is a reference to $func2
+   (struct.get $itable2 0  ;; this is the sub-object in the global $itable2
+    (local.get $ref)       ;; this can be inferred to be the global $itable2
+   )
+  )
+ )
+
+ ;; CHECK:      (func $func1 (type $none_=>_none)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $func1)
+
+ ;; CHECK:      (func $func2 (type $none_=>_none)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $func2)
+)
+
diff --git a/test/lit/passes/gto-mutability.wast b/test/lit/passes/gto-mutability.wast
index 4ecab4f..edcf43b 100644
--- a/test/lit/passes/gto-mutability.wast
+++ b/test/lit/passes/gto-mutability.wast
@@ -20,7 +20,7 @@
 
   ;; CHECK:      (type $none_=>_ref?|$struct| (func_subtype (result (ref null $struct)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (table $0 0 funcref)
 
@@ -33,18 +33,18 @@
   ;; CHECK-NEXT:  (local $temp (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
-  ;; CHECK-NEXT:    (ref.null func)
-  ;; CHECK-NEXT:    (ref.null func)
-  ;; CHECK-NEXT:    (ref.null func)
+  ;; CHECK-NEXT:    (ref.null nofunc)
+  ;; CHECK-NEXT:    (ref.null nofunc)
+  ;; CHECK-NEXT:    (ref.null nofunc)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null nofunc)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 2
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null nofunc)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $temp
   ;; CHECK-NEXT:   (local.get $x)
@@ -108,7 +108,7 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (ref.null $struct)
+  ;; CHECK-NEXT:  (ref.null none)
   ;; CHECK-NEXT: )
   (func $foo (result (ref null $struct))
     ;; Use a tag so that we test proper updating of its type after making
@@ -153,17 +153,17 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 2
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
+  (func $field-keepalive (param $struct (ref null $struct))
     ;; --gto will remove fields that are not read from, so add reads to any
     ;; that don't already have them.
-    (drop (struct.get $struct 2 (ref.null $struct)))
+    (drop (struct.get $struct 2 (local.get $struct)))
   )
 )
 
@@ -178,12 +178,12 @@
 
   ;; CHECK:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$A|_ref?|$B|_=>_none (func_subtype (param (ref null $A) (ref null $B)) func))
 
   ;; CHECK:      (func $func (type $ref|$A|_=>_none) (param $x (ref $A))
   ;; CHECK-NEXT:  (struct.set $A 0
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (ref.null $B)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $A 1
   ;; CHECK-NEXT:   (local.get $x)
@@ -201,33 +201,33 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $A 0
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (local.get $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $A 1
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (local.get $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $B 0
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $B 1
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $A 0 (ref.null $A)))
-    (drop (struct.get $A 1 (ref.null $A)))
-    (drop (struct.get $B 0 (ref.null $B)))
-    (drop (struct.get $B 1 (ref.null $B)))
+  (func $field-keepalive (param $A (ref null $A)) (param $B (ref null $B))
+    (drop (struct.get $A 0 (local.get $A)))
+    (drop (struct.get $A 1 (local.get $A)))
+    (drop (struct.get $B 0 (local.get $B)))
+    (drop (struct.get $B 1 (local.get $B)))
   )
 )
 
@@ -242,12 +242,12 @@
 
   ;; CHECK:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$A|_ref?|$B|_=>_none (func_subtype (param (ref null $A) (ref null $B)) func))
 
   ;; CHECK:      (func $func (type $ref|$B|_=>_none) (param $x (ref $B))
   ;; CHECK-NEXT:  (struct.set $B 0
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $B 1
   ;; CHECK-NEXT:   (local.get $x)
@@ -265,53 +265,54 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $A 0
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (local.get $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $A 1
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (local.get $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $B 0
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $B 1
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $A 0 (ref.null $A)))
-    (drop (struct.get $A 1 (ref.null $A)))
-    (drop (struct.get $B 0 (ref.null $B)))
-    (drop (struct.get $B 1 (ref.null $B)))
+  (func $field-keepalive (param $A (ref null $A)) (param $B (ref null $B))
+    (drop (struct.get $A 0 (local.get $A)))
+    (drop (struct.get $A 1 (local.get $A)))
+    (drop (struct.get $B 0 (local.get $B)))
+    (drop (struct.get $B 1 (local.get $B)))
   )
 )
 
 (module
   ;; As before, but now one field in each can become immutable.
 
+  ;; CHECK:      (type $A (struct_subtype (field (mut (ref null $B))) (field i32) data))
+
   ;; CHECK:      (type $B (struct_subtype (field (ref null $A)) (field (mut f64)) data))
   (type $B (struct (field (mut (ref null $A))) (field (mut f64)) ))
 
-  ;; CHECK:      (type $A (struct_subtype (field (mut (ref null $B))) (field i32) data))
   (type $A (struct (field (mut (ref null $B))) (field (mut i32)) ))
 
   ;; CHECK:      (type $ref|$A|_ref|$B|_=>_none (func_subtype (param (ref $A) (ref $B)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$A|_ref?|$B|_=>_none (func_subtype (param (ref null $A) (ref null $B)) func))
 
   ;; CHECK:      (func $func (type $ref|$A|_ref|$B|_=>_none) (param $x (ref $A)) (param $y (ref $B))
   ;; CHECK-NEXT:  (struct.set $A 0
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (ref.null $B)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $B 1
   ;; CHECK-NEXT:   (local.get $y)
@@ -329,33 +330,33 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $A 0
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (local.get $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $A 1
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (local.get $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $B 0
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $B 1
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $A 0 (ref.null $A)))
-    (drop (struct.get $A 1 (ref.null $A)))
-    (drop (struct.get $B 0 (ref.null $B)))
-    (drop (struct.get $B 1 (ref.null $B)))
+  (func $field-keepalive (param $A (ref null $A)) (param $B (ref null $B))
+    (drop (struct.get $A 0 (local.get $A)))
+    (drop (struct.get $A 1 (local.get $A)))
+    (drop (struct.get $B 0 (local.get $B)))
+    (drop (struct.get $B 1 (local.get $B)))
   )
 )
 
@@ -369,7 +370,7 @@
 
   ;; CHECK:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func))
 
   ;; CHECK:      (func $func (type $ref|$struct|_=>_none) (param $x (ref $struct))
   ;; CHECK-NEXT:  (struct.set $struct 2
@@ -384,27 +385,27 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 1
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 2
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $struct 0 (ref.null $struct)))
-    (drop (struct.get $struct 1 (ref.null $struct)))
-    (drop (struct.get $struct 2 (ref.null $struct)))
+  (func $field-keepalive (param $struct (ref null $struct))
+    (drop (struct.get $struct 0 (local.get $struct)))
+    (drop (struct.get $struct 1 (local.get $struct)))
+    (drop (struct.get $struct 2 (local.get $struct)))
   )
 )
 
@@ -419,6 +420,8 @@
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
+  ;; CHECK:      (type $ref?|$super|_ref?|$sub|_=>_none (func_subtype (param (ref null $super) (ref null $sub)) func))
+
   ;; CHECK:      (func $func (type $none_=>_none)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $super
@@ -445,21 +448,21 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$super|_ref?|$sub|_=>_none) (param $super (ref null $super)) (param $sub (ref null $sub))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $super 0
-  ;; CHECK-NEXT:    (ref.null $super)
+  ;; CHECK-NEXT:    (local.get $super)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $sub 0
-  ;; CHECK-NEXT:    (ref.null $sub)
+  ;; CHECK-NEXT:    (local.get $sub)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $super 0 (ref.null $super)))
-    (drop (struct.get $sub 0 (ref.null $sub)))
+  (func $field-keepalive (param $super (ref null $super)) (param $sub (ref null $sub))
+    (drop (struct.get $super 0 (local.get $super)))
+    (drop (struct.get $sub 0 (local.get $sub)))
   )
 )
 
@@ -473,7 +476,7 @@
 
   ;; CHECK:      (type $ref|$super|_=>_none (func_subtype (param (ref $super)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$super|_ref?|$sub|_=>_none (func_subtype (param (ref null $super) (ref null $sub)) func))
 
   ;; CHECK:      (func $func (type $ref|$super|_=>_none) (param $x (ref $super))
   ;; CHECK-NEXT:  (drop
@@ -509,21 +512,21 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$super|_ref?|$sub|_=>_none) (param $super (ref null $super)) (param $sub (ref null $sub))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $super 0
-  ;; CHECK-NEXT:    (ref.null $super)
+  ;; CHECK-NEXT:    (local.get $super)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $sub 0
-  ;; CHECK-NEXT:    (ref.null $sub)
+  ;; CHECK-NEXT:    (local.get $sub)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $super 0 (ref.null $super)))
-    (drop (struct.get $sub 0 (ref.null $sub)))
+  (func $field-keepalive (param $super (ref null $super)) (param $sub (ref null $sub))
+    (drop (struct.get $super 0 (local.get $super)))
+    (drop (struct.get $sub 0 (local.get $sub)))
   )
 )
 
@@ -538,7 +541,7 @@
 
   ;; CHECK:      (type $ref|$sub|_=>_none (func_subtype (param (ref $sub)) func))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$super|_ref?|$sub|_=>_none (func_subtype (param (ref null $super) (ref null $sub)) func))
 
   ;; CHECK:      (func $func (type $ref|$sub|_=>_none) (param $x (ref $sub))
   ;; CHECK-NEXT:  (struct.set $sub 0
@@ -553,20 +556,20 @@
     )
   )
 
-  ;; CHECK:      (func $field-keepalive (type $none_=>_none)
+  ;; CHECK:      (func $field-keepalive (type $ref?|$super|_ref?|$sub|_=>_none) (param $super (ref null $super)) (param $sub (ref null $sub))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $super 0
-  ;; CHECK-NEXT:    (ref.null $super)
+  ;; CHECK-NEXT:    (local.get $super)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $sub 0
-  ;; CHECK-NEXT:    (ref.null $sub)
+  ;; CHECK-NEXT:    (local.get $sub)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $field-keepalive
-    (drop (struct.get $super 0 (ref.null $super)))
-    (drop (struct.get $sub 0 (ref.null $sub)))
+  (func $field-keepalive (param $super (ref null $super)) (param $sub (ref null $sub))
+    (drop (struct.get $super 0 (local.get $super)))
+    (drop (struct.get $sub 0 (local.get $sub)))
   )
 )
diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast
index f3cbae2..184359f 100644
--- a/test/lit/passes/gto-removals.wast
+++ b/test/lit/passes/gto-removals.wast
@@ -22,20 +22,22 @@
 (module
   ;; A write does not keep a field from being removed.
 
-  ;; CHECK:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
-
   ;; CHECK:      (type $struct (struct_subtype  data))
   (type $struct (struct_subtype (field (mut funcref)) data))
 
+  ;; CHECK:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
   ;; CHECK:      (func $func (type $ref|$struct|_=>_none) (param $x (ref $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (block (result (ref $struct))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (ref.null nofunc)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.null func)
-  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $func (param $x (ref $struct))
     ;; The fields of this set will be dropped, as we do not need to perform
@@ -140,15 +142,15 @@
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (block
-  ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (block (result (ref $mut-struct))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $mut-struct $rw
   ;; CHECK-NEXT:   (local.get $x)
@@ -164,15 +166,15 @@
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (block
-  ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (block (result (ref $mut-struct))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (i32.const 2)
+  ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (i32.const 2)
-  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $mut-struct $rw-2
   ;; CHECK-NEXT:   (local.get $x)
@@ -397,12 +399,14 @@
 (module
   ;; A new with side effects
 
-  ;; CHECK:      (type $struct (struct_subtype (field i32) (field (rtt $struct)) data))
-  (type $struct (struct i32 f64 (ref any) (rtt $struct)))
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32 f64 (ref any)))
 
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
+  ;; CHECK:      (type $ref|any|_ref?|$struct|_=>_none (func_subtype (param (ref any) (ref null $struct)) func))
+
   ;; CHECK:      (type $ref|any|_=>_none (func_subtype (param (ref any)) func))
 
   ;; CHECK:      (type $i32_=>_i32 (func_subtype (param i32) (result i32) func))
@@ -417,28 +421,18 @@
   ;; CHECK:      (global $mut-i32 (mut i32) (i32.const 5678))
   (global $mut-i32 (mut i32) (i32.const 5678))
 
-  ;; CHECK:      (func $gets (type $ref|any|_=>_none) (param $x (ref any))
+  ;; CHECK:      (func $gets (type $ref|any|_ref?|$struct|_=>_none) (param $x (ref any)) (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 0
-  ;; CHECK-NEXT:    (ref.null $struct)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.get $struct 1
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $gets (param $x (ref any))
+  (func $gets (param $x (ref any)) (param $struct (ref null $struct))
     ;; Gets to keep certain fields alive.
     (drop
       (struct.get $struct 0
-        (ref.null $struct)
-      )
-    )
-    (drop
-      (struct.get $struct 3
-        (ref.null $struct)
+        (local.get $struct)
       )
     )
   )
@@ -446,7 +440,7 @@
   ;; CHECK:      (func $new-side-effect (type $none_=>_none)
   ;; CHECK-NEXT:  (local $0 i32)
   ;; CHECK-NEXT:  (local $1 f64)
-  ;; CHECK-NEXT:  (local $2 anyref)
+  ;; CHECK-NEXT:  (local $2 (ref any))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref $struct))
   ;; CHECK-NEXT:    (local.set $0
@@ -466,31 +460,27 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (struct.new $struct
   ;; CHECK-NEXT:     (local.get $0)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $new-side-effect
     ;; The 2nd&3rd fields here will be removed, since those fields have no
-    ;; reads. They has side effects, though, so the operands will be saved in
-    ;; locals. Note that we can't save the rtt.canon in locals, but it has
-    ;; no effects, and we leave such arguments as they are.
-    ;; Note also that one of the fields is non-nullable, and we need to use a
+    ;; reads. They have side effects, though, so the operands will be saved in
+    ;; locals. Note that one of the fields is non-nullable, and we need to use a
     ;; nullable local for it.
     (drop
       (struct.new $struct
         (call $helper0 (i32.const 0))
         (call $helper1 (i32.const 1))
         (call $helper2 (i32.const 2))
-        (rtt.canon $struct)
       )
     )
   )
 
   ;; CHECK:      (func $new-side-effect-global-imm (type $none_=>_none)
   ;; CHECK-NEXT:  (local $0 f64)
-  ;; CHECK-NEXT:  (local $1 anyref)
+  ;; CHECK-NEXT:  (local $1 (ref any))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref $struct))
   ;; CHECK-NEXT:    (local.set $0
@@ -505,7 +495,6 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (struct.new $struct
   ;; CHECK-NEXT:     (global.get $imm-i32)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -520,7 +509,6 @@
         (global.get $imm-i32)
         (call $helper1 (i32.const 0))
         (call $helper2 (i32.const 1))
-        (rtt.canon $struct)
       )
     )
   )
@@ -528,7 +516,7 @@
   ;; CHECK:      (func $new-side-effect-global-mut (type $none_=>_none)
   ;; CHECK-NEXT:  (local $0 i32)
   ;; CHECK-NEXT:  (local $1 f64)
-  ;; CHECK-NEXT:  (local $2 anyref)
+  ;; CHECK-NEXT:  (local $2 (ref any))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref $struct))
   ;; CHECK-NEXT:    (local.set $0
@@ -546,7 +534,6 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (struct.new $struct
   ;; CHECK-NEXT:     (local.get $0)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -559,7 +546,6 @@
         (global.get $mut-i32)
         (call $helper1 (i32.const 0))
         (call $helper2 (i32.const 1))
-        (rtt.canon $struct)
       )
     )
   )
@@ -578,9 +564,7 @@
   ;; CHECK-NEXT:      (i32.const 3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -592,7 +576,6 @@
         (i32.const 2)
         (unreachable)
         (call $helper2 (i32.const 3))
-        (rtt.canon $struct)
       )
     )
   )
@@ -603,7 +586,6 @@
   ;; CHECK-NEXT:    (call $helper0
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -615,7 +597,6 @@
         (call $helper0 (i32.const 0))
         (f64.const 3.14159)
         (local.get $any)
-        (rtt.canon $struct)
       )
     )
   )
@@ -798,3 +779,134 @@
     (drop (struct.get $child1 0 (local.get $child1)))
   )
 )
+
+(module
+  ;; CHECK:      (type ${mut:i8} (struct_subtype  data))
+  (type ${mut:i8} (struct_subtype (field (mut i8)) data))
+
+  ;; CHECK:      (type $ref?|${mut:i8}|_=>_none (func_subtype (param (ref null ${mut:i8})) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|${mut:i8}| (func_subtype (result (ref ${mut:i8})) func))
+
+  ;; CHECK:      (func $unreachable-set (type $ref?|${mut:i8}|_=>_none) (param ${mut:i8} (ref null ${mut:i8}))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (block (result (ref null ${mut:i8}))
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $helper-i32)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get ${mut:i8})
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unreachable-set (param ${mut:i8} (ref null ${mut:i8}))
+    ;; The struct type has no reads, so we want to remove all of the sets of it.
+    ;; This struct.set will trap on null, but first the call must run. When we
+    ;; optimize here we should be careful to not emit something with different
+    ;; ordering (naively emitting ref.as_non_null on the reference would trap
+    ;; before the call, so we must reorder).
+    (struct.set ${mut:i8} 0
+      (local.get ${mut:i8})
+      (call $helper-i32)
+    )
+  )
+
+  ;; CHECK:      (func $unreachable-set-2 (type $ref?|${mut:i8}|_=>_none) (param ${mut:i8} (ref null ${mut:i8}))
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (local.get ${mut:i8})
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (br $block)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unreachable-set-2 (param ${mut:i8} (ref null ${mut:i8}))
+    ;; As above, but the side effects now are a br. Again, the br must happen
+    ;; before the trap (in fact, the br will skip the trap here).
+    (block
+      (struct.set ${mut:i8} 0
+        (local.get ${mut:i8})
+        (br $block)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $unreachable-set-2b (type $ref?|${mut:i8}|_=>_none) (param ${mut:i8} (ref null ${mut:i8}))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (local.get ${mut:i8})
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unreachable-set-2b (param ${mut:i8} (ref null ${mut:i8}))
+    ;; As above, but with an unreachable instead of a br. We add a nop here so
+    ;; that we are inside of a block, and then validation would fail if we do
+    ;; not keep the type of the replacement for the struct.set identical to the
+    ;; struct.set. That is, the type must remain unreachable.
+    (nop)
+    (struct.set ${mut:i8} 0
+      (local.get ${mut:i8})
+      (unreachable)
+    )
+  )
+
+  ;; CHECK:      (func $unreachable-set-3 (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $0 (ref ${mut:i8}))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (block (result (ref ${mut:i8}))
+  ;; CHECK-NEXT:     (local.set $0
+  ;; CHECK-NEXT:      (call $helper-ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $helper-i32)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unreachable-set-3
+    ;; As above, but now we have side effects in both children.
+    (block
+      (struct.set ${mut:i8} 0
+        (call $helper-ref)
+        (call $helper-i32)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $helper-i32 (type $none_=>_i32) (result i32)
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: )
+  (func $helper-i32 (result i32)
+    (i32.const 1)
+  )
+
+  ;; CHECK:      (func $helper-ref (type $none_=>_ref|${mut:i8}|) (result (ref ${mut:i8}))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $helper-ref (result (ref ${mut:i8}))
+    (unreachable)
+  )
+)
diff --git a/test/lit/passes/gufa-extern.wast b/test/lit/passes/gufa-extern.wast
new file mode 100644
index 0000000..4a626b9
--- /dev/null
+++ b/test/lit/passes/gufa-extern.wast
@@ -0,0 +1,70 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --gufa --nominal -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $externref_anyref_=>_none (func_subtype (param externref anyref) func))
+
+  ;; CHECK:      (export "externals" (func $externals))
+
+  ;; CHECK:      (func $externals (type $externref_anyref_=>_none) (param $ext externref) (param $any anyref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_data
+  ;; CHECK-NEXT:    (extern.internalize
+  ;; CHECK-NEXT:     (local.get $ext)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (extern.externalize
+  ;; CHECK-NEXT:    (local.get $any)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $externals (export "externals") (param $ext externref) (param $any anyref)
+    ;; We must not turn these into unreachable code, as the function is
+    ;; exported.
+    (drop
+      (ref.as_data
+        (extern.internalize
+          (local.get $ext)
+        )
+      )
+    )
+    (drop
+      (extern.externalize
+        (local.get $any)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $non-exported (type $externref_anyref_=>_none) (param $ext externref) (param $any anyref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_data
+  ;; CHECK-NEXT:    (extern.internalize
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (extern.externalize
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $non-exported (param $ext externref) (param $any anyref)
+    ;; This is not exported, so the params are dead code, and can be turned
+    ;; unreachable.
+    (drop
+      (ref.as_data
+        (extern.internalize
+          (local.get $ext)
+        )
+      )
+    )
+    (drop
+      (extern.externalize
+        (local.get $any)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/gufa-optimizing.wast b/test/lit/passes/gufa-optimizing.wast
new file mode 100644
index 0000000..7ccd6ae
--- /dev/null
+++ b/test/lit/passes/gufa-optimizing.wast
@@ -0,0 +1,62 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --gufa            -S -o - | filecheck %s --check-prefix NO_OPT
+;; RUN: foreach %s %t wasm-opt -all --gufa-optimizing -S -o - | filecheck %s --check-prefix DO_OPT
+
+;; Compare the results of gufa and gufa-optimizing. The optimizing variant will
+;; remove unneeded extra code that gufa might introduce, like dropped unneeded
+;; things.
+
+(module
+  ;; NO_OPT:      (type $none_=>_i32 (func (result i32)))
+
+  ;; NO_OPT:      (func $foo (result i32)
+  ;; NO_OPT-NEXT:  (i32.const 1)
+  ;; NO_OPT-NEXT: )
+  ;; DO_OPT:      (type $none_=>_i32 (func (result i32)))
+
+  ;; DO_OPT:      (func $foo (result i32)
+  ;; DO_OPT-NEXT:  (i32.const 1)
+  ;; DO_OPT-NEXT: )
+  (func $foo (result i32)
+    ;; Helper function.
+    (i32.const 1)
+  )
+
+  ;; NO_OPT:      (func $bar (result i32)
+  ;; NO_OPT-NEXT:  (drop
+  ;; NO_OPT-NEXT:   (block $out (result i32)
+  ;; NO_OPT-NEXT:    (block (result i32)
+  ;; NO_OPT-NEXT:     (drop
+  ;; NO_OPT-NEXT:      (block $in (result i32)
+  ;; NO_OPT-NEXT:       (block (result i32)
+  ;; NO_OPT-NEXT:        (drop
+  ;; NO_OPT-NEXT:         (call $foo)
+  ;; NO_OPT-NEXT:        )
+  ;; NO_OPT-NEXT:        (i32.const 1)
+  ;; NO_OPT-NEXT:       )
+  ;; NO_OPT-NEXT:      )
+  ;; NO_OPT-NEXT:     )
+  ;; NO_OPT-NEXT:     (i32.const 1)
+  ;; NO_OPT-NEXT:    )
+  ;; NO_OPT-NEXT:   )
+  ;; NO_OPT-NEXT:  )
+  ;; NO_OPT-NEXT:  (i32.const 1)
+  ;; NO_OPT-NEXT: )
+  ;; DO_OPT:      (func $bar (result i32)
+  ;; DO_OPT-NEXT:  (drop
+  ;; DO_OPT-NEXT:   (call $foo)
+  ;; DO_OPT-NEXT:  )
+  ;; DO_OPT-NEXT:  (i32.const 1)
+  ;; DO_OPT-NEXT: )
+  (func $bar (result i32)
+    ;; GUFA infers a constant value for each block here, adding multiple
+    ;; constants of 1 and dropped earlier values. The optimizing variant of this
+    ;; pass will avoid all that and just emit minimal code here (a drop of the
+    ;; call followed by the value we inferred for it, 1).
+    (block $out (result i32)
+      (block $in (result i32)
+        (call $foo)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast
new file mode 100644
index 0000000..310bef5
--- /dev/null
+++ b/test/lit/passes/gufa-refs.wast
@@ -0,0 +1,5531 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --gufa --nominal -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|any| (func_subtype (result (ref any)) func))
+
+  ;; CHECK:      (type $none_=>_funcref (func_subtype (result funcref) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $no-non-null (type $none_=>_ref|any|) (result (ref any))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $no-non-null (result (ref any))
+    ;; The only possible value at the location of this ref.as_non_null is a
+    ;; null - but that value does not have a compatible type (null is nullable).
+    ;; Therefore we can infer that nothing is possible here, and the code must
+    ;; trap, and we'll optimize this to an unreachable.
+    (ref.as_non_null
+      (ref.null any)
+    )
+  )
+
+  ;; CHECK:      (func $nested (type $none_=>_i32) (result i32)
+  ;; CHECK-NEXT:  (ref.is_null
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (loop $loop
+  ;; CHECK-NEXT:      (block
+  ;; CHECK-NEXT:       (unreachable)
+  ;; CHECK-NEXT:       (unreachable)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $nested (result i32)
+    ;; As above, but add other instructions on the outside, which can also be
+    ;; replaced with an unreachable (except for the loop, which as a control
+    ;; flow structure with a name we keep it around and just add an unreachable
+    ;; after it; and for now we don't optimize ref.is* so that stays).
+    (ref.is_null
+      (loop $loop (result (ref func))
+        (nop)
+        (ref.as_func
+          (ref.as_non_null
+            (ref.null any)
+          )
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $yes-non-null (type $none_=>_ref|any|) (result (ref any))
+  ;; CHECK-NEXT:  (ref.as_non_null
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $yes-non-null (result (ref any))
+    ;; Similar to the above but now there *is* an non-null value here, so there
+    ;; is nothing for us to optimize or change here.
+    (ref.as_non_null
+      (struct.new $struct)
+    )
+  )
+
+  ;; CHECK:      (func $breaks (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block $block (result (ref $struct))
+  ;; CHECK-NEXT:    (br $block
+  ;; CHECK-NEXT:     (struct.new_default $struct)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $breaks
+    ;; Check that we notice values sent along breaks. We should optimize
+    ;; nothing in the first block here.
+    (drop
+      (block $block (result (ref any))
+        (br $block
+          (struct.new $struct)
+        )
+      )
+    )
+    ;; But here we send a null so we can optimize to an unreachable.
+    (drop
+      (ref.as_non_null
+        (block $block2 (result (ref null any))
+          (br $block2
+            (ref.null $struct)
+          )
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-nothing (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $get-nothing (result (ref $struct))
+    ;; This function returns a non-nullable struct by type, but does not
+    ;; actually return a value in practice, and our whole-program analysis
+    ;; should pick that up in optimizing the callers (but nothing changes here).
+    (unreachable)
+  )
+
+  ;; CHECK:      (func $get-nothing-calls (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $get-nothing)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $get-nothing)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.is_null
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $get-nothing)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-nothing-calls
+    ;; This can be optimized since the call does not actually return any
+    ;; possible content (it has an unreachable), which means we can optimize
+    ;; away the call's value - we must keep it around in a drop, since it can
+    ;; have side effects, but the drop ignores the value which we do not need.
+    (drop
+      (call $get-nothing)
+    )
+    ;; As above, add another instruction in the middle. We can optimize it to
+    ;; an unreachable, like the call.
+    (drop
+      (ref.as_non_null
+        (call $get-nothing)
+      )
+    )
+    ;; As above, but we do not optimize ref.is_null yet so nothing happens for
+    ;; it (but the call still gets optimized as before).
+    (drop
+      (ref.is_null
+        (call $get-nothing)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $two-inputs (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $get-nothing)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (call $import)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $get-nothing)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:    (call $import)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select (result (ref $struct))
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:    (call $import)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $get-nothing)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $get-nothing)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $two-inputs
+    ;; As above, but now the outer instruction is a select, and some of the arms
+    ;; may have a possible type - we check all 4 permutations. Only in the
+    ;; case where both inputs are nothing can we optimize away the select (that
+    ;; is, drop it and ignore its value), as only then will the select never
+    ;; have any contents.
+    ;; (Note: we are not fully optimal here since we could notice that the
+    ;; select executes both arms unconditionally, so if one traps then it will
+    ;; all trap.)
+    (drop
+      (select (result (ref any))
+        (struct.new $struct)
+        (call $get-nothing)
+        (call $import)
+      )
+    )
+    (drop
+      (select (result (ref any))
+        (call $get-nothing)
+        (struct.new $struct)
+        (call $import)
+      )
+    )
+    (drop
+      (select (result (ref any))
+        (struct.new $struct)
+        (struct.new $struct)
+        (call $import)
+      )
+    )
+    (drop
+      (select (result (ref any))
+        (call $get-nothing)
+        (call $get-nothing)
+        (call $import)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-something-flow (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new_default $struct)
+  ;; CHECK-NEXT: )
+  (func $get-something-flow (result (ref $struct))
+    ;; Return a value by flowing it out. Helper for later code.
+    (struct.new $struct)
+  )
+
+  ;; CHECK:      (func $get-something-return (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (return
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-something-return (result (ref $struct))
+    ;; Return a value using an explicit return. Helper for later code.
+    (return
+      (struct.new $struct)
+    )
+  )
+
+  ;; CHECK:      (func $call-get-something (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $get-something-flow)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $get-something-return)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-get-something
+    ;; In both of these cases a value is actually returned and there is nothing
+    ;; to optimize, unlike get-nothing from above.
+    (drop
+      (call $get-something-flow)
+    )
+    (drop
+      (call $get-something-return)
+    )
+  )
+
+  ;; CHECK:      (func $locals (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $x anyref)
+  ;; CHECK-NEXT:  (local $y anyref)
+  ;; CHECK-NEXT:  (local $z anyref)
+  ;; CHECK-NEXT:  (local.tee $x
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $get-nothing)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $z
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $locals
+    (local $x (ref null any))
+    (local $y (ref null any))
+    (local $z (ref null any))
+    ;; Assign to x from a call that actually will not return anything. We will
+    ;; be able to optimize away the call's return value (drop it) and append an
+    ;; unreachable.
+    (local.set $x
+      (call $get-nothing)
+    )
+    ;; Never assign to y.
+    ;; Assign to z an actual value.
+    (local.set $z
+      (struct.new $struct)
+    )
+    ;; Get the 3 locals, to check that we optimize. We can replace x with an
+    ;; unreachable and y with a null constant.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    (drop
+      (local.get $z)
+    )
+  )
+
+  ;; CHECK:      (func $nondeterminism (type $none_=>_funcref) (result funcref)
+  ;; CHECK-NEXT:  (block $label$1 (result funcref)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (br_on_func $label$1
+  ;; CHECK-NEXT:     (i31.new
+  ;; CHECK-NEXT:      (i32.const 1337)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (ref.null nofunc)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $nondeterminism (result funcref)
+    ;; This block is sent an i31 and a null. The null is compatible with the
+    ;; type and the i31 is not. The order in which we process this matters:
+    ;;
+    ;;  * If the i31 arrives first, we'll filter it out as incompatible with the
+    ;;    type of the block. Then the null arrives and the final result is that
+    ;;    null, which we can then optimize the block to return.
+    ;;  * Or, if the null arrives first, then when the i31 arrives the
+    ;;    combination of nullfunc + i31 is Many (since the types are
+    ;;    incompatible). We then filter that to the block's type, ending up with
+    ;;    a cone of funcref. We cannot optimize in that case, unlike before.
+    ;;
+    ;; Ideally we'd optimize here, but atm we do not since the order in
+    ;; practice is a less ideal one. At minimum we should be deterministic in
+    ;; how we handle this, which this test enforces at least.
+    ;;
+    ;; TODO: Find a way to actually optimize such cases, perhaps by filtering
+    ;;       when sending as well and not just when receiving (the br_on_func
+    ;;       here should not send anything, as what it sends should be first
+    ;;       intersected with funcref).
+    (block $label$1 (result funcref)
+      (drop
+        (br_on_func $label$1
+          (i31.new
+            (i32.const 1337)
+          )
+        )
+      )
+      (ref.null nofunc)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $null anyref (ref.null none))
+  (global $null (ref null any) (ref.null any))
+  ;; CHECK:      (global $something anyref (struct.new_default $struct))
+  (global $something (ref null any) (struct.new $struct))
+
+  ;; CHECK:      (global $mut-null (mut anyref) (ref.null none))
+  (global $mut-null (mut (ref null any)) (ref.null any))
+  ;; CHECK:      (global $mut-something (mut anyref) (ref.null none))
+  (global $mut-something (mut (ref null any)) (ref.null any))
+
+  ;; CHECK:      (func $read-globals (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (global.get $something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (global.get $mut-something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $read-globals
+    ;; This global has no possible contents aside from a null, which we can
+    ;; infer and place here.
+    (drop
+      (global.get $null)
+    )
+    ;; This global has no possible contents aside from a null, so the
+    ;; ref.as_non_null can be optimized to an unreachable (since a null is not
+    ;; compatible with its non-nullable type).
+    (drop
+      (ref.as_non_null
+        (global.get $null)
+      )
+    )
+    ;; This global has a possible non-null value (in the initializer), so there
+    ;; is nothing to do.
+    (drop
+      (ref.as_non_null
+        (global.get $something)
+      )
+    )
+    ;; This mutable global has a write aside from the initializer, but it is
+    ;; also of a null, so we can optimize here.
+    (drop
+      (ref.as_non_null
+        (global.get $mut-null)
+      )
+    )
+    ;; This one also has a later write, of a non-null value, so there is nothing
+    ;; to do.
+    (drop
+      (ref.as_non_null
+        (global.get $mut-something)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $write-globals (type $none_=>_none)
+  ;; CHECK-NEXT:  (global.set $mut-null
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $mut-something
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $write-globals
+    (global.set $mut-null
+      (ref.null $struct)
+    )
+    (global.set $mut-something
+      (struct.new $struct)
+    )
+  )
+)
+
+;; As above, but now with a chain of globals: A starts with a value, which is
+;; copied to B, and then C, and then C is read. We will be able to optimize
+;; away *-null (which is where A-null starts with null) but not *-something
+;; (which is where A-something starts with a value).
+(module
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  ;; CHECK:      (global $A-null anyref (ref.null none))
+  (global $A-null (ref null any) (ref.null any))
+  ;; CHECK:      (global $A-something anyref (struct.new_default $struct))
+  (global $A-something (ref null any) (struct.new $struct))
+
+  ;; CHECK:      (global $B-null (mut anyref) (ref.null none))
+  (global $B-null (mut (ref null any)) (ref.null any))
+  ;; CHECK:      (global $B-something (mut anyref) (ref.null none))
+  (global $B-something (mut (ref null any)) (ref.null any))
+
+  ;; CHECK:      (global $C-null (mut anyref) (ref.null none))
+  (global $C-null (mut (ref null any)) (ref.null any))
+  ;; CHECK:      (global $C-something (mut anyref) (ref.null none))
+  (global $C-something (mut (ref null any)) (ref.null any))
+
+  ;; CHECK:      (func $read-globals (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (global.get $A-something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (global.get $B-something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (global.get $C-something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $read-globals
+    (drop
+      (ref.as_non_null
+        (global.get $A-null)
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $A-something)
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $B-null)
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $B-something)
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $C-null)
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $C-something)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $write-globals (type $none_=>_none)
+  ;; CHECK-NEXT:  (global.set $B-null
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $C-null
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $B-something
+  ;; CHECK-NEXT:   (global.get $A-something)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $C-something
+  ;; CHECK-NEXT:   (global.get $B-something)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $write-globals
+    (global.set $B-null
+      (global.get $A-null)
+    )
+    (global.set $C-null
+      (global.get $B-null)
+    )
+    (global.set $B-something
+      (global.get $A-something)
+    )
+    (global.set $C-something
+      (global.get $B-something)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $ref|any|_=>_ref|any| (func_subtype (param (ref any)) (result (ref any)) func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  ;; CHECK:      (type $i32_=>_i32 (func_subtype (param i32) (result i32) func))
+
+  ;; CHECK:      (type $ref|any|_ref|any|_ref|any|_=>_none (func_subtype (param (ref any) (ref any) (ref any)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $never-called (type $i32_=>_i32) (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $never-called (param $x i32) (result i32)
+    ;; This function is never called, so the parameter has no possible contents,
+    ;; and we can optimize to an unreachable.
+    (local.get $x)
+  )
+
+  ;; CHECK:      (func $never-called-ref (type $ref|any|_=>_ref|any|) (param $x (ref any)) (result (ref any))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $never-called-ref (param $x (ref any)) (result (ref any))
+    ;; As above but with a reference type. Again, we can apply an unreachable.
+    (local.get $x)
+  )
+
+  ;; CHECK:      (func $recursion (type $ref|any|_=>_ref|any|) (param $x (ref any)) (result (ref any))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $recursion
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $recursion (param $x (ref any)) (result (ref any))
+    ;; This function calls itself recursively. That forms a loop, but still,
+    ;; nothing reaches here, so we can optimize to an unreachable (we cannot
+    ;; remove the call though, as it has effects, so we drop it).
+    (call $recursion
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $called (type $ref|any|_ref|any|_ref|any|_=>_none) (param $x (ref any)) (param $y (ref any)) (param $z (ref any))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $called (param $x (ref any)) (param $y (ref any)) (param $z (ref any))
+    ;; This function is called, with possible (non-null) values in the 1st & 3rd
+    ;; params, but nothing can arrive in the 2nd, which we can optimize to an
+    ;; unreachable.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    (drop
+      (local.get $z)
+    )
+  )
+
+  ;; CHECK:      (func $call-called (type $none_=>_none)
+  ;; CHECK-NEXT:  (call $called
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $called
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-called
+    ;; Call the above function as described there: Nothing can arrive in the
+    ;; second param (since we cast a null to non-null there), while the others
+    ;; have both a null and a non-null (different in the 2 calls here). (With
+    ;; more precise analysis we could see that the ref.as must trap, and we
+    ;; could optimize even more here.)
+    (call $called
+      (struct.new $struct)
+      (ref.as_non_null
+        (ref.null any)
+      )
+      (ref.as_non_null
+        (ref.null any)
+      )
+    )
+    (call $called
+      (ref.as_non_null
+        (ref.null any)
+      )
+      (ref.as_non_null
+        (ref.null any)
+      )
+      (struct.new $struct)
+    )
+  )
+)
+
+;; As above, but using indirect calls.
+(module
+  ;; CHECK:      (type $struct (struct_subtype  data))
+
+  ;; CHECK:      (type $two-params (func_subtype (param (ref $struct) (ref $struct)) func))
+  (type $two-params (func (param (ref $struct)) (param (ref $struct))))
+
+  ;; CHECK:      (type $three-params (func_subtype (param (ref $struct) (ref $struct) (ref $struct)) func))
+  (type $three-params (func (param (ref $struct)) (param (ref $struct)) (param (ref $struct))))
+
+  (type $struct (struct))
+
+  (table 10 funcref)
+
+  (elem (i32.const 0) funcref
+    (ref.func $func-2params-a)
+    (ref.func $func-2params-b)
+    (ref.func $func-3params)
+  )
+
+  ;; CHECK:      (table $0 10 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $func-2params-a $func-2params-b $func-3params)
+
+  ;; CHECK:      (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $two-params)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
+    ;; Only null is possible for the first, so we can optimize it to an
+    ;; unreachable.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    ;; Send a value only to the second param.
+    (call_indirect (type $two-params)
+      (ref.as_non_null
+        (ref.null $struct)
+      )
+      (struct.new $struct)
+      (i32.const 0)
+    )
+  )
+
+  ;; CHECK:      (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
+    ;; Another function with the same signature as before, which we should
+    ;; optimize in the same way: the indirect call can go to either.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+  )
+
+  ;; CHECK:      (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $three-params)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $three-params)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct))
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    (drop
+      (local.get $z)
+    )
+    ;; Send a non-null value only to the first and third param. Do so in two
+    ;; separate calls. The second param, $y, can be optimized.
+    (call_indirect (type $three-params)
+      (struct.new $struct)
+      (ref.as_non_null
+        (ref.null $struct)
+      )
+      (ref.as_non_null
+        (ref.null $struct)
+      )
+      (i32.const 0)
+    )
+    (call_indirect (type $three-params)
+      (ref.as_non_null
+        (ref.null $struct)
+      )
+      (ref.as_non_null
+        (ref.null $struct)
+      )
+      (struct.new $struct)
+      (i32.const 0)
+    )
+  )
+)
+
+;; As above, but using call_ref.
+(module
+  ;; CHECK:      (type $struct (struct_subtype  data))
+
+  ;; CHECK:      (type $two-params (func_subtype (param (ref $struct) (ref $struct)) func))
+  (type $two-params (func (param (ref $struct)) (param (ref $struct))))
+
+  (type $struct (struct))
+
+  ;; CHECK:      (elem declare func $func-2params-a)
+
+  ;; CHECK:      (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_ref $two-params
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:   (ref.func $func-2params-a)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    ;; Send a non-null value only to the second param.
+    (call_ref $two-params
+      (ref.as_non_null
+        (ref.null $struct)
+      )
+      (struct.new $struct)
+      (ref.func $func-2params-a)
+    )
+  )
+)
+
+;; Array creation.
+(module
+  ;; CHECK:      (type $vector (array_subtype (mut f64) data))
+  (type $vector (array (mut f64)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $arrays (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (array.new $vector
+  ;; CHECK-NEXT:     (f64.const 3.14159)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (array.new_default $vector
+  ;; CHECK-NEXT:     (i32.const 100)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (array.init_static $vector
+  ;; CHECK-NEXT:     (f64.const 1.1)
+  ;; CHECK-NEXT:     (f64.const 2.2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $arrays
+    (drop
+      (ref.as_non_null
+        (array.new $vector
+          (f64.const 3.14159)
+          (i32.const 1)
+        )
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (array.new_default $vector
+          (i32.const 100)
+        )
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (array.init_static $vector
+          (f64.const 1.1)
+          (f64.const 2.2)
+        )
+      )
+    )
+    ;; In the last case we have no possible non-null value and can optimize to
+    ;; an unreachable.
+    (drop
+      (ref.as_non_null
+        (ref.null $vector)
+      )
+    )
+  )
+)
+
+;; Struct fields.
+(module
+  ;; CHECK:      (type $parent (struct_subtype (field (mut (ref null $struct))) data))
+
+  ;; CHECK:      (type $child (struct_subtype (field (mut (ref null $struct))) (field (mut (ref null $struct))) $parent))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct_subtype data))
+
+  (type $parent (struct_subtype (field (mut (ref null $struct))) data))
+  (type $child (struct_subtype (field (mut (ref null $struct))) (field (mut (ref null $struct))) $parent))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (elem declare func $func)
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $child (ref null $child))
+  ;; CHECK-NEXT:  (local $parent (ref null $parent))
+  ;; CHECK-NEXT:  (local.set $child
+  ;; CHECK-NEXT:   (struct.new $child
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $child 0
+  ;; CHECK-NEXT:    (local.get $child)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $child 1
+  ;; CHECK-NEXT:      (local.get $child)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $parent
+  ;; CHECK-NEXT:   (struct.new $parent
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $parent 0
+  ;; CHECK-NEXT:      (local.get $parent)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (block $parent (result (ref $parent))
+  ;; CHECK-NEXT:       (drop
+  ;; CHECK-NEXT:        (block (result (ref $none_=>_none))
+  ;; CHECK-NEXT:         (drop
+  ;; CHECK-NEXT:          (br_on_cast_static $parent $parent
+  ;; CHECK-NEXT:           (ref.func $func)
+  ;; CHECK-NEXT:          )
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:         (ref.func $func)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (unreachable)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    (local $child (ref null $child))
+    (local $parent (ref null $parent))
+    ;; We create a child with a non-null value in field 0 and null in 1.
+    (local.set $child
+      (struct.new $child
+        (struct.new $struct)
+        (ref.null $struct)
+      )
+    )
+    ;; Getting field 0 should not be optimized or changed in any way.
+    (drop
+      (struct.get $child 0
+        (local.get $child)
+      )
+    )
+    ;; Field one can be optimized into a null constant (+ a drop of the get).
+    (drop
+      (struct.get $child 1
+        (local.get $child)
+      )
+    )
+    ;; Create a parent with a null. The child wrote to the shared field, but
+    ;; using exact type info we can infer that the get's value must be a null,
+    ;; so we can optimize.
+    (local.set $parent
+      (struct.new $parent
+        (ref.null $struct)
+      )
+    )
+    (drop
+      (struct.get $parent 0
+        (local.get $parent)
+      )
+    )
+    ;; A ref.func is cast to a struct type, and then we read from that. The cast
+    ;; will trap at runtime, of course; for here, we should not error and also
+    ;; we can optimize these to unreachables. atm we filter out trapping
+    ;; contents in ref.cast, but not br_on_cast, so test both.
+    (drop
+      (struct.get $parent 0
+        (ref.cast_static $parent
+          (ref.func $func)
+        )
+      )
+    )
+    (drop
+      (struct.get $parent 0
+        (block $parent (result (ref $parent))
+          (drop
+            (br_on_cast_static $parent $parent
+              (ref.func $func)
+            )
+          )
+          (unreachable)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $nulls (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $block (result nullref)
+  ;; CHECK-NEXT:      (br $block
+  ;; CHECK-NEXT:       (ref.null none)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $block0 (result nullref)
+  ;; CHECK-NEXT:      (br $block0
+  ;; CHECK-NEXT:       (ref.null none)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $block1 (result nullref)
+  ;; CHECK-NEXT:      (br $block1
+  ;; CHECK-NEXT:       (block (result nullref)
+  ;; CHECK-NEXT:        (drop
+  ;; CHECK-NEXT:         (ref.cast_static $child
+  ;; CHECK-NEXT:          (ref.null none)
+  ;; CHECK-NEXT:         )
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (ref.null none)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $nulls
+    ;; Leave null constants alone.
+    (drop
+      (ref.null $parent)
+    )
+    ;; Reading from a null reference is easy to optimize - it will trap.
+    (drop
+      (struct.get $parent 0
+        (ref.null $parent)
+      )
+    )
+    ;; Send a null to the block, which is the only value exiting, so we can
+    ;; optimize here.
+    (drop
+      (block $block (result (ref null any))
+        (br $block
+          (ref.null any)
+        )
+        (unreachable)
+      )
+    )
+    ;; Send a more specific type. We should emit a valid null constant (but in
+    ;; this case, a null of either $parent or $child would be ok).
+    (drop
+      (block $block (result (ref null $parent))
+        (br $block
+          (ref.null $child)
+        )
+        (unreachable)
+      )
+    )
+    ;; Send a less specific type, via a cast. But all nulls are identical and
+    ;; ref.cast passes nulls through, so this is ok, but we must be careful to
+    ;; emit a ref.null $child on the outside (to not change the outer type to a
+    ;; less refined one).
+    (drop
+      (block $block (result (ref null $child))
+        (br $block
+          (ref.cast_static $child
+            (ref.null $parent)
+          )
+        )
+        (unreachable)
+      )
+    )
+  )
+)
+
+;; Default values in struct fields.
+(module
+  (type $A (struct_subtype (field i32) data))
+  (type $B (struct_subtype (field i32) data))
+  (type $C (struct_subtype (field i32) data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    ;; Create a struct with default values. We can propagate a 0 to the get.
+    (drop
+      (struct.get $A 0
+        (struct.new_default $A)
+      )
+    )
+    ;; Allocate with a non-default value, that can also be propagated.
+    (drop
+      (struct.get $B 0
+        (struct.new $B
+          (i32.const 1)
+        )
+      )
+    )
+  )
+)
+
+;; Exact types: Writes to the parent class do not confuse us.
+(module
+  ;; CHECK:      (type $parent (struct_subtype (field (mut (ref null $struct))) data))
+
+  ;; CHECK:      (type $child (struct_subtype (field (mut (ref null $struct))) (field i32) $parent))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct_subtype data))
+  (type $parent (struct_subtype (field (mut (ref null $struct))) data))
+  (type $child (struct_subtype (field (mut (ref null $struct))) (field i32) $parent))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $child (ref null $child))
+  ;; CHECK-NEXT:  (local $parent (ref null $parent))
+  ;; CHECK-NEXT:  (local.set $parent
+  ;; CHECK-NEXT:   (struct.new $parent
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (struct.get $parent 0
+  ;; CHECK-NEXT:     (local.get $parent)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $child
+  ;; CHECK-NEXT:   (struct.new $child
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result nullref)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (struct.get $child 0
+  ;; CHECK-NEXT:        (local.get $child)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (ref.null none)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    (local $child (ref null $child))
+    (local $parent (ref null $parent))
+    ;; Allocate when writing to the parent's field.
+    (local.set $parent
+      (struct.new $parent
+        (struct.new $struct)
+      )
+    )
+    ;; This cannot be optimized in any way.
+    (drop
+      (ref.as_non_null
+        (struct.get $parent 0
+          (local.get $parent)
+        )
+      )
+    )
+    ;; The child writes a null to the first field.
+    (local.set $child
+      (struct.new $child
+        (ref.null $struct)
+        (i32.const 0)
+      )
+    )
+    ;; The parent wrote to the shared field, but that does not prevent us from
+    ;; seeing that the child must have a null there, and so this will trap.
+    (drop
+      (ref.as_non_null
+        (struct.get $child 0
+          (local.get $child)
+        )
+      )
+    )
+  )
+)
+
+;; Write values to the parent *and* the child and read from the child.
+(module
+  ;; CHECK:      (type $parent (struct_subtype (field (mut i32)) data))
+  (type $parent (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
+  (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $child (ref null $child))
+  ;; CHECK-NEXT:  (local $parent (ref null $parent))
+  ;; CHECK-NEXT:  (local.set $parent
+  ;; CHECK-NEXT:   (struct.new $parent
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $parent 0
+  ;; CHECK-NEXT:      (local.get $parent)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $child
+  ;; CHECK-NEXT:   (struct.new $child
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $child 0
+  ;; CHECK-NEXT:      (local.get $child)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $child 1
+  ;; CHECK-NEXT:      (local.get $child)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    (local $child (ref null $child))
+    (local $parent (ref null $parent))
+    (local.set $parent
+      (struct.new $parent
+        (i32.const 10)
+      )
+    )
+    ;; This can be optimized to 10. The child also sets this field, but the
+    ;; reference in the local $parent can only be a $parent and nothing else.
+    (drop
+      (struct.get $parent 0
+        (local.get $parent)
+      )
+    )
+    (local.set $child
+      (struct.new $child
+        ;; The value here conflicts with the parent's for this field, but the
+        ;; local $child can only contain a $child and nothing else, so we can
+        ;; optimize the get below us.
+        (i32.const 20)
+        (i32.const 30)
+      )
+    )
+    (drop
+      (struct.get $child 0
+        (local.get $child)
+      )
+    )
+    ;; This get aliases nothing but 30, so we can optimize.
+    (drop
+      (struct.get $child 1
+        (local.get $child)
+      )
+    )
+  )
+)
+
+;; As above, but the $parent local can now contain a child too.
+(module
+  ;; CHECK:      (type $parent (struct_subtype (field (mut i32)) data))
+  (type $parent (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
+  (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "func" (func $func))
+
+  ;; CHECK:      (func $func (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $child (ref null $child))
+  ;; CHECK-NEXT:  (local $parent (ref null $parent))
+  ;; CHECK-NEXT:  (local.set $parent
+  ;; CHECK-NEXT:   (struct.new $parent
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (local.set $parent
+  ;; CHECK-NEXT:    (local.tee $child
+  ;; CHECK-NEXT:     (struct.new $child
+  ;; CHECK-NEXT:      (i32.const 20)
+  ;; CHECK-NEXT:      (i32.const 30)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $parent 0
+  ;; CHECK-NEXT:    (local.get $parent)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $child 0
+  ;; CHECK-NEXT:      (local.get $child)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func (export "func") (param $x i32)
+    (local $child (ref null $child))
+    (local $parent (ref null $parent))
+    (local.set $parent
+      (struct.new $parent
+        (i32.const 10)
+      )
+    )
+    ;; Another, optional, set to $parent.
+    (if
+      (local.get $x)
+      (local.set $parent
+        (local.tee $child
+          (struct.new $child
+            (i32.const 20)
+            (i32.const 30)
+          )
+        )
+      )
+    )
+    ;; This get cannot be optimized because before us the local might be set a
+    ;; child as well. So the local $parent can refer to either type, and they
+    ;; disagree on the aliased value.
+    (drop
+      (struct.get $parent 0
+        (local.get $parent)
+      )
+    )
+    ;; But this one can be optimized as $child can only contain a child.
+    (drop
+      (struct.get $child 0
+        (local.get $child)
+      )
+    )
+  )
+)
+
+;; As above, but now the parent and child happen to agree on the aliased value.
+(module
+  ;; CHECK:      (type $parent (struct_subtype (field (mut i32)) data))
+  (type $parent (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
+  (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $child (ref null $child))
+  ;; CHECK-NEXT:  (local $parent (ref null $parent))
+  ;; CHECK-NEXT:  (local.set $parent
+  ;; CHECK-NEXT:   (struct.new $parent
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $parent 0
+  ;; CHECK-NEXT:      (local.get $parent)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $parent
+  ;; CHECK-NEXT:   (local.tee $child
+  ;; CHECK-NEXT:    (struct.new $child
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:     (i32.const 30)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $child 0
+  ;; CHECK-NEXT:      (local.get $child)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    (local $child (ref null $child))
+    (local $parent (ref null $parent))
+    (local.set $parent
+      (struct.new $parent
+        (i32.const 10)
+      )
+    )
+    (drop
+      (struct.get $parent 0
+        (local.get $parent)
+      )
+    )
+    (local.set $parent
+      (local.tee $child
+        (struct.new $child
+          (i32.const 10) ;; This is 10, like above, so we can optimize the get
+                         ;; before us.
+          (i32.const 30)
+        )
+      )
+    )
+    (drop
+      (struct.get $child 0
+        (local.get $child)
+      )
+    )
+  )
+)
+
+;; Arrays get/set
+(module
+  (type $nothing (array_subtype (mut (ref null any)) data))
+
+  ;; CHECK:      (type $null (array_subtype (mut anyref) data))
+  (type $null (array_subtype (mut (ref null any)) data))
+
+  ;; CHECK:      (type $something (array_subtype (mut anyref) data))
+  (type $something (array_subtype (mut (ref null any)) data))
+
+  (type $something-child (array_subtype (mut (ref null any)) $something))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.set $null
+  ;; CHECK-NEXT:   (array.new_default $null
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result nullref)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (array.get $null
+  ;; CHECK-NEXT:        (array.new_default $null
+  ;; CHECK-NEXT:         (i32.const 10)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (i32.const 0)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (ref.null none)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.set $something
+  ;; CHECK-NEXT:   (array.new_default $something
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (array.get $something
+  ;; CHECK-NEXT:     (array.new_default $something
+  ;; CHECK-NEXT:      (i32.const 10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    ;; Reading from a null will trap, and we can optimize to an unreachable.
+    (drop
+      (array.get $nothing
+        (ref.null $nothing)
+        (i32.const 0)
+      )
+    )
+    ;; Write a null to this array.
+    (array.set $null
+      (array.new_default $null
+        (i32.const 10)
+      )
+      (i32.const 0)
+      (ref.null any)
+    )
+    ;; We can only read a null here, so this will trap and can be optimized.
+    (drop
+      (ref.as_non_null
+        (array.get $null
+          (array.new_default $null
+            (i32.const 10)
+          )
+          (i32.const 0)
+        )
+      )
+    )
+    ;; In $something we do actually write a non-null value, so we cannot add
+    ;; unreachables here.
+    (array.set $something
+      (array.new_default $something
+        (i32.const 10)
+      )
+      (i32.const 0)
+      (struct.new $struct)
+    )
+    (drop
+      (ref.as_non_null
+        (array.get $something
+          (array.new_default $something
+            (i32.const 10)
+          )
+          (i32.const 0)
+        )
+      )
+    )
+    ;; $something-child has nothing written to it, but its parent does. Still,
+    ;; with exact type info that does not confuse us, and we can optimize to an
+    ;; unreachable.
+    (drop
+      (ref.as_non_null
+        (array.get $something-child
+          (ref.cast_static $something-child
+            (array.new_default $something
+              (i32.const 10)
+            )
+          )
+          (i32.const 0)
+        )
+      )
+    )
+  )
+)
+
+;; A big chain, from an allocation that passes through many locations along the
+;; way before it is used. Nothing here can be optimized.
+(module
+  ;; CHECK:      (type $storage (struct_subtype (field (mut anyref)) data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  (type $storage (struct (field (mut (ref null any)))))
+
+  ;; CHECK:      (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func))
+
+  ;; CHECK:      (global $x (mut anyref) (ref.null none))
+  (global $x (mut (ref null any)) (ref.null any))
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $x anyref)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $x
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (struct.get $storage 0
+  ;; CHECK-NEXT:     (struct.new $storage
+  ;; CHECK-NEXT:      (call $pass-through
+  ;; CHECK-NEXT:       (global.get $x)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo
+    (local $x (ref null any))
+    ;; Allocate a non-null value and pass it through a local.
+    (local.set $x
+      (struct.new $struct)
+    )
+    ;; Pass it through a global.
+    (global.set $x
+      (local.get $x)
+    )
+    ;; Pass it through a call, then write it to a struct, then read it from
+    ;; there, and coerce to non-null which we would optimize if the value were
+    ;; only a null. But it is not a null, and no optimizations happen here.
+    (drop
+      (ref.as_non_null
+        (struct.get $storage 0
+          (struct.new $storage
+            (call $pass-through
+              (global.get $x)
+            )
+          )
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $pass-through (type $anyref_=>_anyref) (param $x anyref) (result anyref)
+  ;; CHECK-NEXT:  (local.get $x)
+  ;; CHECK-NEXT: )
+  (func $pass-through (param $x (ref null any)) (result (ref null any))
+    (local.get $x)
+  )
+)
+
+;; As above, but the chain is turned into a loop, replacing the initial
+;; allocation with a get from the end. We can optimize such cycles.
+(module
+  (type $struct (struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $storage (struct_subtype (field (mut anyref)) data))
+  (type $storage (struct (field (mut (ref null any)))))
+
+  ;; CHECK:      (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func))
+
+  ;; CHECK:      (global $x (mut anyref) (ref.null none))
+  (global $x (mut (ref null any)) (ref.null any))
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $x anyref)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $x
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.new $storage
+  ;; CHECK-NEXT:      (block (result nullref)
+  ;; CHECK-NEXT:       (drop
+  ;; CHECK-NEXT:        (call $pass-through
+  ;; CHECK-NEXT:         (ref.null none)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (ref.null none)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo
+    (local $x (ref null any))
+    ;; Replace the initial allocation with a read from the global. That is
+    ;; written to lower down, forming a loop - a loop with no actual allocation
+    ;; anywhere, so we can infer the possible values are only a null.
+    (local.set $x
+      (global.get $x)
+    )
+    (global.set $x
+      (struct.get $storage 0
+        (struct.new $storage
+          (call $pass-through
+            (local.get $x)
+          )
+        )
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $pass-through (type $anyref_=>_anyref) (param $x anyref) (result anyref)
+  ;; CHECK-NEXT:  (ref.null none)
+  ;; CHECK-NEXT: )
+  (func $pass-through (param $x (ref null any)) (result (ref null any))
+    (local.get $x)
+  )
+)
+
+;; A single long chain as above, but now we break the chain in the middle by
+;; adding a non-null value.
+(module
+  ;; CHECK:      (type $storage (struct_subtype (field (mut anyref)) data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  (type $storage (struct (field (mut (ref null any)))))
+
+  ;; CHECK:      (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func))
+
+  ;; CHECK:      (global $x (mut anyref) (ref.null none))
+  (global $x (mut (ref null any)) (ref.null any))
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $x anyref)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (global.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $x
+  ;; CHECK-NEXT:   (struct.get $storage 0
+  ;; CHECK-NEXT:    (struct.new $storage
+  ;; CHECK-NEXT:     (call $pass-through
+  ;; CHECK-NEXT:      (struct.new_default $struct)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (global.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo
+    (local $x (ref null any))
+    (local.set $x
+      (global.get $x)
+    )
+    (global.set $x
+      (struct.get $storage 0
+        (struct.new $storage
+          (call $pass-through
+            ;; The only change is to allocate here instead of reading the local
+            ;; $x. This causes us to not optimize anything in this function.
+            (struct.new $struct)
+          )
+        )
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (global.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $pass-through (type $anyref_=>_anyref) (param $x anyref) (result anyref)
+  ;; CHECK-NEXT:  (local.get $x)
+  ;; CHECK-NEXT: )
+  (func $pass-through (param $x (ref null any)) (result (ref null any))
+    (local.get $x)
+  )
+)
+
+;; Exceptions.
+(module
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $anyref_=>_none (func_subtype (param anyref) func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  ;; CHECK:      (tag $nothing (param anyref))
+  (tag $nothing (param (ref null any)))
+
+  ;; CHECK:      (tag $something (param anyref))
+  (tag $something (param (ref null any)))
+
+  ;; CHECK:      (tag $empty (param))
+  (tag $empty (param))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $0 anyref)
+  ;; CHECK-NEXT:  (throw $nothing
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (try $try
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $nothing
+  ;; CHECK-NEXT:    (local.set $0
+  ;; CHECK-NEXT:     (pop anyref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (block (result nullref)
+  ;; CHECK-NEXT:        (drop
+  ;; CHECK-NEXT:         (local.get $0)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (ref.null none)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (throw $something
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (try $try0
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $something
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (pop anyref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    ;; This tag receives no non-null value, so we can optimize the pop of it,
+    ;; in the next try-catch, to an unreachable.
+    (throw $nothing
+      (ref.null $struct)
+    )
+    (try
+      (do)
+      (catch $nothing
+        (drop
+          (ref.as_non_null
+            (pop (ref null any))
+          )
+        )
+      )
+    )
+    ;; This tag cannot be optimized as we send it something.
+    (throw $something
+      (struct.new $struct)
+    )
+    (try
+      (do)
+      (catch $something
+        (drop
+          (ref.as_non_null
+            (pop (ref null any))
+          )
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $empty-tag (type $none_=>_none)
+  ;; CHECK-NEXT:  (try $label$3
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $empty
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $empty-tag
+    ;; Check we do not error on catching an empty tag.
+    (try $label$3
+      (do
+        (nop)
+      )
+      (catch $empty
+        (nop)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $try-results (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (try $try (result i32)
+  ;; CHECK-NEXT:      (do
+  ;; CHECK-NEXT:       (i32.const 0)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (catch $empty
+  ;; CHECK-NEXT:       (i32.const 0)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (catch_all
+  ;; CHECK-NEXT:       (i32.const 0)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (try $try1 (result i32)
+  ;; CHECK-NEXT:    (do
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch $empty
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch_all
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (try $try2 (result i32)
+  ;; CHECK-NEXT:    (do
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch $empty
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch_all
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (try $try3 (result i32)
+  ;; CHECK-NEXT:    (do
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch $empty
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch_all
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $try-results
+    ;; If all values flowing out are identical, we can optimize. That is only
+    ;; the case in the very first try.
+    (drop
+      (try (result i32)
+        (do
+          (i32.const 0)
+        )
+        (catch $empty
+          (i32.const 0)
+        )
+        (catch_all
+          (i32.const 0)
+        )
+      )
+    )
+    ;; If any of the values is changed, we cannot.
+    (drop
+      (try (result i32)
+        (do
+          (i32.const 42)
+        )
+        (catch $empty
+          (i32.const 0)
+        )
+        (catch_all
+          (i32.const 0)
+        )
+      )
+    )
+    (drop
+      (try (result i32)
+        (do
+          (i32.const 0)
+        )
+        (catch $empty
+          (i32.const 42)
+        )
+        (catch_all
+          (i32.const 0)
+        )
+      )
+    )
+    (drop
+      (try (result i32)
+        (do
+          (i32.const 0)
+        )
+        (catch $empty
+          (i32.const 0)
+        )
+        (catch_all
+          (i32.const 42)
+        )
+      )
+    )
+  )
+)
+
+;; Exceptions with a tuple
+(module
+  ;; CHECK:      (type $anyref_anyref_=>_none (func_subtype (param anyref anyref) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct))
+
+  ;; CHECK:      (tag $tag (param anyref anyref))
+  (tag $tag (param (ref null any)) (param (ref null any)))
+
+  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $0 (anyref anyref))
+  ;; CHECK-NEXT:  (throw $tag
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (try $try
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $tag
+  ;; CHECK-NEXT:    (local.set $0
+  ;; CHECK-NEXT:     (pop anyref anyref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result nullref)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (local.get $0)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (ref.null none)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (try $try0
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $tag
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (tuple.extract 1
+  ;; CHECK-NEXT:      (pop anyref anyref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $func
+    ;; This tag receives a null in the first parameter.
+    (throw $tag
+      (ref.null $struct)
+      (struct.new $struct)
+    )
+    ;; Catch the first, which we can optimize to a null.
+    (try
+      (do)
+      (catch $tag
+        (drop
+          (tuple.extract 0
+            (pop (ref null any) (ref null any))
+          )
+        )
+      )
+    )
+    ;; Catch the second, which we cannot optimize.
+    (try
+      (do)
+      (catch $tag
+        (drop
+          (tuple.extract 1
+            (pop (ref null any) (ref null any))
+          )
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $none_=>_ref|${}| (func_subtype (result (ref ${})) func))
+
+  ;; CHECK:      (type ${} (struct_subtype  data))
+  (type ${} (struct_subtype data))
+
+  ;; CHECK:      (func $func (type $none_=>_ref|${}|) (result (ref ${}))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block $block (result (ref none))
+  ;; CHECK-NEXT:    (br_on_non_null $block
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $func (result (ref ${}))
+    ;; This block can only return a null in theory (in practice, not even that -
+    ;; the br will not be taken, but this pass is not smart enough to see that).
+    ;; We can optimize to an unreachable here, but must be careful - we cannot
+    ;; remove the block as the wasm would not validate (not unless we also
+    ;; removed the br, which we don't do atm). All we will do is add an
+    ;; unreachable after the block, on the outside of it (which would help other
+    ;; passes do more work).
+    (block $block (result (ref ${}))
+      (br_on_non_null $block
+        (ref.null ${})
+      )
+      (unreachable)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $A (struct_subtype (field i32) data))
+  (type $A (struct_subtype (field i32) data))
+  ;; CHECK:      (type $B (struct_subtype (field i64) data))
+  (type $B (struct_subtype (field i64) data))
+  ;; CHECK:      (type $C (struct_subtype (field f32) data))
+  (type $C (struct_subtype (field f32) data))
+  ;; CHECK:      (type $D (struct_subtype (field f64) data))
+  (type $D (struct_subtype (field f64) data))
+
+  ;; CHECK:      (func $many-types (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $x anyref)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (f32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (struct.new $D
+  ;; CHECK-NEXT:    (f64.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $many-types
+    (local $x (ref null any))
+    ;; Write 4 different types into $x. That should not confuse us, and we
+    ;; should not make any changes in this function.
+    (local.set $x
+      (struct.new $A
+        (i32.const 0)
+      )
+    )
+    (local.set $x
+      (struct.new $B
+        (i64.const 1)
+      )
+    )
+    (local.set $x
+      (struct.new $C
+        (f32.const 2)
+      )
+    )
+    (local.set $x
+      (struct.new $D
+        (f64.const 3)
+      )
+    )
+    (drop
+      (ref.as_non_null
+        (local.get $x)
+      )
+    )
+  )
+)
+
+;; Test a vtable-like pattern. This tests ref.func values flowing into struct
+;; locations being properly noticed, both from global locations (the global's
+;; init) and a function ($create).
+(module
+  ;; CHECK:      (type $vtable-A (struct_subtype (field funcref) (field funcref) (field funcref) data))
+  (type $vtable-A (struct_subtype (field (ref null func)) (field (ref null func)) (field (ref null func)) data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global-A (ref $vtable-A) (struct.new $vtable-A
+  ;; CHECK-NEXT:  (ref.func $foo)
+  ;; CHECK-NEXT:  (ref.null nofunc)
+  ;; CHECK-NEXT:  (ref.func $foo)
+  ;; CHECK-NEXT: ))
+  (global $global-A (ref $vtable-A)
+    (struct.new $vtable-A
+      (ref.func $foo)
+      (ref.null func)
+      (ref.func $foo)
+    )
+  )
+
+  ;; CHECK:      (elem declare func $foo $test)
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.func $foo)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.null nofunc)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $vtable-A 2
+  ;; CHECK-NEXT:    (global.get $global-A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; The first item here contains a fixed value (ref.func $foo) in both the
+    ;; global init and in the function $create, which we can apply.
+    (drop
+      (struct.get $vtable-A 0
+        (global.get $global-A)
+      )
+    )
+    ;; The second item here contains a null in all cases, which we can also
+    ;; apply.
+    (drop
+      (struct.get $vtable-A 1
+        (global.get $global-A)
+      )
+    )
+    ;; The third item has more than one possible value, due to the function
+    ;; $create later down, so we cannot optimize.
+    (drop
+      (struct.get $vtable-A 2
+        (global.get $global-A)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $create (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $vtable-A
+  ;; CHECK-NEXT:    (ref.func $foo)
+  ;; CHECK-NEXT:    (ref.null nofunc)
+  ;; CHECK-NEXT:    (ref.func $test)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create
+    (drop
+      (struct.new $vtable-A
+        (ref.func $foo)
+        (ref.null func)
+        (ref.func $test)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $foo)
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct_subtype (field i32) data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $ref (ref null $struct))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (block (result (ref $struct))
+  ;; CHECK-NEXT:    (block (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct 0
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (local $ref (ref null $struct))
+    ;; Regression test for an assertion firing in this case. We should properly
+    ;; handle the multiple intermediate blocks here, allowing us to optimize the
+    ;; get below to a 42.
+    (local.set $ref
+      (block (result (ref $struct))
+        (block (result (ref $struct))
+          (struct.new $struct
+            (i32.const 42)
+          )
+        )
+      )
+    )
+    (drop
+      (struct.get $struct 0
+        (local.get $ref)
+      )
+    )
+  )
+)
+
+;; Casts.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct_subtype (field i32) data))
+  ;; CHECK:      (type $substruct (struct_subtype (field i32) (field i32) $struct))
+  (type $substruct (struct_subtype (field i32) (field i32) $struct))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct))
+  (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct))
+
+  ;; CHECK:      (type $other (struct_subtype  data))
+  (type $other (struct_subtype data))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $i32_ref?|$struct|_ref?|$struct|_ref?|$other|_ref|$struct|_ref|$struct|_ref|$other|_=>_none (func_subtype (param i32 (ref null $struct) (ref null $struct) (ref null $other) (ref $struct) (ref $struct) (ref $other)) func))
+
+  ;; CHECK:      (type $none_=>_ref|eq| (func_subtype (result (ref eq)) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (export "test-cones" (func $test-cones))
+
+  ;; CHECK:      (export "ref.test-inexact" (func $ref.test-inexact))
+
+  ;; CHECK:      (export "ref.eq-zero" (func $ref.eq-zero))
+
+  ;; CHECK:      (export "ref.eq-unknown" (func $ref.eq-unknown))
+
+  ;; CHECK:      (export "ref.eq-cone" (func $ref.eq-cone))
+
+  ;; CHECK:      (export "local-no" (func $ref.eq-local-no))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $substruct
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $substruct
+  ;; CHECK-NEXT:    (struct.new $subsubstruct
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:     (i32.const 4)
+  ;; CHECK-NEXT:     (i32.const 5)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; The cast here will fail, and the ref.cast allows nothing through, so we
+    ;; can emit an unreachable here.
+    (drop
+      (ref.cast_static $substruct
+        (struct.new $struct
+          (i32.const 0)
+        )
+      )
+    )
+    ;; This cast of a type to itself can succeed (in fact, it will), so we make
+    ;; no changes here.
+    (drop
+      (ref.cast_static $substruct
+        (struct.new $substruct
+          (i32.const 1)
+          (i32.const 2)
+        )
+      )
+    )
+    ;; This cast of a subtype will also succeed. As above, we make no changes.
+    (drop
+      (ref.cast_static $substruct
+        (struct.new $subsubstruct
+          (i32.const 3)
+          (i32.const 4)
+          (i32.const 5)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $test-nulls (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.cast_static $struct
+  ;; CHECK-NEXT:      (block (result nullref)
+  ;; CHECK-NEXT:       (drop
+  ;; CHECK-NEXT:        (call $import)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (ref.null none)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.cast_static $struct
+  ;; CHECK-NEXT:      (select (result i31ref)
+  ;; CHECK-NEXT:       (ref.null none)
+  ;; CHECK-NEXT:       (i31.new
+  ;; CHECK-NEXT:        (i32.const 0)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (call $import)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $struct
+  ;; CHECK-NEXT:    (select (result (ref null $struct))
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test-nulls
+    ;; Only a null can flow through the cast, which we can infer for the value
+    ;; of the cast.
+    (drop
+      (ref.cast_static $struct
+        (select
+          (ref.null $struct)
+          (ref.null $struct)
+          (call $import)
+        )
+      )
+    )
+    ;; A null or an i31 will reach the cast; only the null can actually pass
+    ;; through (an i31 would fail the cast). Given that, we can infer a null for
+    ;; the value of the cast.
+    (drop
+      (ref.cast_static $struct
+        (select
+          (ref.null $struct)
+          (i31.new (i32.const 0))
+          (call $import)
+        )
+      )
+    )
+    ;; A null or a $struct may arrive, and so we cannot do anything here.
+    (drop
+      (ref.cast_static $struct
+        (select
+          (ref.null $struct)
+          (struct.new $struct
+            (i32.const 6)
+          )
+          (call $import)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $test-cones (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $struct
+  ;; CHECK-NEXT:    (select (result (ref null $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $struct
+  ;; CHECK-NEXT:    (select (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 2)
+  ;; CHECK-NEXT:      (i32.const 3)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $substruct
+  ;; CHECK-NEXT:    (select (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 5)
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test-cones (export "test-cones") (param $x i32)
+    ;; The input to the ref.cast is potentially null, so we cannot infer here.
+    (drop
+      (ref.cast_static $struct
+        (select
+          (struct.new $struct
+            (i32.const 0)
+          )
+          (ref.null any)
+          (local.get $x)
+        )
+      )
+    )
+    ;; The input to the ref.cast is either $struct or $substruct, both of which
+    ;; work, so we cannot optimize anything here away.
+    (drop
+      (ref.cast_static $struct
+        (select
+          (struct.new $struct
+            (i32.const 1)
+          )
+          (struct.new $substruct
+            (i32.const 2)
+            (i32.const 3)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    ;; As above, but now we test with $substruct, so one possibility fails and
+    ;; one succeeds. We cannot infer here either.
+    (drop
+      (ref.cast_static $substruct
+        (select
+          (struct.new $struct
+            (i32.const 4)
+          )
+          (struct.new $substruct
+            (i32.const 5)
+            (i32.const 6)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    ;; Two possible types, both are supertypes, so neither is a subtype, and we
+    ;; can infer an unreachable. The combination of these two is a cone from
+    ;; $struct of depth 1, which does not overlap with $subsubstruct.
+    (drop
+      (ref.cast_static $subsubstruct
+        (select
+          (struct.new $struct
+            (i32.const 7)
+          )
+          (struct.new $substruct
+            (i32.const 8)
+            (i32.const 9)
+          )
+          (local.get $x)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.test-exact (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.test-exact
+    ;; This cast will fail: we know the exact type of the reference, and it is
+    ;; not a subtype.
+    (drop
+      (ref.test_static $substruct
+        (struct.new $struct
+          (i32.const 0)
+        )
+      )
+    )
+    ;; Casting a thing to itself must succeed.
+    (drop
+      (ref.test_static $substruct
+        (struct.new $substruct
+          (i32.const 1)
+          (i32.const 2)
+        )
+      )
+    )
+    ;; Casting a thing to a supertype must succeed.
+    (drop
+      (ref.test_static $substruct
+        (struct.new $subsubstruct
+          (i32.const 3)
+          (i32.const 4)
+          (i32.const 5)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.test-inexact (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $struct
+  ;; CHECK-NEXT:    (select (result (ref null $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $substruct
+  ;; CHECK-NEXT:    (select (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 5)
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.test-inexact (export "ref.test-inexact") (param $x i32)
+    ;; The input to the ref.test is potentially null, so we cannot infer here.
+    (drop
+      (ref.test_static $struct
+        (select
+          (struct.new $struct
+            (i32.const 0)
+          )
+          (ref.null any)
+          (local.get $x)
+        )
+      )
+    )
+    ;; The input to the ref.test is either $struct or $substruct, both of which
+    ;; work, so here we can infer a 1. We do so using a cone type: the
+    ;; combination of those two types is a cone on $struct of depth 1, and that
+    ;; cone is 100% a subtype of $struct, so the test will succeed.
+    (drop
+      (ref.test_static $struct
+        (select
+          (struct.new $struct
+            (i32.const 1)
+          )
+          (struct.new $substruct
+            (i32.const 2)
+            (i32.const 3)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    ;; As above, but now we test with $substruct, so one possibility fails and
+    ;; one succeeds. We cannot infer here.
+    (drop
+      (ref.test_static $substruct
+        (select
+          (struct.new $struct
+            (i32.const 4)
+          )
+          (struct.new $substruct
+            (i32.const 5)
+            (i32.const 6)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    ;; Two possible types, both are supertypes, so neither is a subtype, and we
+    ;; can infer a 0. The combination of these two is a cone from $struct of
+    ;; depth 1, which does not overlap with $subsubstruct.
+    (drop
+      (ref.test_static $subsubstruct
+        (select
+          (struct.new $struct
+            (i32.const 7)
+          )
+          (struct.new $substruct
+            (i32.const 8)
+            (i32.const 9)
+          )
+          (local.get $x)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.eq-zero (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.eq-zero (export "ref.eq-zero")
+    ;; We do not track specific references, so only the types can be used here.
+    ;; Using the types, we can infer that two different ExactTypes cannot
+    ;; contain the same reference, so we infer a 0.
+    (drop
+      (ref.eq
+        (struct.new $struct
+          (i32.const 1)
+        )
+        (struct.new $substruct
+          (i32.const 2)
+          (i32.const 3)
+        )
+      )
+    )
+    ;; A null and a non-null reference cannot be identical, so we infer 0.
+    (drop
+      (ref.eq
+        (ref.null $struct)
+        (struct.new $struct
+          (i32.const 5)
+        )
+      )
+    )
+    (drop
+      (ref.eq
+        (struct.new $struct
+          (i32.const 5)
+        )
+        (ref.null $struct)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.eq-unknown (type $i32_ref?|$struct|_ref?|$struct|_ref?|$other|_ref|$struct|_ref|$struct|_ref|$other|_=>_none) (param $x i32) (param $struct (ref null $struct)) (param $struct2 (ref null $struct)) (param $other (ref null $other)) (param $nn-struct (ref $struct)) (param $nn-struct2 (ref $struct)) (param $nn-other (ref $other))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (i32.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (i32.const 5)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:    (local.get $other)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:    (local.get $struct2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (local.get $struct)
+  ;; CHECK-NEXT:    (local.get $nn-struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (local.get $nn-struct)
+  ;; CHECK-NEXT:    (local.get $nn-struct2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.eq-unknown (export "ref.eq-unknown") (param $x i32) (param $struct (ref null $struct)) (param $struct2 (ref null $struct)) (param $other (ref null $other)) (param $nn-struct (ref $struct)) (param $nn-struct2 (ref $struct)) (param $nn-other (ref $other))
+    ;; Here we cannot infer as the type is identical. (Though, if we used more
+    ;; than the type, we could see they cannot be identical.)
+    (drop
+      (ref.eq
+        (struct.new $struct
+          (i32.const 4)
+        )
+        (struct.new $struct
+          (i32.const 5)
+        )
+      )
+    )
+    ;; These nulls are identical, so we could infer 1, but we leave that for
+    ;; other passes, and do not infer here.
+    (drop
+      (ref.eq
+        (ref.null $struct)
+        (ref.null $struct)
+      )
+    )
+    ;; When nulls are possible, we cannot infer anything (with or without the
+    ;; same type on both sides).
+    (drop
+      (ref.eq
+        (local.get $struct)
+        (local.get $other)
+      )
+    )
+    (drop
+      (ref.eq
+        (local.get $struct)
+        (local.get $struct2)
+      )
+    )
+    ;; A null is only possible on one side, but the same non-null value could be
+    ;; on both.
+    (drop
+      (ref.eq
+        (local.get $struct)
+        (local.get $nn-struct)
+      )
+    )
+    ;; The type is identical, and non-null, but we don't know if the value is
+    ;; the same or not.
+    (drop
+      (ref.eq
+        (local.get $nn-struct)
+        (local.get $nn-struct2)
+      )
+    )
+    ;; Non-null on both sides, and incompatible types. We can infer 0 here.
+    (drop
+      (ref.eq
+        (local.get $nn-struct)
+        (local.get $nn-other)
+      )
+    )
+    ;; We can ignore unreachable code.
+    (drop
+      (ref.eq
+        (ref.null $struct)
+        (unreachable)
+      )
+    )
+    ;; The called function here traps and never returns an actual value, which
+    ;; will lead to an unreachable emitted right after the call. We should not
+    ;; prevent that from happening: an unreachable must be emitted (we will also
+    ;; emit an i32.const 0, which will never be reached, and not cause issues).
+    (drop
+      (ref.eq
+        (ref.null $struct)
+        (call $unreachable)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.eq-cone (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (select (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 3)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $subsubstruct
+  ;; CHECK-NEXT:      (i32.const 4)
+  ;; CHECK-NEXT:      (i32.const 5)
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (select (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 3)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 4)
+  ;; CHECK-NEXT:      (i32.const 5)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (select (result (ref $substruct))
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 3)
+  ;; CHECK-NEXT:      (i32.const 4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $subsubstruct
+  ;; CHECK-NEXT:      (i32.const 5)
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:      (i32.const 7)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (select (result (ref $substruct))
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 3)
+  ;; CHECK-NEXT:      (i32.const 4)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 5)
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.eq-cone (export "ref.eq-cone") (param $x i32)
+    ;; One side has two possible types, so we have a cone there. This cone is
+    ;; of subtypes of the other type, which is exact, so we cannot intersect
+    ;; here and we infer a 0.
+    (drop
+      (ref.eq
+        (struct.new $struct
+          (i32.const 1)
+        )
+        (select
+          (struct.new $substruct
+            (i32.const 2)
+            (i32.const 3)
+          )
+          (struct.new $subsubstruct
+            (i32.const 4)
+            (i32.const 5)
+            (i32.const 6)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    ;; Now the cone is large enough, so there might be an intersection, and we
+    ;; do not optimize (the cone of $struct and $subsubstruct contains
+    ;; $substruct which is in the middle).
+    (drop
+      (ref.eq
+        (struct.new $substruct
+          (i32.const 1)
+          (i32.const 2)
+        )
+        (select
+          (struct.new $struct
+            (i32.const 3)
+          )
+          (struct.new $subsubstruct
+            (i32.const 4)
+            (i32.const 5)
+            (i32.const 6)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (ref.eq
+        (struct.new $substruct
+          (i32.const 1)
+          (i32.const 2)
+        )
+        (select
+          (struct.new $struct
+            (i32.const 3)
+          )
+          (struct.new $substruct ;; As above, but with this changed. We still
+            (i32.const 4)        ;; cannot optimize.
+            (i32.const 5)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (ref.eq
+        (struct.new $substruct
+          (i32.const 1)
+          (i32.const 2)
+        )
+        (select
+          (struct.new $substruct ;; As above, but with this changed. We still
+            (i32.const 3)        ;; cannot optimize.
+            (i32.const 4)
+          )
+          (struct.new $subsubstruct
+            (i32.const 5)
+            (i32.const 6)
+            (i32.const 7)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (ref.eq
+        (struct.new $substruct
+          (i32.const 1)
+          (i32.const 2)
+        )
+        (select
+          (struct.new $substruct
+            (i32.const 3)
+            (i32.const 4)
+          )
+          (struct.new $substruct ;; As above, but with this changed. We still
+            (i32.const 5)        ;; cannot optimize (here the type is actually
+            (i32.const 6)        ;; exact, despite the select).
+          )
+          (local.get $x)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $unreachable (type $none_=>_ref|eq|) (result (ref eq))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $unreachable (result (ref eq))
+    (unreachable)
+  )
+
+  ;; CHECK:      (func $ref.eq-updates (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $x eqref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (block (result nullref)
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $import)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.eq-updates
+    (local $x (ref null eq))
+    ;; The local.get will be optimized to a ref.null. After that we will leave
+    ;; the ref.eq as it is. This guards against a possible bug of us not
+    ;; setting the contents of the new ref.null expression just created: the
+    ;; parent ref.eq will query the contents right after adding that expression,
+    ;; and the contents must be set or else we'll think nothing is possible
+    ;; there.
+    ;;
+    ;; (We could optimize ref.eq of two nulls to 1, but we leave that for other
+    ;; passes.)
+    (drop
+      (ref.eq
+        (ref.null eq)
+        (local.get $x)
+      )
+    )
+    ;; Another situation we need to be careful with effects of updates. Here
+    ;; we have a block whose result we can infer to a null, but that does not
+    ;; let us optimize the ref.eq, and we also must be careful to not drop side
+    ;; effects - the call must remain.
+    (drop
+      (ref.eq
+        (block (result eqref)
+          (drop
+            (call $import)
+          )
+          (ref.null $struct)
+        )
+        (ref.null $struct)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.eq-local-no (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $ref (ref $struct))
+  ;; CHECK-NEXT:  (local $ref-null (ref null $struct))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (local.set $ref-null
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (local.get $ref-null)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.eq-local-no (export "local-no") (param $x i32)
+    (local $ref (ref $struct))
+    (local $ref-null (ref null $struct))
+    ;; Always set the non-nullable ref, but only sometimes set the nullable.
+    (local.set $ref
+      (struct.new $struct
+        (i32.const 0)
+      )
+    )
+    (if
+      (local.get $x)
+      (local.set $ref-null
+        (local.get $ref)
+      )
+    )
+    ;; If the |if| executed they are equal, but otherwise not, so we can't
+    ;; optimize.
+    (drop
+      (ref.eq
+        (local.get $ref)
+        (local.get $ref-null)
+      )
+    )
+  )
+)
+
+;; Test ref.eq on globals.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field i32) data))
+  (type $A (struct_subtype (field i32) data))
+  (type $B (struct_subtype (field i32) $A))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $a (ref $A) (struct.new $A
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: ))
+  (global $a (ref $A) (struct.new $A
+    (i32.const 0)
+  ))
+
+  ;; CHECK:      (global $a-other (ref $A) (struct.new $A
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: ))
+  (global $a-other (ref $A) (struct.new $A
+    (i32.const 1)
+  ))
+
+  ;; CHECK:      (global $a-copy (ref $A) (global.get $a))
+  (global $a-copy (ref $A) (global.get $a))
+
+  ;; CHECK:      (global $a-mut (mut (ref $A)) (struct.new $A
+  ;; CHECK-NEXT:  (i32.const 2)
+  ;; CHECK-NEXT: ))
+  (global $a-mut (mut (ref $A)) (struct.new $A
+    (i32.const 2)
+  ))
+
+  ;; CHECK:      (global $a-mut-copy (mut (ref $A)) (global.get $a))
+  (global $a-mut-copy (mut (ref $A)) (global.get $a))
+
+  ;; CHECK:      (global $a-copy-mut (ref $A) (global.get $a-mut))
+  (global $a-copy-mut (ref $A) (global.get $a-mut))
+
+  ;; CHECK:      (global $a-mut-copy-written (mut (ref $A)) (global.get $a))
+  (global $a-mut-copy-written (mut (ref $A)) (global.get $a))
+
+  ;; CHECK:      (func $compare-a (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a-other)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a-mut)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a-copy-mut)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $a-mut-copy-written
+  ;; CHECK-NEXT:   (global.get $a-other)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (global.get $a)
+  ;; CHECK-NEXT:    (global.get $a-mut-copy-written)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $compare-a
+    ;; Comparisons of $a to everything else.
+    ;;
+    ;; GUFA does not compute the results of these yet, as it leaves it to other
+    ;; passes. This test guards against us doing anything unexpected here.
+    ;;
+    ;; What we do change here is update a copied global to the original,
+    ;; so $a-copy will turn into $a (because that is the only value it can
+    ;; contain). That should happen for the first three only. (For the 3rd, it
+    ;; works even though it is mutable, since there is only a single write
+    ;; anywhere.)
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a)
+      )
+    )
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a-copy)
+      )
+    )
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a-mut-copy)
+      )
+    )
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a-other)
+      )
+    )
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a-mut)
+      )
+    )
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a-copy-mut)
+      )
+    )
+    (global.set $a-mut-copy-written
+      (global.get $a-other)
+    )
+    (drop
+      (ref.eq
+        (global.get $a)
+        (global.get $a-mut-copy-written)
+      )
+    )
+  )
+)
+
+(module
+  (type $A (struct_subtype (field i32) data))
+  (type $B (struct_subtype (ref $A) data))
+  (type $C (struct_subtype (ref $B) data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; Test nested struct.get operations. We can optimize all this into the
+    ;; constant 42.
+    (drop
+      (struct.get $A 0
+        (struct.get $B 0
+          (struct.get $C 0
+            (struct.new $C
+              (struct.new $B
+                (struct.new $A
+                  (i32.const 42)
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $A (struct_subtype (field i32) data))
+  (type $A (struct_subtype (field i32) data))
+  ;; CHECK:      (type $B (struct_subtype (field (ref $A)) data))
+  (type $B (struct_subtype (ref $A) data))
+  ;; CHECK:      (type $C (struct_subtype (field (ref $B)) data))
+  (type $C (struct_subtype (ref $B) data))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (struct.get $B 0
+  ;; CHECK-NEXT:     (struct.get $C 0
+  ;; CHECK-NEXT:      (struct.new $C
+  ;; CHECK-NEXT:       (struct.new $B
+  ;; CHECK-NEXT:        (struct.new $A
+  ;; CHECK-NEXT:         (call $import)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As above, but now call an import for the i32; we cannot optimize.
+    (drop
+      (struct.get $A 0
+        (struct.get $B 0
+          (struct.get $C 0
+            (struct.new $C
+              (struct.new $B
+                (struct.new $A
+                  (call $import)
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
+;; ref.as* test.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field i32) data))
+  (type $A (struct_subtype (field i32) data))
+  ;; CHECK:      (type $B (struct_subtype (field i32) (field f64) $A))
+  (type $B (struct_subtype (field i32) (field f64) $A))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|$B| (func_subtype (result (ref $B)) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $foo (type $none_=>_ref|$B|) (result (ref $B))
+  ;; CHECK-NEXT:  (local $A (ref null $A))
+  ;; CHECK-NEXT:  (ref.cast_static $B
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.tee $A
+  ;; CHECK-NEXT:     (struct.new $B
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:      (f64.const 13.37)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo (result (ref $B))
+    (local $A (ref null $A))
+
+    ;; Read the following from the most nested comment first.
+
+    (ref.cast_static $B ;; if we mistakenly think this contains content of
+                        ;; type $A, it would trap, but it should not, and we
+                        ;; have nothing to optimize here
+      (ref.as_non_null ;; also $B, based on the child's *contents* (not type!)
+        (local.tee $A ;; flows out a $B, but has type $A
+          (struct.new $B ;; returns a $B
+            (i32.const 42)
+            (f64.const 13.37)
+          )
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $A (struct_subtype (field i32) data))
+  (type $A (struct_subtype (field i32) data))
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $B (struct_subtype (field i32) (field i32) $A))
+  (type $B (struct_subtype (field i32) (field i32) $A))
+  ;; CHECK:      (func $0 (type $none_=>_i32) (result i32)
+  ;; CHECK-NEXT:  (local $ref (ref null $A))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $A 0
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $0 (result i32)
+    (local $ref (ref null $A))
+    (local.set $ref
+      (struct.new $B
+        (i32.const 0)
+        (i32.const 1)
+      )
+    )
+    ;; This struct.get has a reference of type $A, but we can infer the type
+    ;; present in the reference must actually be a $B, and $B precisely - no
+    ;; sub or supertypes. So we can infer a value of 0.
+    ;;
+    ;; A possible bug that this is a regression test for is a confusion between
+    ;; the type of the content and the declared type. If we mixed them up and
+    ;; thought this must be precisely an $A and not a $B then we'd emit an
+    ;; unreachable here (since no $A is ever allocated).
+    (struct.get $A 0
+      (local.get $ref)
+    )
+  )
+)
+
+;; array.copy between types.
+(module
+  ;; CHECK:      (type $bytes (array_subtype (mut anyref) data))
+  (type $bytes (array (mut anyref)))
+  ;; CHECK:      (type $chars (array_subtype (mut anyref) data))
+  (type $chars (array (mut anyref)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $bytes (ref null $bytes))
+  ;; CHECK-NEXT:  (local $chars (ref null $chars))
+  ;; CHECK-NEXT:  (local.set $bytes
+  ;; CHECK-NEXT:   (array.init_static $bytes
+  ;; CHECK-NEXT:    (i31.new
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $chars
+  ;; CHECK-NEXT:   (array.init_static $chars
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.copy $chars $bytes
+  ;; CHECK-NEXT:   (local.get $chars)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $bytes)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.get $bytes
+  ;; CHECK-NEXT:    (local.get $bytes)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.get $chars
+  ;; CHECK-NEXT:    (local.get $chars)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (local $bytes (ref null $bytes))
+    (local $chars (ref null $chars))
+
+    ;; Write something to $bytes, but just a null to $chars. But then do a copy
+    ;; which means two things are possible in $chars, and we can't optimize
+    ;; there.
+    (local.set $bytes
+      (array.init_static $bytes
+        (i31.new (i32.const 0))
+      )
+    )
+    (local.set $chars
+      (array.init_static $chars
+        (ref.null any)
+      )
+    )
+    (array.copy $chars $bytes
+      (local.get $chars)
+      (i32.const 0)
+      (local.get $bytes)
+      (i32.const 0)
+      (i32.const 1)
+    )
+    (drop
+      (array.get $bytes
+        (local.get $bytes)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (array.get $chars
+        (local.get $chars)
+        (i32.const 0)
+      )
+    )
+  )
+)
+
+;; As above, but with a copy in the opposite direction. Now $chars has a single
+;; value (a null) which we can optimize, but $bytes has two values and we
+;; cannot optimize there.
+(module
+  ;; CHECK:      (type $bytes (array_subtype (mut anyref) data))
+  (type $bytes (array (mut anyref)))
+  ;; CHECK:      (type $chars (array_subtype (mut anyref) data))
+  (type $chars (array (mut anyref)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $bytes (ref null $bytes))
+  ;; CHECK-NEXT:  (local $chars (ref null $chars))
+  ;; CHECK-NEXT:  (local.set $bytes
+  ;; CHECK-NEXT:   (array.init_static $bytes
+  ;; CHECK-NEXT:    (i31.new
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $chars
+  ;; CHECK-NEXT:   (array.init_static $chars
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.copy $bytes $chars
+  ;; CHECK-NEXT:   (local.get $bytes)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $chars)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.get $bytes
+  ;; CHECK-NEXT:    (local.get $bytes)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (array.get $chars
+  ;; CHECK-NEXT:      (local.get $chars)
+  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (local $bytes (ref null $bytes))
+    (local $chars (ref null $chars))
+    (local.set $bytes
+      (array.init_static $bytes
+        (i31.new (i32.const 0))
+      )
+    )
+    (local.set $chars
+      (array.init_static $chars
+        (ref.null any)
+      )
+    )
+    (array.copy $bytes $chars
+      (local.get $bytes)
+      (i32.const 0)
+      (local.get $chars)
+      (i32.const 0)
+      (i32.const 1)
+    )
+    (drop
+      (array.get $bytes
+        (local.get $bytes)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (array.get $chars
+        (local.get $chars)
+        (i32.const 0)
+      )
+    )
+  )
+)
+
+;; Basic tests for all instructions appearing in possible-contents.cpp but not
+;; already shown above. If we forgot to add the proper links to any of them,
+;; they might appear as if no content were possible there, and we'd emit an
+;; unreachable. That should not happen anywhere here.
+(module
+  (type $A (struct_subtype data))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $B (array_subtype (mut anyref) data))
+  (type $B (array (mut anyref)))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
+
+  ;; CHECK:      (memory $0 10)
+
+  ;; CHECK:      (table $t 0 externref)
+
+  ;; CHECK:      (tag $e-i32 (param i32))
+  (tag $e-i32 (param i32))
+
+  (memory $0 10)
+
+  (table $t 0 externref)
+
+  ;; CHECK:      (func $br_table (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $A (result i32)
+  ;; CHECK-NEXT:      (br_table $A $A
+  ;; CHECK-NEXT:       (i32.const 1)
+  ;; CHECK-NEXT:       (i32.const 2)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $br_table
+    (drop
+      ;; The value 1 can be inferred here.
+      (block $A (result i32)
+        (br_table $A $A
+          (i32.const 1)
+          (i32.const 2)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $memory (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.atomic.rmw.add
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.atomic.rmw.cmpxchg
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:    (i32.const 15)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (memory.atomic.wait32
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:    (i64.const 15)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (memory.atomic.notify
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (memory.size)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (memory.grow
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $memory
+    (drop
+      (i32.load
+        (i32.const 5)
+      )
+    )
+    (drop
+      (i32.atomic.rmw.add
+        (i32.const 5)
+        (i32.const 10)
+      )
+    )
+    (drop
+      (i32.atomic.rmw.cmpxchg
+        (i32.const 5)
+        (i32.const 10)
+        (i32.const 15)
+      )
+    )
+    (drop
+      (memory.atomic.wait32
+        (i32.const 5)
+        (i32.const 10)
+        (i64.const 15)
+      )
+    )
+    (drop
+      (memory.atomic.notify
+        (i32.const 5)
+        (i32.const 10)
+      )
+    )
+    (drop
+      (memory.size)
+    )
+    (drop
+      (memory.grow
+        (i32.const 1)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $simd (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i8x16.extract_lane_s 0
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i8x16.replace_lane 0
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (v128.bitselect
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000)
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000005 0x00000000 0x00000006 0x00000000)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i8x16.shr_s
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (v128.load8_splat
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (v128.load8_lane 0
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simd
+    (drop
+      (i8x16.extract_lane_s 0
+        (v128.const i64x2 1 2)
+      )
+    )
+    (drop
+      (i8x16.replace_lane 0
+        (v128.const i64x2 1 2)
+        (i32.const 3)
+      )
+    )
+    (drop
+      (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31
+        (v128.const i64x2 1 2)
+        (v128.const i64x2 3 4)
+      )
+    )
+    (drop
+      (v128.bitselect
+        (v128.const i64x2 1 2)
+        (v128.const i64x2 3 4)
+        (v128.const i64x2 5 6)
+      )
+    )
+    (drop
+      (i8x16.shr_s
+        (v128.const i64x2 1 2)
+        (i32.const 3)
+      )
+    )
+    (drop
+      (v128.load8_splat
+        (i32.const 0)
+      )
+    )
+    (drop
+      (v128.load8_lane 0
+        (i32.const 0)
+        (v128.const i64x2 1 2)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $unary (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unary
+    (drop
+      (i32.eqz
+        (i32.const 1)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $binary (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $binary
+    (drop
+      (i32.add
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $table (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (table.get $t
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (table.size $t)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (table.grow $t
+  ;; CHECK-NEXT:    (ref.null noextern)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $table
+    (drop
+      (table.get $t
+        (i32.const 1)
+      )
+    )
+    (drop
+      (table.size $t)
+    )
+    (drop
+      (table.grow $t
+        (ref.null extern)
+        (i32.const 1)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $i31 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i31.get_s
+  ;; CHECK-NEXT:    (i31.new
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $i31
+    (drop
+      (i31.get_s
+        (i31.new
+          (i32.const 0)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $arrays (type $ref|$B|_=>_none) (param $B (ref $B))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.len
+  ;; CHECK-NEXT:    (array.init_static $B
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $arrays (param $B (ref $B))
+    (drop
+      (array.len $B
+        (array.init_static $B
+          (ref.null none)
+          (ref.null none)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $rethrow (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $0 i32)
+  ;; CHECK-NEXT:  (try $l0
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (throw $e-i32
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $e-i32
+  ;; CHECK-NEXT:    (local.set $0
+  ;; CHECK-NEXT:     (pop i32)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (block (result i32)
+  ;; CHECK-NEXT:       (drop
+  ;; CHECK-NEXT:        (local.get $0)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (i32.const 0)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (rethrow $l0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $rethrow
+    (try $l0
+      (do
+        (throw $e-i32
+          (i32.const 0)
+        )
+      )
+      (catch $e-i32
+        (drop
+          (pop i32)
+        )
+        (rethrow $l0)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $tuples (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (tuple.make
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $tuples
+    (drop
+      (tuple.make
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct_subtype (mut i32) data))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
+  (type $substruct (struct_subtype (mut i32) f64 $struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $something (mut (ref $struct)) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 10)
+  ;; CHECK-NEXT: ))
+  (global $something (mut (ref $struct)) (struct.new $struct
+    (i32.const 10)
+  ))
+
+  ;; CHECK:      (global $subsomething (mut (ref $substruct)) (struct.new $substruct
+  ;; CHECK-NEXT:  (i32.const 22)
+  ;; CHECK-NEXT:  (f64.const 3.14159)
+  ;; CHECK-NEXT: ))
+  (global $subsomething (mut (ref $substruct)) (struct.new $substruct
+    (i32.const 22)
+    (f64.const 3.14159)
+  ))
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (global.set $something
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (global.get $something)
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (global.get $something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 22)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo
+    ;; The global $something has an initial value and this later value, and they
+    ;; are both of type $struct, so we can infer an exact type for the global.
+    (global.set $something
+      (struct.new $struct
+        (i32.const 10)
+      )
+    )
+    ;; Write to that global here. This can only affect $struct, and *not*
+    ;; $substruct, thanks to the exact type.
+    (struct.set $struct 0
+      (global.get $something)
+      (i32.const 12)
+    )
+    ;; We cannot optimize the first get here, as it might be 10 or 11.
+    (drop
+      (struct.get $struct 0
+        (global.get $something)
+      )
+    )
+    ;; We can optimize this get, however, as nothing aliased it and 22 is the
+    ;; only possibility.
+    (drop
+      (struct.get $substruct 0
+        (global.get $subsomething)
+      )
+    )
+  )
+)
+
+;; As above, but we can no longer infer an exact type for the struct.set on the
+;; global $something.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct_subtype (mut i32) data))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
+  (type $substruct (struct_subtype (mut i32) f64 $struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $something (mut (ref $struct)) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 10)
+  ;; CHECK-NEXT: ))
+  (global $something (mut (ref $struct)) (struct.new $struct
+    (i32.const 10)
+  ))
+
+  ;; CHECK:      (global $subsomething (mut (ref $substruct)) (struct.new $substruct
+  ;; CHECK-NEXT:  (i32.const 22)
+  ;; CHECK-NEXT:  (f64.const 3.14159)
+  ;; CHECK-NEXT: ))
+  (global $subsomething (mut (ref $substruct)) (struct.new $substruct
+    (i32.const 22)
+    (f64.const 3.14159)
+  ))
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (global.set $something
+  ;; CHECK-NEXT:   (struct.new $substruct
+  ;; CHECK-NEXT:    (i32.const 22)
+  ;; CHECK-NEXT:    (f64.const 3.14159)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (global.get $something)
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (global.get $something)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $substruct 0
+  ;; CHECK-NEXT:    (global.get $subsomething)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo
+    ;; Write a $substruct to $something, so that the global might contain either
+    ;; of the two types.
+    (global.set $something
+      (struct.new $substruct
+        (i32.const 22)
+        (f64.const 3.14159)
+      )
+      (i32.const 11)
+    )
+    ;; This write might alias both types now.
+    (struct.set $struct 0
+      (global.get $something)
+      (i32.const 12)
+    )
+    ;; As a result, we can optimize neither of these gets.
+    (drop
+      (struct.get $struct 0
+        (global.get $something)
+      )
+    )
+    (drop
+      (struct.get $substruct 0
+        (global.get $subsomething)
+      )
+    )
+  )
+)
+
+;; As above, but change the constants in the first field in all cases to 10. Now
+;; we can optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct_subtype (mut i32) data))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
+  (type $substruct (struct_subtype (mut i32) f64 $struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $something (mut (ref $struct)) (struct.new $struct
+  ;; CHECK-NEXT:  (i32.const 10)
+  ;; CHECK-NEXT: ))
+  (global $something (mut (ref $struct)) (struct.new $struct
+    (i32.const 10)
+  ))
+
+  ;; CHECK:      (global $subsomething (mut (ref $substruct)) (struct.new $substruct
+  ;; CHECK-NEXT:  (i32.const 10)
+  ;; CHECK-NEXT:  (f64.const 3.14159)
+  ;; CHECK-NEXT: ))
+  (global $subsomething (mut (ref $substruct)) (struct.new $substruct
+    (i32.const 10)
+    (f64.const 3.14159)
+  ))
+
+  ;; CHECK:      (func $foo (type $none_=>_none)
+  ;; CHECK-NEXT:  (global.set $something
+  ;; CHECK-NEXT:   (struct.new $substruct
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:    (f64.const 3.14159)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (global.get $something)
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo
+    (global.set $something
+      (struct.new $substruct
+        (i32.const 10)
+        (f64.const 3.14159)
+      )
+      (i32.const 10)
+    )
+    (struct.set $struct 0
+      (global.get $something)
+      (i32.const 10)
+    )
+    (drop
+      (struct.get $struct 0
+        (global.get $something)
+      )
+    )
+    (drop
+      (struct.get $substruct 0
+        (global.get $subsomething)
+      )
+    )
+  )
+)
+
+;; call_ref types
+(module
+  ;; CHECK:      (type $i1 (func_subtype (param i32) func))
+  (type $i1 (func (param i32)))
+  ;; CHECK:      (type $i2 (func_subtype (param i32) func))
+  (type $i2 (func (param i32)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (global $func (ref func) (ref.func $reffed-in-global-code))
+  (global $func (ref func) (ref.func $reffed-in-global-code))
+
+  ;; CHECK:      (elem declare func $reffed1 $reffed2)
+
+  ;; CHECK:      (func $reffed1 (type $i1) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed1 (type $i1) (param $x i32)
+    ;; This is called with one possible value, 42, which we can optimize the
+    ;; param to.
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $not-reffed (type $i1) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $not-reffed (type $i1) (param $x i32)
+    ;; This function has the same type as the previous one, but it is never
+    ;; taken by reference, which means the call_refs below do not affect it. As
+    ;; there are no other calls, this local.get can be turned into an
+    ;; unreachable.
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $reffed-in-global-code (type $i1) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed-in-global-code (type $i1) (param $x i32)
+    ;; The only ref to this function is in global code, so this tests that we
+    ;; scan that properly. This can be optimized like $reffed, that is, we can
+    ;; infer 42 here.
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $reffed2 (type $i2) (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed2 (type $i2) (param $x i32)
+    ;; This is called with two possible values, so we cannot optimize.
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $do-calls (type $none_=>_none)
+  ;; CHECK-NEXT:  (call_ref $i1
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (ref.func $reffed1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_ref $i1
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (ref.func $reffed1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_ref $i2
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:   (ref.func $reffed2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_ref $i2
+  ;; CHECK-NEXT:   (i32.const 99999)
+  ;; CHECK-NEXT:   (ref.func $reffed2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $do-calls
+    ;; Call $i1 twice with the same value, and $i2 twice with different values.
+    ;; Note that structurally the types are identical, but we still
+    ;; differentiate them, allowing us to optimize.
+    (call_ref $i1
+      (i32.const 42)
+      (ref.func $reffed1)
+    )
+    (call_ref $i1
+      (i32.const 42)
+      (ref.func $reffed1)
+    )
+    (call_ref $i2
+      (i32.const 1337)
+      (ref.func $reffed2)
+    )
+    (call_ref $i2
+      (i32.const 99999)
+      (ref.func $reffed2)
+    )
+  )
+
+  ;; CHECK:      (func $call_ref-nofunc (type $none_=>_none)
+  ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (ref.null nofunc)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call_ref-nofunc
+    ;; Test a call_ref of something of type nofunc. That has a heap type, but it
+    ;; is not a signature type. We should not crash on that.
+    (call_ref $i1
+      (i32.const 1)
+      (ref.null nofunc)
+    )
+  )
+)
+
+;; Limited cone reads.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $B))
+  (type $C (struct_subtype (field (mut i32)) $B))
+
+
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "reads" (func $reads))
+
+  ;; CHECK:      (func $reads (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref $B))
+  ;; CHECK-NEXT:  (local $C (ref $C))
+  ;; CHECK-NEXT:  (local.set $A
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $B
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $C
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:     (local.get $B)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:     (local.get $C)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reads (export "reads") (param $x i32)
+    (local $A (ref $A))
+    (local $B (ref $B))
+    (local $C (ref $C))
+    ;; B and C agree on their value.
+    (local.set $A
+      (struct.new $A
+        (i32.const 10)
+      )
+    )
+    (local.set $B
+      (struct.new $B
+        (i32.const 20)
+      )
+    )
+    (local.set $C
+      (struct.new $C
+        (i32.const 20)
+      )
+    )
+    ;; We can optimize the last of these, which mixes B and C, into 20.
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $A)
+          (local.get $B)
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $A)
+          (local.get $C)
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $B)
+          (local.get $C)
+          (local.get $x)
+        )
+      )
+    )
+  )
+)
+
+;; As above, but now A and B agree on the value and not B and C.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $B))
+  (type $C (struct_subtype (field (mut i32)) $B))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "reads" (func $reads))
+
+  ;; CHECK:      (func $reads (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref $B))
+  ;; CHECK-NEXT:  (local $C (ref $C))
+  ;; CHECK-NEXT:  (local.set $A
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $B
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $C
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:     (local.get $C)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $B 0
+  ;; CHECK-NEXT:    (select (result (ref $B))
+  ;; CHECK-NEXT:     (local.get $B)
+  ;; CHECK-NEXT:     (local.get $C)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reads (export "reads") (param $x i32)
+    (local $A (ref $A))
+    (local $B (ref $B))
+    (local $C (ref $C))
+    ;; A and B agree on their value.
+    (local.set $A
+      (struct.new $A
+        (i32.const 10)
+      )
+    )
+    (local.set $B
+      (struct.new $B
+        (i32.const 10)
+      )
+    )
+    (local.set $C
+      (struct.new $C
+        (i32.const 20)
+      )
+    )
+    ;; We can optimize the first of these, which mixes A and B, into 10.
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $A)
+          (local.get $B)
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $A)
+          (local.get $C)
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $B)
+          (local.get $C)
+          (local.get $x)
+        )
+      )
+    )
+  )
+)
+
+;; As above but now A has two subtypes, instead of a chain A->B->C
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $A))
+  (type $C (struct_subtype (field (mut i32)) $A)) ;; This line changed.
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "reads" (func $reads))
+
+  ;; CHECK:      (func $reads (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref $B))
+  ;; CHECK-NEXT:  (local $C (ref $C))
+  ;; CHECK-NEXT:  (local.set $A
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $B
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $C
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:     (local.get $B)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:     (local.get $C)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $B)
+  ;; CHECK-NEXT:     (local.get $C)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reads (export "reads") (param $x i32)
+    (local $A (ref $A))
+    (local $B (ref $B))
+    (local $C (ref $C))
+    ;; A and B agree on their value.
+    (local.set $A
+      (struct.new $A
+        (i32.const 10)
+      )
+    )
+    (local.set $B
+      (struct.new $B
+        (i32.const 10)
+      )
+    )
+    (local.set $C
+      (struct.new $C
+        (i32.const 20)
+      )
+    )
+    ;; We cannot optimize any of these. The first is optimizable in theory,
+    ;; since A and B agree on the value, but we end up with a cone on A of depth
+    ;; 1, and that includes B and C. To optimize this we'd need a sum type.
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $A)
+          (local.get $B)
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $A)
+          (local.get $C)
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (select
+          (local.get $B)
+          (local.get $C)
+          (local.get $x)
+        )
+      )
+    )
+  )
+)
+
+;; Cone writes.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $B))
+  (type $C (struct_subtype (field (mut i32)) $B))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "write" (func $write))
+
+  ;; CHECK:      (func $write (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref $B))
+  ;; CHECK-NEXT:  (local $C (ref $C))
+  ;; CHECK-NEXT:  (local.set $A
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $B
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $C
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $A 0
+  ;; CHECK-NEXT:   (select (result (ref $A))
+  ;; CHECK-NEXT:    (local.get $A)
+  ;; CHECK-NEXT:    (local.get $B)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $write (export "write") (param $x i32)
+    (local $A (ref $A))
+    (local $B (ref $B))
+    (local $C (ref $C))
+    ;; A and B agree on their value.
+    (local.set $A
+      (struct.new $A
+        (i32.const 10)
+      )
+    )
+    (local.set $B
+      (struct.new $B
+        (i32.const 10)
+      )
+    )
+    (local.set $C
+      (struct.new $C
+        (i32.const 20)
+      )
+    )
+    ;; Do a cone write. This writes the same value as they already have.
+    (struct.set $A 0
+      (select
+        (local.get $A)
+        (local.get $B)
+        (local.get $x)
+      )
+      (i32.const 10)
+    )
+    ;; Read from all the locals. We can optimize them all, to 10, 10, 20.
+    (drop
+      (struct.get $A 0
+        (local.get $A)
+      )
+    )
+    (drop
+      (struct.get $B 0
+        (local.get $B)
+      )
+    )
+    (drop
+      (struct.get $C 0
+        (local.get $C)
+      )
+    )
+  )
+)
+
+;; As above, but write a different value.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $B))
+  (type $C (struct_subtype (field (mut i32)) $B))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "write" (func $write))
+
+  ;; CHECK:      (func $write (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref $B))
+  ;; CHECK-NEXT:  (local $C (ref $C))
+  ;; CHECK-NEXT:  (local.set $A
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $B
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $C
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $B 0
+  ;; CHECK-NEXT:   (select (result (ref $B))
+  ;; CHECK-NEXT:    (local.get $B)
+  ;; CHECK-NEXT:    (local.get $C)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $C 0
+  ;; CHECK-NEXT:    (local.get $C)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $write (export "write") (param $x i32)
+    (local $A (ref $A))
+    (local $B (ref $B))
+    (local $C (ref $C))
+    (local.set $A
+      (struct.new $A
+        (i32.const 10)
+      )
+    )
+    (local.set $B
+      (struct.new $B
+        (i32.const 10)
+      )
+    )
+    (local.set $C
+      (struct.new $C
+        (i32.const 20)
+      )
+    )
+    ;; Do a different cone write from before: now we write to B and C. This
+    ;; means C can have 10 or 20, and so we don't optimize it down below.
+    (struct.set $A 0
+      (select
+        (local.get $B)
+        (local.get $C)
+        (local.get $x)
+      )
+      (i32.const 10)
+    )
+    (drop
+      (struct.get $A 0
+        (local.get $A)
+      )
+    )
+    (drop
+      (struct.get $B 0
+        (local.get $B)
+      )
+    )
+    (drop
+      (struct.get $C 0
+        (local.get $C)
+      )
+    )
+  )
+)
+
+;; As above, but write a different cone.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $B))
+  (type $C (struct_subtype (field (mut i32)) $B))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (export "write" (func $write))
+
+  ;; CHECK:      (func $write (type $i32_=>_none) (param $x i32)
+  ;; CHECK-NEXT:  (local $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref $B))
+  ;; CHECK-NEXT:  (local $C (ref $C))
+  ;; CHECK-NEXT:  (local.set $A
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $B
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $C
+  ;; CHECK-NEXT:   (struct.new $C
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $B 0
+  ;; CHECK-NEXT:   (select (result (ref $B))
+  ;; CHECK-NEXT:    (local.get $B)
+  ;; CHECK-NEXT:    (local.get $C)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $B 0
+  ;; CHECK-NEXT:    (local.get $B)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $write (export "write") (param $x i32)
+    (local $A (ref $A))
+    (local $B (ref $B))
+    (local $C (ref $C))
+    (local.set $A
+      (struct.new $A
+        (i32.const 10)
+      )
+    )
+    (local.set $B
+      (struct.new $B
+        (i32.const 10)
+      )
+    )
+    (local.set $C
+      (struct.new $C
+        (i32.const 20)
+      )
+    )
+    ;; Write a different value now: 20. This prevents us from optimizing B, but
+    ;; we can still optimize A and C.
+    (struct.set $A 0
+      (select
+        (local.get $B)
+        (local.get $C)
+        (local.get $x)
+      )
+      (i32.const 20)
+    )
+    (drop
+      (struct.get $A 0
+        (local.get $A)
+      )
+    )
+    (drop
+      (struct.get $B 0
+        (local.get $B)
+      )
+    )
+    (drop
+      (struct.get $C 0
+        (local.get $C)
+      )
+    )
+  )
+)
+
+;; Tests for proper inference of imported etc. values - we do know their type,
+;; at least.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+  (type $A (struct_subtype (field (mut i32)) data))
+
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+  (type $B (struct_subtype (field (mut i32)) $A))
+
+  ;; CHECK:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$A| (func_subtype (result (ref $A)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (global $A (ref $A)))
+  (import "a" "b" (global $A (ref $A)))
+
+  ;; CHECK:      (import "a" "c" (func $A (result (ref $A))))
+  (import "a" "c" (func $A (result (ref $A))))
+
+  ;; CHECK:      (global $mut_A (ref $A) (struct.new $A
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: ))
+  (global $mut_A (ref $A) (struct.new $A
+    (i32.const 42)
+  ))
+
+  ;; CHECK:      (export "mut_A" (global $mut_A))
+  (export "mut_A" (global $mut_A))
+
+  ;; CHECK:      (export "yes" (func $yes))
+
+  ;; CHECK:      (export "no" (func $no))
+
+  ;; CHECK:      (func $yes (type $ref|$A|_=>_none) (param $A (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $A)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $yes (export "yes") (param $A (ref $A))
+    ;; An imported global has a known type, at least, which in this case is
+    ;; enough for us to infer a result of 1.
+    (drop
+      (ref.test_static $A
+        (global.get $A)
+      )
+    )
+    ;; Likewise, a function result.
+    (drop
+      (ref.test_static $A
+        (call $A)
+      )
+    )
+    ;; Likewise, a parameter to this function, which is exported, but we do
+    ;; still know the type it will be called with, and can optimize to 1.
+    (drop
+      (ref.test_static $A
+        (local.get $A)
+      )
+    )
+    ;; Likewise, an exported mutable global can be modified by the outside, but
+    ;; the type remains known, and we can optimize to 1.
+    (drop
+      (ref.test_static $A
+        (global.get $A)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $no (type $ref|$A|_=>_none) (param $A (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $B
+  ;; CHECK-NEXT:    (global.get $A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $B
+  ;; CHECK-NEXT:    (call $A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $B
+  ;; CHECK-NEXT:    (local.get $A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $B
+  ;; CHECK-NEXT:    (global.get $A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no (export "no") (param $A (ref $A))
+    ;; Identical to the above function, but now all tests are vs type $B. We
+    ;; cannot optimize any of these, as all we know is the type is $A.
+    (drop
+      (ref.test_static $B
+        (global.get $A)
+      )
+    )
+    (drop
+      (ref.test_static $B
+        (call $A)
+      )
+    )
+    (drop
+      (ref.test_static $B
+        (local.get $A)
+      )
+    )
+    (drop
+      (ref.test_static $B
+        (global.get $A)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $filtering (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $B (result (ref $B))
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (br_on_cast_static $B $B
+  ;; CHECK-NEXT:        (struct.new $A
+  ;; CHECK-NEXT:         (i32.const 100)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $A (result (ref $A))
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (br_on_cast_static $A $A
+  ;; CHECK-NEXT:        (struct.new $A
+  ;; CHECK-NEXT:         (i32.const 200)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $filtering
+    ;; Check for filtering of values by the declared type in the wasm. We do not
+    ;; have specific filtering or flowing for br_on_* yet, so it will always
+    ;; send the value to the branch target. But the target has a declared type
+    ;; of $B, which means the exact $A gets filtered out, and nothing remains,
+    ;; so we can append an unreachable.
+    ;;
+    ;; When we add filtering/flowing for br_on_* this test should continue to
+    ;; pass and only the comment will need to be updated, so if you are reading
+    ;; this and it is stale, please fix that :)
+    (drop
+      (block $B (result (ref $B))
+        (drop
+          (br_on_cast_static $B $B
+            (struct.new $A
+              (i32.const 100)
+            )
+          )
+        )
+        (unreachable)
+      )
+    )
+    ;; But casting to $A will succeed, so the block is reachable, and also the
+    ;; cast will return 1.
+    (drop
+      (ref.test_static $A
+        (block $A (result (ref $A))
+          (drop
+            (br_on_cast_static $A $A
+              (struct.new $A
+                (i32.const 200)
+              )
+            )
+          )
+          (unreachable)
+        )
+      )
+    )
+  )
+)
+
+
+;; Check that array.new_data and array.new_seg are handled properly.
+(module
+  ;; CHECK:      (type $array-i8 (array_subtype i8 data))
+  (type $array-i8 (array i8))
+  ;; CHECK:      (type $array-funcref (array_subtype funcref data))
+  (type $array-funcref (array funcref))
+  ;; CHECK:      (type $ref|$array-i8|_ref|$array-funcref|_=>_none (func_subtype (param (ref $array-i8) (ref $array-funcref)) func))
+
+  ;; CHECK:      (data "hello")
+  (data "hello")
+  ;; CHECK:      (elem func $test)
+  (elem func $test)
+
+  ;; CHECK:      (export "test" (func $test))
+
+  ;; CHECK:      (func $test (type $ref|$array-i8|_ref|$array-funcref|_=>_none) (param $array-i8 (ref $array-i8)) (param $array-funcref (ref $array-funcref))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.new_data $array-i8 0
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.new_elem $array-funcref 0
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.get_u $array-i8
+  ;; CHECK-NEXT:    (local.get $array-i8)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (array.get $array-funcref
+  ;; CHECK-NEXT:    (local.get $array-funcref)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (export "test") (param $array-i8 (ref $array-i8)) (param $array-funcref (ref $array-funcref))
+    (drop
+      (array.new_data $array-i8 0
+        (i32.const 0)
+        (i32.const 5)
+      )
+    )
+    (drop
+      (array.new_elem $array-funcref 0
+        (i32.const 0)
+        (i32.const 1)
+      )
+    )
+    (drop
+      (array.get $array-i8
+        (local.get $array-i8)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (array.get $array-funcref
+        (local.get $array-funcref)
+        (i32.const 0)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/gufa-ssa.wast b/test/lit/passes/gufa-ssa.wast
new file mode 100644
index 0000000..6653428
--- /dev/null
+++ b/test/lit/passes/gufa-ssa.wast
@@ -0,0 +1,104 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (export "test" (func $test))
+
+  ;; CHECK:      (func $test (param $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $y
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $y
+  ;; CHECK-NEXT:   (i32.const 40)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 40)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:   (local.set $y
+  ;; CHECK-NEXT:    (i32.const 50)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (export "test") (param $x i32)
+    (local $y i32)
+    ;; A parameter - nothing to optimize.
+    (drop
+      (local.get $x)
+    )
+    ;; This has the default value, and can be optimized to 0.
+    (drop
+      (local.get $y)
+    )
+    (local.set $x
+      (i32.const 10)
+    )
+    (local.set $y
+      (i32.const 20)
+    )
+    ;; These can both be optimized to constants, 10 and 20.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    (local.set $x
+      (i32.const 30)
+    )
+    (local.set $y
+      (i32.const 40)
+    )
+    ;; Now these are 30 and 40.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+    (if
+      (local.get $x)
+      (local.set $y
+        (i32.const 50)
+      )
+    )
+    ;; x is the same but y is no longer optimizable, since it might contain 50.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $y)
+    )
+  )
+)
diff --git a/test/lit/passes/gufa-tags.wast b/test/lit/passes/gufa-tags.wast
new file mode 100644
index 0000000..8e43dc9
--- /dev/null
+++ b/test/lit/passes/gufa-tags.wast
@@ -0,0 +1,111 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s
+
+;; Two tags with different values.
+(module
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $f32_=>_none (func (param f32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (tag $tag$i32 (param i32))
+  (tag $tag$i32 (param i32))
+  ;; CHECK:      (tag $tag$f32 (param f32))
+  (tag $tag$f32 (param f32))
+
+  ;; CHECK:      (func $test
+  ;; CHECK-NEXT:  (local $0 i32)
+  ;; CHECK-NEXT:  (local $1 f32)
+  ;; CHECK-NEXT:  (try $try
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (throw $tag$i32
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $tag$i32
+  ;; CHECK-NEXT:    (local.set $0
+  ;; CHECK-NEXT:     (pop i32)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (local.get $0)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch $tag$f32
+  ;; CHECK-NEXT:    (local.set $1
+  ;; CHECK-NEXT:     (pop f32)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (local.get $1)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (try
+      (do
+        (throw $tag$i32
+          (i32.const 42)
+        )
+      )
+      (catch $tag$i32
+        ;; We always throw a 42 to this tag, so we can optimize this.
+        (drop
+          (pop i32)
+        )
+      )
+      (catch $tag$f32
+        ;; We never actually throw this, so it can be turned into an
+        ;; unreachable.
+        (drop
+          (pop f32)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $bar (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (try $try (result i32)
+  ;; CHECK-NEXT:    (do
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (catch $tag$i32
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (pop i32)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: )
+  (func $bar (result i32)
+    ;; Like the first case, we can optimize the pop here. The pop and the try
+    ;; body agree on the value, 42, so we can replace the entire try in theory,
+    ;; but we should not - removing the try would leave a pop without a proper
+    ;; parent (that is a problem even though the try does not have a name). We
+    ;; can still emit a 42 for the try, but must leave the try right before it,
+    ;; dropped.
+    (try (result i32)
+      (do
+        (i32.const 42)
+      )
+      (catch $tag$i32
+        (pop i32)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast
new file mode 100644
index 0000000..98db728
--- /dev/null
+++ b/test/lit/passes/gufa-vs-cfp.wast
@@ -0,0 +1,2667 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --nominal --remove-unused-names --gufa -all -S -o - | filecheck %s
+;; (remove-unused-names is added to test fallthrough values without a block
+;; name getting in the way)
+
+;; This is almost identical to cfp.wast, and is meant to facilitate comparisons
+;; between the passes - in particular, gufa should do everything cfp can do,
+;; although it may do it differently. Changes include:
+;;
+;;  * Tests must avoid things gufa optimizes away that would make the test
+;;    irrelevant. In particular, parameters to functions that are never called
+;;    will be turned to unreachable by gufa, so instead make those calls to
+;;    imports. Gufa will also realize that passing ref.null as the reference of
+;;    a struct.get/set will trap, so we must actually allocate something.
+;;  * Gufa optimizes in a more general way. Cfp will turn a struct.get whose
+;;    value it infers into a ref.as_non_null (to preserve the trap if the ref is
+;;    null) followed by the constant. Gufa has no special handling for
+;;    struct.get, so it will use its normal pattern there, of a drop of the
+;;    struct.get followed by the constant. (Other passes can remove the
+;;    dropped operation, like vacuum in trapsNeverHappen mode).
+;;  * Gufa's more general optimizations can remove more unreachable code, as it
+;;    checks for effects (and removes effectless code).
+;;
+;; This file could also run cfp in addition to gufa, but the aforementioned
+;; changes cause cfp to behave differently in some cases, which could lead to
+;; more confusion than benefit - the reader would not be able to compare the two
+;; outputs and see cfp as "correct" which gufa should match.
+;;
+;; Note that there is some overlap with gufa-refs.wast in some places, but
+;; intentionally no tests are removed here compared to cfp.wast, to make it
+;; simple to map the original cfp tests to their ported versions here.
+
+(module
+  (type $struct (struct i32))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $impossible-get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $impossible-get
+    (drop
+      ;; This type is never created, so a get is impossible, and we will trap
+      ;; anyhow. So we can turn this into an unreachable.
+      (struct.get $struct 0
+        (ref.null $struct)
+      )
+    )
+  )
+)
+
+(module
+  (type $struct (struct i64))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; The only place this type is created is with a default value, and so we
+    ;; can optimize the get into a constant (note that no drop of the
+    ;; ref is needed: the optimizer can see that the struct.get cannot trap, as
+    ;; its reference is non-nullable).
+    (drop
+      (struct.get $struct 0
+        (struct.new_default $struct)
+      )
+    )
+  )
+)
+
+(module
+  (type $struct (struct f32))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; The only place this type is created is with a constant value, and so we
+    ;; can optimize to a constant, the same as above (but the constant was
+    ;; passed in, as opposed to being a default value as in the last testcase).
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (f32.const 42)
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field f32) data))
+  (type $struct (struct f32))
+
+  ;; CHECK:      (type $none_=>_f32 (func_subtype (result f32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result f32)))
+  (import "a" "b" (func $import (result f32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; The value given is not a constant, and so we cannot optimize.
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (call $import)
+        )
+      )
+    )
+  )
+)
+
+;; Create in one function, get in another. The 10 should be forwarded to the
+;; get.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (i32.const 10)
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    ;; The reference will be dropped here, and not removed entirely, because
+    ;; the optimizer thinks it might have side effects (since it has a call).
+    ;; But the forwarded value, 10, is applied after that drop.
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+)
+
+;; As before, but with the order of functions reversed to check for any ordering
+;; issues.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (i32.const 10)
+    )
+  )
+)
+
+;; Different values assigned in the same function, in different struct.news,
+;; so we cannot optimize the struct.get away.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field f32) data))
+  (type $struct (struct f32))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (f32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (f32.const 1337)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.new $struct
+        (f32.const 42)
+      )
+    )
+    ;; (A better analysis could see that the first struct.new is dropped and its
+    ;; value cannot reach this struct.get.)
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (f32.const 1337)
+        )
+      )
+    )
+  )
+)
+
+;; Different values assigned in different functions, and one is a struct.set.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) data))
+  (type $struct (struct (mut f32)))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (f32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (f32.const 42)
+    )
+  )
+  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:   (f32.const 1337)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $set
+    (struct.set $struct 0
+      (call $create)
+      (f32.const 1337)
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (call $create)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    ;; (A better analysis could see that only $create's value can reach here.)
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+)
+
+;; As the last testcase, but the values happen to coincide, so we can optimize
+;; the get into a constant.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) data))
+  (type $struct (struct (mut f32)))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (f32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (f32.const 42)
+    )
+  )
+  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:   (f32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $set
+    (struct.set $struct 0
+      (call $create)
+      (f32.const 42) ;; The last testcase had 1337 here.
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+)
+
+;; Check that we look into the fallthrough value that is assigned.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) data))
+  (type $struct (struct (mut f32)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (f32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      ;; Fall though a 42. The block can be optimized to a constant.
+      (block $named (result f32)
+        (nop)
+        (f32.const 42)
+      )
+    )
+  )
+  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:   (block (result f32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (if (result f32)
+  ;; CHECK-NEXT:      (call $import)
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:      (f32.const 42)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $set
+    (struct.set $struct 0
+      (call $create)
+      ;; Fall though a 42 via an if.
+      (if (result f32)
+        (call $import)
+        (unreachable)
+        (f32.const 42)
+      )
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    ;; This can be inferred to be 42 since both the new and the set write that
+    ;; value.
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+)
+
+;; Test a function reference instead of a number.
+(module
+  (type $struct (struct funcref))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (elem declare func $test)
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.func $test)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (ref.func $test)
+        )
+      )
+    )
+  )
+)
+
+;; Test for unreachable creations, sets, and gets.
+(module
+  (type $struct (struct (mut i32)))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (unreachable)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.new $struct
+        (i32.const 10)
+        (unreachable)
+      )
+    )
+    (struct.set $struct 0
+      (struct.get $struct 0
+        (unreachable)
+      )
+      (i32.const 20)
+    )
+  )
+)
+
+;; Subtyping: Create a supertype and get a subtype. As we never create a
+;;            subtype, the get must trap anyhow (the reference it receives can
+;;            only be null in this closed world).
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+  (type $substruct (struct_subtype i32 $struct))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (i32.const 10)
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $create)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    ;; As the get must trap, we can optimize to an unreachable here.
+    (drop
+      (struct.get $substruct 0
+        (ref.cast_static $substruct
+          (call $create)
+        )
+      )
+    )
+  )
+)
+
+;; As above, but in addition to a new of $struct also add a set. The set,
+;; however, cannot write to the subtype, so we still know that any reads from
+;; the subtype must trap.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+  (type $substruct (struct_subtype (mut i32) $struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (i32.const 10)
+    )
+  )
+
+  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $set
+    (struct.set $struct 0
+      (call $create)
+      (i32.const 10)
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $create)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    (drop
+      (struct.get $substruct 0
+        (ref.cast_static $substruct
+          (call $create)
+        )
+      )
+    )
+  )
+)
+
+;; As above, pass the created supertype through a local and a cast on the way
+;; to a read of the subtype. Still, no actual instance of the subtype can
+;; appear in the get, so we can optimize to an unreachable.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+  (type $substruct (struct_subtype (mut i32) $struct))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $ref (ref null $struct))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (local.get $ref)
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (local $ref (ref null $struct))
+    (local.set $ref
+      (struct.new $struct
+        (i32.const 10)
+      )
+    )
+    (struct.set $struct 0
+      (local.get $ref)
+      (i32.const 10)
+    )
+    (drop
+      ;; This must trap, so we can add an unreachable.
+      (struct.get $substruct 0
+        ;; Only a null can pass through here, as the cast would not allow a ref
+        ;; to $struct. But no null is possible since the local gets written a
+        ;; non-null value before we get here, so we can optimize this to an
+        ;; unreachable.
+        (ref.cast_static $substruct
+          (local.get $ref)
+        )
+      )
+    )
+  )
+)
+
+;; Subtyping: Create a subtype and get a supertype. The get must receive a
+;;            reference to the subtype and so we can infer the value of the get.
+(module
+  (type $substruct (struct_subtype i32 f64 $struct))
+
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.get $struct 0
+        (struct.new $substruct
+          (i32.const 10)
+          (f64.const 3.14159)
+        )
+      )
+    )
+  )
+)
+
+;; Subtyping: Create both a subtype and a supertype, with identical constants
+;;            for the shared field, and get the supertype.
+(module
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+  ;; CHECK:      (type $substruct (struct_subtype (field i32) (field f64) $struct))
+  (type $substruct (struct_subtype i32 f64 $struct))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (select (result (ref $struct))
+  ;; CHECK-NEXT:      (struct.new $struct
+  ;; CHECK-NEXT:       (i32.const 10)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (struct.new $substruct
+  ;; CHECK-NEXT:       (i32.const 10)
+  ;; CHECK-NEXT:       (f64.const 3.14159)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (call $import)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; We can infer the value here must be 10.
+    (drop
+      (struct.get $struct 0
+        (select
+          (struct.new $struct
+            (i32.const 10)
+          )
+          (struct.new $substruct
+            (i32.const 10)
+            (f64.const 3.14159)
+          )
+          (call $import)
+        )
+      )
+    )
+  )
+)
+
+;; Subtyping: Create both a subtype and a supertype, with different constants
+;;            for the shared field, preventing optimization, as a get of the
+;;            supertype may receive an instance of the subtype.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field i32) (field f64) $struct))
+  (type $substruct (struct_subtype i32 f64 $struct))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (select (result (ref $struct))
+  ;; CHECK-NEXT:     (struct.new $struct
+  ;; CHECK-NEXT:      (i32.const 10)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new $substruct
+  ;; CHECK-NEXT:      (i32.const 20)
+  ;; CHECK-NEXT:      (f64.const 3.14159)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.get $struct 0
+        (select
+          (struct.new $struct
+            (i32.const 10)
+          )
+          (struct.new $substruct
+            (i32.const 20) ;; this constant changed
+            (f64.const 3.14159)
+          )
+          (call $import)
+        )
+      )
+    )
+  )
+)
+
+;; Subtyping: Create both a subtype and a supertype, with different constants
+;;            for the shared field, but get from the subtype. The field is
+;;            shared between the types, but we only create the subtype with
+;;            one value, so we can optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field i32) (field f64) $struct))
+  (type $substruct (struct_subtype i32 f64 $struct))
+
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.cast_static $substruct
+  ;; CHECK-NEXT:      (select (result (ref $struct))
+  ;; CHECK-NEXT:       (struct.new $struct
+  ;; CHECK-NEXT:        (i32.const 10)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (struct.new $substruct
+  ;; CHECK-NEXT:        (i32.const 20)
+  ;; CHECK-NEXT:        (f64.const 3.14159)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:       (call $import)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.get $struct 0
+        ;; This cast is added, ensuring only a $substruct can reach the get.
+        (ref.cast_static $substruct
+          (select
+            (struct.new $struct
+              (i32.const 10)
+            )
+            (struct.new $substruct
+              (i32.const 20)
+              (f64.const 3.14159)
+            )
+            (call $import)
+          )
+        )
+      )
+    )
+  )
+)
+
+;; As above, but add a set of $struct. The set prevents the optimization.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
+  (type $substruct (struct_subtype (mut i32) f64 $struct))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $ref (ref null $struct))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (select (result (ref $struct))
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 20)
+  ;; CHECK-NEXT:     (f64.const 3.14159)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (call $import)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (local.get $ref)
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $substruct 0
+  ;; CHECK-NEXT:    (ref.cast_static $substruct
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (local $ref (ref null $struct))
+    (local.set $ref
+      (select
+        (struct.new $struct
+          (i32.const 10)
+        )
+        (struct.new $substruct
+          (i32.const 20)
+          (f64.const 3.14159)
+        )
+        (call $import)
+      )
+    )
+    ;; This set is added. Even though the type is the super, this may write to
+    ;; the child, and so we cannot optimize.
+    (struct.set $struct 0
+      (local.get $ref)
+      (i32.const 10)
+    )
+    (drop
+      (struct.get $substruct 0
+        (ref.cast_static $substruct
+          (local.get $ref)
+        )
+      )
+    )
+  )
+)
+
+;; As above, but now the constant in the set agrees with the substruct value,
+;; so we can optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
+  (type $substruct (struct_subtype (mut i32) f64 $struct))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $ref (ref null $struct))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (select (result (ref $struct))
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new $substruct
+  ;; CHECK-NEXT:     (i32.const 20)
+  ;; CHECK-NEXT:     (f64.const 3.14159)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (call $import)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (local.get $ref)
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $substruct 0
+  ;; CHECK-NEXT:      (ref.cast_static $substruct
+  ;; CHECK-NEXT:       (local.get $ref)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (local $ref (ref null $struct))
+    (local.set $ref
+      (select
+        (struct.new $struct
+          (i32.const 10)
+        )
+        (struct.new $substruct
+          (i32.const 20)
+          (f64.const 3.14159)
+        )
+        (call $import)
+      )
+    )
+    (struct.set $struct 0
+      (local.get $ref)
+      ;; This now writes the same value as in the $substruct already has, 20, so
+      ;; we can optimize the get below (which must contain a $substruct).
+      (i32.const 20)
+    )
+    (drop
+      (struct.get $substruct 0
+        (ref.cast_static $substruct
+          (local.get $ref)
+        )
+      )
+    )
+  )
+)
+
+;; Multi-level subtyping, check that we propagate not just to the immediate
+;; supertype but all the way as needed.
+(module
+  ;; CHECK:      (type $struct1 (struct_subtype (field i32) data))
+  (type $struct1 (struct_subtype i32 data))
+
+  ;; CHECK:      (type $struct2 (struct_subtype (field i32) (field f64) $struct1))
+  (type $struct2 (struct_subtype i32 f64 $struct1))
+
+  ;; CHECK:      (type $struct3 (struct_subtype (field i32) (field f64) (field anyref) $struct2))
+  (type $struct3 (struct_subtype i32 f64 anyref $struct2))
+
+  ;; CHECK:      (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct3|) (result (ref $struct3))
+  ;; CHECK-NEXT:  (struct.new $struct3
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:   (f64.const 3.14159)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct3))
+    (struct.new $struct3
+      (i32.const 20)
+      (f64.const 3.14159)
+      (ref.null any)
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (local $ref (ref null $struct3))
+  ;; CHECK-NEXT:  (local.set $ref
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct3 0
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct3 0
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct3 1
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 3.14159)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct3 0
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct3 1
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 3.14159)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (struct.get $struct3 2
+  ;; CHECK-NEXT:      (local.get $ref)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    (local $ref (ref null $struct3))
+    (local.set $ref
+      (call $create)
+    )
+    ;; Get field 0 from $struct1. This can be optimized to a constant since
+    ;; we only ever created an instance of struct3 with a constant there - the
+    ;; reference must point to a $struct3. The same happens in all the other
+    ;; gets below as well, all optimize to constants.
+    (drop
+      (struct.get $struct1 0
+        (local.get $ref)
+      )
+    )
+    ;; Get both fields of $struct2.
+    (drop
+      (struct.get $struct2 0
+        (local.get $ref)
+      )
+    )
+    (drop
+      (struct.get $struct2 1
+        (local.get $ref)
+      )
+    )
+    ;; Get all 3 fields of $struct3
+    (drop
+      (struct.get $struct3 0
+        (local.get $ref)
+      )
+    )
+    (drop
+      (struct.get $struct3 1
+        (local.get $ref)
+      )
+    )
+    (drop
+      (struct.get $struct3 2
+        (local.get $ref)
+      )
+    )
+  )
+)
+
+;; Multi-level subtyping with conflicts. The even-numbered fields will get
+;; different values in the sub-most type. Create the top and bottom types, but
+;; not the middle one.
+(module
+  ;; CHECK:      (type $struct1 (struct_subtype (field i32) (field i32) data))
+  (type $struct1 (struct i32 i32))
+
+  ;; CHECK:      (type $struct2 (struct_subtype (field i32) (field i32) (field f64) (field f64) $struct1))
+  (type $struct2 (struct_subtype i32 i32 f64 f64 $struct1))
+
+  ;; CHECK:      (type $struct3 (struct_subtype (field i32) (field i32) (field f64) (field f64) (field anyref) (field anyref) $struct2))
+  (type $struct3 (struct_subtype i32 i32 f64 f64 anyref anyref $struct2))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_anyref (func_subtype (result anyref) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result anyref)))
+  (import "a" "b" (func $import (result anyref)))
+
+  ;; CHECK:      (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1))
+  ;; CHECK-NEXT:  (struct.new $struct1
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create1 (result (ref $struct1))
+    (struct.new $struct1
+      (i32.const 10)
+      (i32.const 20)
+    )
+  )
+
+  ;; CHECK:      (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3))
+  ;; CHECK-NEXT:  (struct.new $struct3
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:   (i32.const 999)
+  ;; CHECK-NEXT:   (f64.const 2.71828)
+  ;; CHECK-NEXT:   (f64.const 9.9999999)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:   (call $import)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create3 (result (ref $struct3))
+    (struct.new $struct3
+      (i32.const 10)
+      (i32.const 999) ;; use a different value here
+      (f64.const 2.71828)
+      (f64.const 9.9999999)
+      (ref.null any)
+      (call $import) ;; use an unknown value here, which can never be
+                     ;; optimized.
+    )
+  )
+
+  ;; CHECK:      (func $get-1 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-1
+    ;; Get all the fields of all the structs. First, create $struct1 and get
+    ;; its fields. Even though there are subtypes with different fields for some
+    ;; of them, we can optimize these using exact type info, as this must be a
+    ;; $struct1 and nothing else.
+    (drop
+      (struct.get $struct1 0
+        (call $create1)
+      )
+    )
+    (drop
+      (struct.get $struct1 1
+        (call $create1)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-2 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 2.71828)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 9.9999999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-2
+    ;; $struct2 is never created, instead create a $struct3. We can optimize,
+    ;; since $struct1's values are not relevant and cannot confuse us.
+    ;; trap.
+    (drop
+      (struct.get $struct2 0
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct2 1
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct2 2
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct2 3
+        (call $create3)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-3 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 2.71828)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 9.9999999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct3 5
+  ;; CHECK-NEXT:    (call $create3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-3
+    ;; We can optimize all these (where the field is constant).
+    (drop
+      (struct.get $struct3 0
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct3 1
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct3 2
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct3 3
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct3 4
+        (call $create3)
+      )
+    )
+    (drop
+      (struct.get $struct3 5
+        (call $create3)
+      )
+    )
+  )
+)
+
+;; Multi-level subtyping with a different value in the middle of the chain.
+(module
+  ;; CHECK:      (type $struct1 (struct_subtype (field (mut i32)) data))
+  (type $struct1 (struct (mut i32)))
+  ;; CHECK:      (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1))
+  (type $struct2 (struct_subtype (mut i32) f64 $struct1))
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2))
+  (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct2| (func_subtype (result (ref $struct2)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1))
+  ;; CHECK-NEXT:  (struct.new $struct1
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create1 (result (ref $struct1))
+    (struct.new $struct1
+      (i32.const 10)
+    )
+  )
+
+  ;; CHECK:      (func $create2 (type $none_=>_ref|$struct2|) (result (ref $struct2))
+  ;; CHECK-NEXT:  (struct.new $struct2
+  ;; CHECK-NEXT:   (i32.const 9999)
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create2 (result (ref $struct2))
+    (struct.new $struct2
+      (i32.const 9999) ;; use a different value here
+      (f64.const 0)
+    )
+  )
+
+  ;; CHECK:      (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3))
+  ;; CHECK-NEXT:  (struct.new $struct3
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create3 (result (ref $struct3))
+    (struct.new $struct3
+      (i32.const 10)
+      (f64.const 0)
+      (ref.null any)
+    )
+  )
+
+  ;; CHECK:      (func $get-precise (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 9999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-precise
+    ;; Get field 0 in all the types. We know precisely what the type is in each
+    ;; case here, so we can optimize all of these.
+    (drop
+      (struct.get $struct1 0
+        (call $create1)
+      )
+    )
+    (drop
+      (struct.get $struct2 0
+        (call $create2)
+      )
+    )
+    (drop
+      (struct.get $struct3 0
+        (call $create3)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-imprecise-1 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (select (result (ref $struct1))
+  ;; CHECK-NEXT:      (call $create1)
+  ;; CHECK-NEXT:      (call $create1)
+  ;; CHECK-NEXT:      (call $import)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct1 0
+  ;; CHECK-NEXT:    (select (result (ref $struct1))
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:     (call $create2)
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct1 0
+  ;; CHECK-NEXT:    (select (result (ref $struct1))
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-imprecise-1
+    ;; Check the results of reading from a ref that can be one of two things.
+    ;; We check all permutations in the arms of the select in this function and
+    ;; the next two.
+    ;;
+    ;; Atm we can only optimize when the ref is the same in both arms, since
+    ;; even if two different types agree on the value (like $struct1 and
+    ;; $struct3 do), once we see two different types we already see the type as
+    ;; imprecise, and $struct2 in the middle has a different value, so imprecise
+    ;; info is not enough.
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create1)
+          (call $create1)
+          (call $import)
+        )
+      )
+    )
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create1)
+          (call $create2)
+          (call $import)
+        )
+      )
+    )
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create1)
+          (call $create3)
+          (call $import)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-imprecise-2 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct1 0
+  ;; CHECK-NEXT:    (select (result (ref $struct1))
+  ;; CHECK-NEXT:     (call $create2)
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (select (result (ref $struct2))
+  ;; CHECK-NEXT:      (call $create2)
+  ;; CHECK-NEXT:      (call $create2)
+  ;; CHECK-NEXT:      (call $import)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 9999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct2 0
+  ;; CHECK-NEXT:    (select (result (ref $struct2))
+  ;; CHECK-NEXT:     (call $create2)
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-imprecise-2
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create2)
+          (call $create1)
+          (call $import)
+        )
+      )
+    )
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create2)
+          (call $create2)
+          (call $import)
+        )
+      )
+    )
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create2)
+          (call $create3)
+          (call $import)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $get-imprecise-3 (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct1 0
+  ;; CHECK-NEXT:    (select (result (ref $struct1))
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct2 0
+  ;; CHECK-NEXT:    (select (result (ref $struct2))
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:     (call $create2)
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (select (result (ref $struct3))
+  ;; CHECK-NEXT:      (call $create3)
+  ;; CHECK-NEXT:      (call $create3)
+  ;; CHECK-NEXT:      (call $import)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-imprecise-3
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create3)
+          (call $create1)
+          (call $import)
+        )
+      )
+    )
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create3)
+          (call $create2)
+          (call $import)
+        )
+      )
+    )
+    (drop
+      (struct.get $struct1 0
+        (select
+          (call $create3)
+          (call $create3)
+          (call $import)
+        )
+      )
+    )
+  )
+)
+
+;; As above, but add not just a new of the middle class with a different value
+;; but also a set. We can see that the set just affects the middle class,
+;; though, so it is not a problem.
+(module
+  ;; CHECK:      (type $struct1 (struct_subtype (field (mut i32)) data))
+  (type $struct1 (struct (mut i32)))
+  ;; CHECK:      (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1))
+  (type $struct2 (struct_subtype (mut i32) f64 $struct1))
+  ;; CHECK:      (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2))
+  (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct2| (func_subtype (result (ref $struct2)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1))
+  ;; CHECK-NEXT:  (struct.new $struct1
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create1 (result (ref $struct1))
+    (struct.new $struct1
+      (i32.const 10)
+    )
+  )
+
+  ;; CHECK:      (func $create2 (type $none_=>_ref|$struct2|) (result (ref $struct2))
+  ;; CHECK-NEXT:  (struct.new $struct2
+  ;; CHECK-NEXT:   (i32.const 9999)
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create2 (result (ref $struct2))
+    (struct.new $struct2
+      (i32.const 9999) ;; use a different value here
+      (f64.const 0)
+    )
+  )
+
+  ;; CHECK:      (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3))
+  ;; CHECK-NEXT:  (struct.new $struct3
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create3 (result (ref $struct3))
+    (struct.new $struct3
+      (i32.const 10)
+      (f64.const 0)
+      (ref.null any)
+    )
+  )
+
+  ;; CHECK:      (func $get-precise (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct2 0
+  ;; CHECK-NEXT:   (call $create2)
+  ;; CHECK-NEXT:   (i32.const 9999)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create2)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 9999)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-precise
+    ;; The set only affects $struct2, exactly it and nothing else, so we can
+    ;; optimize all the gets in this function.
+    (drop
+      (struct.get $struct1 0
+        (call $create1)
+      )
+    )
+    (struct.set $struct2 0
+      (call $create2)
+      (i32.const 9999)
+    )
+    (drop
+      (struct.get $struct2 0
+        (call $create2)
+      )
+    )
+    (drop
+      (struct.get $struct3 0
+        (call $create3)
+      )
+    )
+  )
+)
+
+;; As above, but the set is of a different value.
+(module
+  ;; CHECK:      (type $struct1 (struct_subtype (field (mut i32)) data))
+  (type $struct1 (struct (mut i32)))
+  ;; CHECK:      (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1))
+  (type $struct2 (struct_subtype (mut i32) f64 $struct1))
+  ;; CHECK:      (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2))
+  (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct2| (func_subtype (result (ref $struct2)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1))
+  ;; CHECK-NEXT:  (struct.new $struct1
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create1 (result (ref $struct1))
+    (struct.new $struct1
+      (i32.const 10)
+    )
+  )
+
+  ;; CHECK:      (func $create2 (type $none_=>_ref|$struct2|) (result (ref $struct2))
+  ;; CHECK-NEXT:  (struct.new $struct2
+  ;; CHECK-NEXT:   (i32.const 9999)
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create2 (result (ref $struct2))
+    (struct.new $struct2
+      (i32.const 9999) ;; use a different value here
+      (f64.const 0)
+    )
+  )
+
+  ;; CHECK:      (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3))
+  ;; CHECK-NEXT:  (struct.new $struct3
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create3 (result (ref $struct3))
+    (struct.new $struct3
+      (i32.const 10)
+      (f64.const 0)
+      (ref.null any)
+    )
+  )
+
+  ;; CHECK:      (func $get-precise (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $struct2 0
+  ;; CHECK-NEXT:   (call $create2)
+  ;; CHECK-NEXT:   (i32.const 1234)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct2 0
+  ;; CHECK-NEXT:    (call $create2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get-precise
+    (drop
+      (struct.get $struct1 0
+        (call $create1)
+      )
+    )
+    ;; This set of a different value limits our ability to optimize the get
+    ;; after us. But the get before us and the one at the very end remain
+    ;; optimized - changes to $struct2 do not confuse the other types.
+    (struct.set $struct2 0
+      (call $create2)
+      (i32.const 1234)
+    )
+    (drop
+      (struct.get $struct2 0
+        (call $create2)
+      )
+    )
+    (drop
+      (struct.get $struct3 0
+        (call $create3)
+      )
+    )
+  )
+)
+
+;; Test for a struct with multiple fields, some of which are constant and hence
+;; optimizable, and some not. Also test that some have the same type.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) (field f64) (field i32) (field f64) (field i32) data))
+  (type $struct (struct i32 f64 i32 f64 i32))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const 3.14159)
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.const 2.71828)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (i32.eqz (i32.const 10)) ;; not a constant (as far as this pass knows)
+      (f64.const 3.14159)
+      (i32.const 20)
+      (f64.abs (f64.const 2.71828)) ;; not a constant
+      (i32.const 30)
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (call $create)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result f64)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.const 3.14159)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 3
+  ;; CHECK-NEXT:    (call $create)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+    (drop
+      (struct.get $struct 1
+        (call $create)
+      )
+    )
+    (drop
+      (struct.get $struct 2
+        (call $create)
+      )
+    )
+    (drop
+      (struct.get $struct 3
+        (call $create)
+      )
+    )
+    (drop
+      (struct.get $struct 4
+        (call $create)
+      )
+    )
+    ;; Also test for multiple gets of the same field.
+    (drop
+      (struct.get $struct 4
+        (call $create)
+      )
+    )
+  )
+)
+
+;; Never create A, but have a set to its field. A subtype B has no creates nor
+;; sets, and the final subtype C has a create and a get. The set to A should
+;; apply to it, preventing optimization.
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut i32)) data))
+
+  ;; CHECK:      (type $B (struct_subtype (field (mut i32)) $A))
+
+  ;; CHECK:      (type $C (struct_subtype (field (mut i32)) $B))
+  (type $C (struct_subtype (mut i32) $B))
+
+  (type $A (struct (mut i32)))
+
+  (type $B (struct_subtype (mut i32) $A))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $none_=>_ref|$C| (func_subtype (result (ref $C)) func))
+
+  ;; CHECK:      (func $create-C (type $none_=>_ref|$C|) (result (ref $C))
+  ;; CHECK-NEXT:  (struct.new $C
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create-C (result (ref $C))
+    (struct.new $C
+      (i32.const 10)
+    )
+  )
+  ;; CHECK:      (func $set (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $A 0
+  ;; CHECK-NEXT:   (ref.cast_static $A
+  ;; CHECK-NEXT:    (call $create-C)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 20)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $set
+    ;; Set of $A, but the reference is actually a $C. We add a cast to make sure
+    ;; the type is $A, which should not confuse us: this set does alias the data
+    ;; in $C, which means we cannot optimize in the function $get below.
+    (struct.set $A 0
+      (ref.cast_static $A
+        (call $create-C)
+      )
+      (i32.const 20) ;; different value than in $create
+    )
+  )
+  ;; CHECK:      (func $get (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $C 0
+  ;; CHECK-NEXT:    (call $create-C)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $get
+    (drop
+      (struct.get $C 0
+        (call $create-C)
+      )
+    )
+  )
+)
+
+;; Copies of a field to itself can be ignored. As a result, we can optimize both
+;; of the gets here.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new_default $struct)
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new_default $struct)
+  )
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; This copy does not actually introduce any new possible values, and so it
+    ;; remains true that the only possible value is the default 0, so we can
+    ;; optimize the get below to a 0 (and also the get in the set).
+    (struct.set $struct 0
+      (call $create)
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+)
+
+;; Test of a near-copy, of a similar looking field (same index, and same field
+;; type) but in a different struct. The value in both structs is the same, so
+;; we can optimize.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) (field (mut i32)) data))
+  (type $struct (struct (mut f32) (mut i32)))
+  ;; CHECK:      (type $other (struct_subtype (field (mut f64)) (field (mut i32)) data))
+  (type $other (struct (mut f64) (mut i32)))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$other| (func_subtype (result (ref $other)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create-struct (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (f32.const 0)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create-struct (result (ref $struct))
+    (struct.new $struct
+      (f32.const 0)
+      (i32.const 42)
+    )
+  )
+
+  ;; CHECK:      (func $create-other (type $none_=>_ref|$other|) (result (ref $other))
+  ;; CHECK-NEXT:  (struct.new $other
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create-other (result (ref $other))
+    (struct.new $other
+      (f64.const 0)
+      (i32.const 42)
+    )
+  )
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 1
+  ;; CHECK-NEXT:   (call $create-struct)
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create-other)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create-struct)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; We copy data between the types, but the possible values of their fields
+    ;; are the same anyhow, so we can optimize all the gets to 42.
+    (struct.set $struct 1
+      (call $create-struct)
+      (struct.get $other 1
+        (call $create-other)
+      )
+    )
+    (drop
+      (struct.get $struct 1
+        (call $create-struct)
+      )
+    )
+  )
+)
+
+;; As above, but each struct has a different value, so copying between them
+;; inhibits one optimization.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut f32)) (field (mut i32)) data))
+  (type $struct (struct (mut f32) (mut i32)))
+  ;; CHECK:      (type $other (struct_subtype (field (mut f64)) (field (mut i32)) data))
+  (type $other (struct (mut f64) (mut i32)))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$other| (func_subtype (result (ref $other)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create-struct (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (f32.const 0)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create-struct (result (ref $struct))
+    (struct.new $struct
+      (f32.const 0)
+      (i32.const 42)
+    )
+  )
+
+  ;; CHECK:      (func $create-other (type $none_=>_ref|$other|) (result (ref $other))
+  ;; CHECK-NEXT:  (struct.new $other
+  ;; CHECK-NEXT:   (f64.const 0)
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create-other (result (ref $other))
+    (struct.new $other
+      (f64.const 0)
+      (i32.const 1337) ;; this changed
+    )
+  )
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 1
+  ;; CHECK-NEXT:   (call $create-struct)
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create-other)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 1
+  ;; CHECK-NEXT:    (call $create-struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As this is not a copy between a struct and itself, we cannot optimize
+    ;; the last get lower down: $struct has both 42 and 1337 written to it.
+    (struct.set $struct 1
+      (call $create-struct)
+      (struct.get $other 1
+        (call $create-other)
+      )
+    )
+    (drop
+      (struct.get $struct 1
+        (call $create-struct)
+      )
+    )
+  )
+)
+
+;; Similar to the above, but different fields within the same struct.
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) (field (mut i32)) data))
+  (type $struct (struct (mut i32) (mut i32)))
+
+  ;; CHECK:      (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (func $create (type $none_=>_ref|$struct|) (result (ref $struct))
+  ;; CHECK-NEXT:  (struct.new $struct
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $create (result (ref $struct))
+    (struct.new $struct
+      (i32.const 42)
+      (i32.const 1337)
+    )
+  )
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (call $create)
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $create)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (call $create)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; The get from field 1 can be optimized to 1337, but field 0 has this
+    ;; write to it, which means it can contain 42 or 1337, so we cannot
+    ;; optimize.
+    (struct.set $struct 0
+      (call $create)
+      (struct.get $struct 1
+        (call $create)
+      )
+    )
+    (drop
+      (struct.get $struct 0
+        (call $create)
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $A (struct_subtype  data))
+  (type $A (struct))
+  (type $B (struct (ref $A)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global (ref $A) (struct.new_default $A))
+  (global $global (ref $A) (struct.new $A))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $global)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; An immutable global is the only thing written to this field, so we can
+    ;; propagate the value to the struct.get and replace it with a global.get.
+    (drop
+      (struct.get $B 0
+        (struct.new $B
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+;; As above, but with an imported global, which we can also optimize (since it
+;; is still immutable).
+(module
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (import "a" "b" (global $global i32))
+  (import "a" "b" (global $global i32))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $global)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global i32 (i32.const 42))
+  (global $global i32 (i32.const 42))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; An immutable global is the only thing written to this field, so we can
+    ;; propagate the value to the struct.get to get 42 here (even better than a
+    ;; global.get as in the last examples).
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global (mut i32) (i32.const 42))
+  (global $global (mut i32) (i32.const 42))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As above, but the global is *not* immutable. Still, it has no writes, so
+    ;; we can optimize.
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field i32) data))
+  (type $struct (struct i32))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global (mut i32) (i32.const 42))
+  (global $global (mut i32) (i32.const 42))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (global.set $global
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (global.get $global)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As above, but the global does have another write of another value, which
+    ;; prevents optimization.
+    (global.set $global
+      (i32.const 1337)
+    )
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global i32 (i32.const 42))
+  (global $global i32 (i32.const 42))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As above, but there is another set of the field. It writes the same
+    ;; value, though, so that is fine. Also, the struct's field is now mutable
+    ;; as well to allow that, and that also does not prevent optimization.
+    (struct.set $struct 0
+      (struct.new $struct
+        (global.get $global)
+      )
+      (i32.const 42)
+    )
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global i32 (i32.const 42))
+  (global $global i32 (i32.const 42))
+  ;; CHECK:      (global $global-2 i32 (i32.const 1337))
+  (global $global-2 i32 (i32.const 1337))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As above, but set a different global, which prevents optimization of the
+    ;; struct.get below.
+    (struct.set $struct 0
+      (struct.new $struct
+        (global.get $global)
+      )
+      (global.get $global-2)
+    )
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i32)) data))
+  (type $struct (struct (mut i32)))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (global $global i32 (i32.const 42))
+  (global $global i32 (i32.const 42))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (struct.set $struct 0
+  ;; CHECK-NEXT:   (struct.new $struct
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (struct.new $struct
+  ;; CHECK-NEXT:     (i32.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; As above, but set a constant, which means we are mixing constants with
+    ;; globals, which prevents the optimization of the struct.get.
+    (struct.set $struct 0
+      (struct.new $struct
+        (global.get $global)
+      )
+      (i32.const 1337)
+    )
+    (drop
+      (struct.get $struct 0
+        (struct.new $struct
+          (global.get $global)
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; Test a global type other than i32. Arrays of structs are a realistic case
+  ;; as they are used to implement itables.
+
+  ;; CHECK:      (type $vtable (struct_subtype (field funcref) data))
+  (type $vtable (struct funcref))
+
+  ;; CHECK:      (type $itable (array_subtype (ref $vtable) data))
+  (type $itable (array (ref $vtable)))
+
+  (type $object (struct (field $itable (ref $itable))))
+
+  ;; CHECK:      (type $none_=>_funcref (func_subtype (result funcref) func))
+
+  ;; CHECK:      (global $global (ref $itable) (array.init_static $itable
+  ;; CHECK-NEXT:  (struct.new $vtable
+  ;; CHECK-NEXT:   (ref.null nofunc)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.new $vtable
+  ;; CHECK-NEXT:   (ref.func $test)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: ))
+  (global $global (ref $itable) (array.init_static $itable
+    (struct.new $vtable
+      (ref.null func)
+    )
+    (struct.new $vtable
+      (ref.func $test)
+    )
+  ))
+
+  ;; CHECK:      (func $test (type $none_=>_funcref) (result funcref)
+  ;; CHECK-NEXT:  (struct.get $vtable 0
+  ;; CHECK-NEXT:   (array.get $itable
+  ;; CHECK-NEXT:    (global.get $global)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test (result funcref)
+    ;; Realistic usage of an itable: read an item from it, then a func from
+    ;; that, and return the value (all verifying that the types are correct
+    ;; after optimization).
+    ;;
+    ;; We optimize some of this, but stop at reading from the immutable global.
+    ;; To continue we'd need to track the fields of allocated objects, or look
+    ;; at immutable globals directly, neither of which we do yet. TODO
+    (struct.get $vtable 0
+      (array.get $itable
+        (struct.get $object $itable
+          (struct.new $object
+            (global.get $global)
+          )
+        )
+        (i32.const 1)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast
new file mode 100644
index 0000000..d9b11f7
--- /dev/null
+++ b/test/lit/passes/gufa.wast
@@ -0,0 +1,1116 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
+
+  ;; CHECK:      (type $i32_=>_i32 (func (param i32) (result i32)))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+
+  ;; CHECK:      (export "param-no" (func $param-no))
+
+  ;; CHECK:      (func $never-called (param $param i32) (result i32)
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $never-called (param $param i32) (result i32)
+    ;; This function is never called, so no content is possible in $param, and
+    ;; we know this must be unreachable code that can be removed (replaced with
+    ;; an unreachable).
+    (local.get $param)
+  )
+
+  ;; CHECK:      (func $foo (result i32)
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: )
+  (func $foo (result i32)
+    (i32.const 1)
+  )
+
+  ;; CHECK:      (func $bar
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (call $foo)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $bar
+    ;; Both arms of the select have identical values, 1. Inlining +
+    ;; OptimizeInstructions could of course discover that in this case, but
+    ;; GUFA can do so even without inlining. As a result the select will be
+    ;; dropped (due to the call which may have effects, we keep it), and at the
+    ;; end we emit the constant 1 for the value.
+    (drop
+      (select
+        (call $foo)
+        (i32.const 1)
+        (call $import)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $baz
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (block (result i32)
+  ;; CHECK-NEXT:     (drop
+  ;; CHECK-NEXT:      (call $foo)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (i32.eqz
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (call $import)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $baz
+    (drop
+      (select
+        (call $foo)
+        ;; As above, but replace 1 with eqz(eqz(1)).This pass assumes any eqz
+        ;; etc is a new value, and so here we do not optimize the select (we do
+        ;; still optimize the call's result, though).
+        (i32.eqz
+          (i32.eqz
+            (i32.const 1)
+          )
+        )
+        (call $import)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $return (result i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 2)
+  ;; CHECK-NEXT: )
+  (func $return (result i32)
+    ;; Helper function that returns one result in a return and flows another
+    ;; out. There is nothing to optimize in this function, but see the caller
+    ;; below.
+    (if
+      (i32.const 0)
+      (return
+        (i32.const 1)
+      )
+    )
+    (i32.const 2)
+  )
+
+  ;; CHECK:      (func $call-return
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $return)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-return
+    ;; The called function has two possible return values, so we cannot optimize
+    ;; anything here.
+    (drop
+      (call $return)
+    )
+  )
+
+  ;; CHECK:      (func $return-same (result i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: )
+  (func $return-same (result i32)
+    ;; Helper function that returns the same result in a return as it flows out.
+    ;; This is the same as above, but now the values are identical, and the
+    ;; function must return 1. There is nothing to optimize in this function,
+    ;; but see the caller below.
+    (if
+      (i32.const 0)
+      (return
+        (i32.const 1)
+      )
+    )
+    (i32.const 1)
+  )
+
+  ;; CHECK:      (func $call-return-same
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $return-same)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-return-same
+    ;; Unlike in $call-return, now we can optimize here.
+    (drop
+      (call $return-same)
+    )
+  )
+
+  ;; CHECK:      (func $local-no (result i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (call $import)
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.get $x)
+  ;; CHECK-NEXT: )
+  (func $local-no (result i32)
+    (local $x i32)
+    (if
+      (call $import)
+      (local.set $x
+        (i32.const 1)
+      )
+    )
+    ;; $x has two possible values, 1 and the default 0, so we cannot optimize
+    ;; anything here.
+    (local.get $x)
+  )
+
+  ;; CHECK:      (func $local-yes (result i32)
+  ;; CHECK-NEXT:  (local $x i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (call $import)
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $local-yes (result i32)
+    (local $x i32)
+    (if
+      (call $import)
+      (local.set $x
+        ;; As above, but now we set 0 here. We can optimize the local.get to 0
+        ;; in this case.
+        (i32.const 0)
+      )
+    )
+    (local.get $x)
+  )
+
+  ;; CHECK:      (func $param-no (param $param i32) (result i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $param)
+  ;; CHECK-NEXT:   (local.set $param
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.get $param)
+  ;; CHECK-NEXT: )
+  (func $param-no (export "param-no") (param $param i32) (result i32)
+    (if
+      (local.get $param)
+      (local.set $param
+        (i32.const 1)
+      )
+    )
+    ;; $x has two possible values, the incoming param value and 1, so we cannot
+    ;; optimize, since the function is exported - anything on the outside could
+    ;; call it with values we are not aware of during the optimization.
+    (local.get $param)
+  )
+
+  ;; CHECK:      (func $param-yes (param $param i32) (result i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (local.set $param
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: )
+  (func $param-yes (param $param i32) (result i32)
+    ;; As above, but now the function is not exported. That means it has no
+    ;; callers, so the first local.get can contain nothing, and will become an
+    ;; unreachable. The other local.get later down can only contain the
+    ;; local.set in the if, so we'll optimize it to 1.
+    (if
+      (local.get $param)
+      (local.set $param
+        (i32.const 1)
+      )
+    )
+    (local.get $param)
+  )
+
+  ;; CHECK:      (func $cycle (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $cycle
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (call $cycle
+  ;; CHECK-NEXT:        (i32.const 42)
+  ;; CHECK-NEXT:        (i32.const 1)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: )
+  (func $cycle (param $x i32) (param $y i32) (result i32)
+    ;; Return 42, or else the result from a recursive call. The only possible
+    ;; value is 42, which we can optimize to.
+    ;; (Nothing else calls $cycle, so this is dead code in actuality, but this
+    ;; pass leaves such things to other passes.)
+    ;; Note that the first call passes constants for $x and $y which lets us
+    ;; optimize them too (as we see no other contents arrive to them).
+    (drop
+      (call $cycle
+        (i32.const 42)
+        (i32.const 1)
+      )
+    )
+    (select
+      (i32.const 42)
+      (call $cycle
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $y)
+    )
+  )
+
+  ;; CHECK:      (func $cycle-2 (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $cycle-2
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (call $cycle-2
+  ;; CHECK-NEXT:        (i32.const 1)
+  ;; CHECK-NEXT:        (i32.const 1)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: )
+  (func $cycle-2 (param $x i32) (param $y i32) (result i32)
+    (drop
+      (call $cycle-2
+        (i32.const 42)
+        (i32.const 1)
+      )
+    )
+    ;; As above, but flip one $x and $y on the first and last local.gets. We
+    ;; can see that $y must contain 1, and we cannot infer a value for $x (it
+    ;; is sent both 42 and $y which is 1). Even without $x, however, we can see
+    ;; the value leaving the select is 42, which means the call returns 42.
+    (select
+      (i32.const 42)
+      (call $cycle-2
+        (local.get $y)
+        (local.get $y)
+      )
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $cycle-3 (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $cycle-3
+  ;; CHECK-NEXT:      (i32.const 1337)
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (call $cycle-3
+  ;; CHECK-NEXT:        (i32.eqz
+  ;; CHECK-NEXT:         (local.get $x)
+  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:        (i32.const 1)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 42)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: )
+  (func $cycle-3 (param $x i32) (param $y i32) (result i32)
+    ;; Even adding a caller with a different value for $x does not prevent us
+    ;; from optimizing here.
+    (drop
+      (call $cycle-3
+        (i32.const 1337)
+        (i32.const 1)
+      )
+    )
+    ;; As $cycle, but add an i32.eqz on $x. We can still optimize this, as
+    ;; while the eqz is a new value arriving in $x, we do not actually return
+    ;; $x, and again the only possible value flowing in the graph is 42.
+    (select
+      (i32.const 42)
+      (call $cycle-3
+        (i32.eqz
+          (local.get $x)
+        )
+        (local.get $y)
+      )
+      (local.get $y)
+    )
+  )
+
+  ;; CHECK:      (func $cycle-4 (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $cycle-4
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (select
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (call $cycle-4
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $cycle-4 (param $x i32) (param $y i32) (result i32)
+    (drop
+      (call $cycle-4
+        (i32.const 1337)
+        (i32.const 1)
+      )
+    )
+    (select
+      ;; As above, but we have no constant here, but $x. We may now return $x or
+      ;; $eqz of $x, which means we cannot infer the result of the call. (But we
+      ;; can still infer the value of $y, which is 1.)
+      (local.get $x)
+      (call $cycle-4
+        (i32.eqz
+          (local.get $x)
+        )
+        (local.get $y)
+      )
+      (local.get $y)
+    )
+  )
+
+  ;; CHECK:      (func $cycle-5 (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $cycle-5
+  ;; CHECK-NEXT:      (i32.const 1337)
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (drop
+  ;; CHECK-NEXT:       (call $cycle-5
+  ;; CHECK-NEXT:        (i32.const 1337)
+  ;; CHECK-NEXT:        (i32.const 1)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 1337)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 1337)
+  ;; CHECK-NEXT: )
+  (func $cycle-5 (param $x i32) (param $y i32) (result i32)
+    (drop
+      (call $cycle-5
+        (i32.const 1337)
+        (i32.const 1)
+      )
+    )
+    (select
+      (local.get $x)
+      (call $cycle-5
+        ;; As above, but now we return $x in both cases, so we can optimize, and
+        ;; infer the result is the 1337 which is passed in the earlier call.
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $y)
+    )
+  )
+
+  ;; CHECK:      (func $blocks
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block $named (result i32)
+  ;; CHECK-NEXT:      (if
+  ;; CHECK-NEXT:       (i32.const 0)
+  ;; CHECK-NEXT:       (br $named
+  ;; CHECK-NEXT:        (i32.const 1)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block $named0 (result i32)
+  ;; CHECK-NEXT:    (if
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:     (br $named0
+  ;; CHECK-NEXT:      (i32.const 2)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $blocks
+    ;; A block with a branch to it, which we can infer a constant for.
+    (drop
+      (block $named (result i32)
+        (if
+          (i32.const 0)
+          (br $named
+            (i32.const 1)
+          )
+        )
+        (i32.const 1)
+      )
+    )
+    ;; As above, but the two values reaching the block do not agree, so we
+    ;; should not optimize.
+    (drop
+      (block $named (result i32)
+        (if
+          (i32.const 0)
+          (br $named
+            (i32.const 2) ;; this changed
+          )
+        )
+        (i32.const 1)
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $i (func (param i32)))
+  (type $i (func (param i32)))
+
+  (table 10 funcref)
+  (elem (i32.const 0) funcref
+    (ref.func $reffed)
+  )
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (table $0 10 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $reffed)
+
+  ;; CHECK:      (export "table" (table $0))
+  (export "table" (table 0))
+
+  ;; CHECK:      (func $reffed (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed (param $x i32)
+    ;; This function is in the table, and the table is exported, so we cannot
+    ;; see all callers, and cannot infer the value here.
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $do-calls
+  ;; CHECK-NEXT:  (call $reffed
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $do-calls
+    (call $reffed
+      (i32.const 42)
+    )
+    (call_indirect (type $i)
+      (i32.const 42)
+      (i32.const 0)
+    )
+  )
+)
+
+;; As above, but the table is not exported. We have a direct and an indirect
+;; call with the same value, so we can optimize inside $reffed.
+(module
+  ;; CHECK:      (type $i (func (param i32)))
+  (type $i (func (param i32)))
+
+  (table 10 funcref)
+  (elem (i32.const 0) funcref
+    (ref.func $reffed)
+  )
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (table $0 10 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $reffed)
+
+  ;; CHECK:      (func $reffed (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $do-calls
+  ;; CHECK-NEXT:  (call $reffed
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $do-calls
+    (call $reffed
+      (i32.const 42)
+    )
+    (call_indirect (type $i)
+      (i32.const 42)
+      (i32.const 0)
+    )
+  )
+)
+
+;; As above but the only calls are indirect.
+(module
+  ;; CHECK:      (type $i (func (param i32)))
+  (type $i (func (param i32)))
+
+  (table 10 funcref)
+  (elem (i32.const 0) funcref
+    (ref.func $reffed)
+  )
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (table $0 10 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $reffed)
+
+  ;; CHECK:      (func $reffed (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $do-calls
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $do-calls
+    (call_indirect (type $i)
+      (i32.const 42)
+      (i32.const 0)
+    )
+    (call_indirect (type $i)
+      (i32.const 42)
+      (i32.const 0)
+    )
+  )
+)
+
+;; As above but the indirect calls have different parameters, so we do not
+;; optimize.
+(module
+  ;; CHECK:      (type $i (func (param i32)))
+  (type $i (func (param i32)))
+
+  (table 10 funcref)
+  (elem (i32.const 0) funcref
+    (ref.func $reffed)
+  )
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (table $0 10 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $reffed)
+
+  ;; CHECK:      (func $reffed (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $do-calls
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 1337)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $do-calls
+    (call_indirect (type $i)
+      (i32.const 42)
+      (i32.const 0)
+    )
+    (call_indirect (type $i)
+      (i32.const 1337)
+      (i32.const 0)
+    )
+  )
+)
+
+;; As above but the second call is of another function type, so it does not
+;; prevent us from optimizing even though it has a different value.
+(module
+  ;; CHECK:      (type $i (func (param i32)))
+  (type $i (func (param i32)))
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $f (func (param f32)))
+  (type $f (func (param f32)))
+
+  (table 10 funcref)
+  (elem (i32.const 0) funcref
+    (ref.func $reffed)
+  )
+
+  ;; CHECK:      (table $0 10 funcref)
+
+  ;; CHECK:      (elem (i32.const 0) $reffed)
+
+  ;; CHECK:      (func $reffed (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $reffed (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $do-calls
+  ;; CHECK-NEXT:  (call_indirect $0 (type $i)
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_indirect $0 (type $f)
+  ;; CHECK-NEXT:   (f32.const 1337)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $do-calls
+    (call_indirect (type $i)
+      (i32.const 42)
+      (i32.const 0)
+    )
+    (call_indirect (type $f)
+      (f32.const 1337)
+      (i32.const 0)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (func $const (result i32)
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: )
+  (func $const (result i32)
+    ;; Return a const to the caller below.
+    (i32.const 42)
+  )
+
+  ;; CHECK:      (func $retcall (result i32)
+  ;; CHECK-NEXT:  (return_call $const)
+  ;; CHECK-NEXT: )
+  (func $retcall (result i32)
+    ;; Do a return call. This tests that we pass its value out as a result.
+    (return_call $const)
+  )
+
+  ;; CHECK:      (func $caller
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $retcall)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $caller
+    ;; Call the return caller. We can optimize this value to 42.
+    (drop
+      (call $retcall)
+    )
+  )
+)
+
+;; Imports have unknown values.
+(module
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (import "a" "b" (func $import (result i32)))
+  (import "a" "b" (func $import (result i32)))
+
+  ;; CHECK:      (func $internal (result i32)
+  ;; CHECK-NEXT:  (i32.const 42)
+  ;; CHECK-NEXT: )
+  (func $internal (result i32)
+    (i32.const 42)
+  )
+
+  ;; CHECK:      (func $calls
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $import)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (call $internal)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $calls
+    (drop
+      (call $import)
+    )
+    ;; For comparison, we can optimize this call to an internal function.
+    (drop
+      (call $internal)
+    )
+  )
+)
+
+;; Test for nontrivial code in a global init. We need to process such code
+;; normally and not hit any internal asserts (nothing can be optimized here).
+(module
+  ;; CHECK:      (global $global$0 i32 (i32.add
+  ;; CHECK-NEXT:  (i32.const 10)
+  ;; CHECK-NEXT:  (i32.const 20)
+  ;; CHECK-NEXT: ))
+  (global $global$0 i32
+    (i32.add
+      (i32.const 10)
+      (i32.const 20)
+    )
+  )
+)
+
+;; The call.without.effects intrinsic does a call to the reference given to it,
+;; but for now other imports do not (until we add a flag for closed-world).
+(module
+  ;; CHECK:      (type $A (func (param i32)))
+  (type $A (func (param i32)))
+
+  ;; CHECK:      (type $i32_funcref_=>_none (func (param i32 funcref)))
+
+  ;; CHECK:      (type $funcref_=>_none (func (param funcref)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param i32 funcref)))
+  (import "binaryen-intrinsics" "call.without.effects"
+    (func $call-without-effects (param i32 funcref)))
+
+  ;; CHECK:      (import "other" "import" (func $other-import (param funcref)))
+  (import "other" "import"
+    (func $other-import (param funcref)))
+
+  ;; CHECK:      (elem declare func $target-drop $target-keep)
+
+  ;; CHECK:      (export "foo" (func $foo))
+
+  ;; CHECK:      (func $foo
+  ;; CHECK-NEXT:  (call $call-without-effects
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (ref.func $target-keep)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $other-import
+  ;; CHECK-NEXT:   (ref.func $target-drop)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo (export "foo")
+    ;; Calling the intrinsic with a reference is considered a call of the
+    ;; reference, so $target-keep's code is reachable. We should leave it alone,
+    ;; but we can put an unreachable in $target-drop.
+    (call $call-without-effects
+     (i32.const 1)
+     (ref.func $target-keep)
+    )
+    ;; The other import is not enough to keep $target-drop alive.
+    (call $other-import
+      (ref.func $target-drop)
+    )
+  )
+
+  ;; CHECK:      (func $target-keep (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $target-keep (type $A) (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $target-drop (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $target-drop (type $A) (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+)
+
+;; As above, but now the call to the intrinsic does not let us see the exact
+;; function being called.
+(module
+  ;; CHECK:      (type $A (func (param i32)))
+  (type $A (func (param i32)))
+
+  ;; CHECK:      (type $i32_funcref_=>_none (func (param i32 funcref)))
+
+  ;; CHECK:      (type $funcref_=>_none (func (param funcref)))
+
+  ;; CHECK:      (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+  ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param i32 funcref)))
+  (import "binaryen-intrinsics" "call.without.effects"
+    (func $call-without-effects (param i32 funcref)))
+
+  ;; CHECK:      (import "other" "import" (func $other-import (param funcref)))
+  (import "other" "import"
+    (func $other-import (param funcref)))
+
+  ;; CHECK:      (elem declare func $target-keep $target-keep-2)
+
+  ;; CHECK:      (export "foo" (func $foo))
+
+  ;; CHECK:      (func $foo (param $A (ref null $A))
+  ;; CHECK-NEXT:  (call $call-without-effects
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (local.get $A)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.func $target-keep)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $other-import
+  ;; CHECK-NEXT:   (ref.func $target-keep-2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo (export "foo") (param $A (ref null $A))
+    ;; Call the intrinsic without a RefFunc. All we infer here is the type,
+    ;; which means we must assume anything with type $A (and a reference) can be
+    ;; called, which will keep alive the bodies of both $target-keep and
+    ;; $target-keep-2 - no unreachables will be placed in either one.
+    (call $call-without-effects
+      (i32.const 1)
+      (local.get $A)
+    )
+    (drop
+      (ref.func $target-keep)
+    )
+    (call $other-import
+      (ref.func $target-keep-2)
+    )
+  )
+
+  ;; CHECK:      (func $target-keep (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $target-keep (type $A) (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $target-keep-2 (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $target-keep-2 (type $A) (param $x i32)
+    (drop
+      (local.get $x)
+    )
+  )
+)
+
+;; Exported mutable globals can contain any value, as the outside can write to
+;; them.
+(module
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (global $exported-mutable (mut i32) (i32.const 42))
+  (global $exported-mutable (mut i32) (i32.const 42))
+  ;; CHECK:      (global $exported-immutable i32 (i32.const 42))
+  (global $exported-immutable i32 (i32.const 42))
+  ;; CHECK:      (global $mutable (mut i32) (i32.const 42))
+  (global $mutable (mut i32) (i32.const 42))
+  ;; CHECK:      (global $immutable i32 (i32.const 42))
+  (global $immutable i32 (i32.const 42))
+
+  ;; CHECK:      (export "exported-mutable" (global $exported-mutable))
+  (export "exported-mutable" (global $exported-mutable))
+  ;; CHECK:      (export "exported-immutable" (global $exported-immutable))
+  (export "exported-immutable" (global $exported-immutable))
+
+  ;; CHECK:      (func $test
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $exported-mutable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 42)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    ;; This should not be optimized to a constant.
+    (drop
+      (global.get $exported-mutable)
+    )
+    ;; All the rest can be optimized.
+    (drop
+      (global.get $exported-immutable)
+    )
+    (drop
+      (global.get $mutable)
+    )
+    (drop
+      (global.get $immutable)
+    )
+  )
+)
diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast
index ebe5d1d..aace567 100644
--- a/test/lit/passes/heap2local.wast
+++ b/test/lit/passes/heap2local.wast
@@ -11,19 +11,13 @@
 
   ;; CHECK:      (type $struct.recursive (struct (field (mut (ref null $struct.recursive)))))
 
-  ;; CHECK:      (type $struct.nonnullable (struct (field (ref $struct.A))))
-
   ;; CHECK:      (type $struct.packed (struct (field (mut i8))))
   ;; NOMNL:      (type $struct.recursive (struct_subtype (field (mut (ref null $struct.recursive))) data))
 
-  ;; NOMNL:      (type $struct.nonnullable (struct_subtype (field (ref $struct.A)) data))
-
   ;; NOMNL:      (type $struct.packed (struct_subtype (field (mut i8)) data))
   (type $struct.packed (struct (field (mut i8))))
 
-  ;; CHECK:      (type $struct.nondefaultable (struct (field (rtt $struct.A))))
-  ;; NOMNL:      (type $struct.nondefaultable (struct_subtype (field (rtt $struct.A)) data))
-  (type $struct.nondefaultable (struct (field (rtt $struct.A))))
+  (type $struct.nondefaultable (struct (field (ref $struct.A))))
 
   (type $struct.recursive (struct (field (mut (ref null $struct.recursive)))))
 
@@ -33,17 +27,14 @@
   ;; CHECK-NEXT:  (local $0 i32)
   ;; CHECK-NEXT:  (local $1 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $0
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -51,17 +42,14 @@
   ;; NOMNL-NEXT:  (local $0 i32)
   ;; NOMNL-NEXT:  (local $1 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $0
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -69,9 +57,7 @@
     ;; Other passes can remove such a trivial case of an unused allocation, but
     ;; we still optimize it.
     (drop
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
   )
 
@@ -80,17 +66,14 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -99,17 +82,14 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -120,9 +100,7 @@
     ;; drop (and adding some unnecessary code to allocate the values, which we
     ;; depend on other passes to remove).
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
   )
 
@@ -132,17 +110,14 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $0
   ;; CHECK-NEXT:       (i32.const 0)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.set $1
   ;; CHECK-NEXT:       (f64.const 0)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $0)
@@ -155,17 +130,14 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $0
   ;; NOMNL-NEXT:       (i32.const 0)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.set $1
   ;; NOMNL-NEXT:       (f64.const 0)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $0)
@@ -180,9 +152,7 @@
     ;; locals, and we read from the locals instead of the struct.get.
     (drop
       (struct.get $struct.A 0
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
   )
@@ -193,17 +163,14 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $0
   ;; CHECK-NEXT:       (i32.const 0)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.set $1
   ;; CHECK-NEXT:       (f64.const 0)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $1)
@@ -216,17 +183,14 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result f64)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $0
   ;; NOMNL-NEXT:       (i32.const 0)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.set $1
   ;; NOMNL-NEXT:       (f64.const 0)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $1)
@@ -237,9 +201,7 @@
     ;; Similar to the above, but using a different field index.
     (drop
       (struct.get $struct.A 1
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
   )
@@ -248,17 +210,14 @@
   ;; CHECK-NEXT:  (local $0 i32)
   ;; CHECK-NEXT:  (local $1 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $0
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $0
@@ -269,17 +228,14 @@
   ;; NOMNL-NEXT:  (local $0 i32)
   ;; NOMNL-NEXT:  (local $1 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $0
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (local.set $0
@@ -289,9 +245,7 @@
   (func $one-set
     ;; A simple optimizable allocation only used in one set.
     (struct.set $struct.A 0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
       (i32.const 1)
     )
   )
@@ -299,18 +253,14 @@
   ;; CHECK:      (func $packed
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get_u $struct.packed 0
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct.packed
-  ;; CHECK-NEXT:     (rtt.canon $struct.packed)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct.packed)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $packed (type $none_=>_none)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (struct.get_u $struct.packed 0
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct.packed
-  ;; NOMNL-NEXT:     (rtt.canon $struct.packed)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (struct.new_default $struct.packed)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -318,9 +268,7 @@
     ;; We do not optimize packed structs yet.
     (drop
       (struct.get $struct.packed 0
-        (struct.new_default_with_rtt $struct.packed
-          (rtt.canon $struct.packed)
-        )
+        (struct.new_default $struct.packed)
       )
     )
   )
@@ -333,7 +281,7 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $2
   ;; CHECK-NEXT:       (i32.const 2)
   ;; CHECK-NEXT:      )
@@ -346,10 +294,7 @@
   ;; CHECK-NEXT:      (local.set $1
   ;; CHECK-NEXT:       (local.get $3)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $0)
@@ -364,7 +309,7 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $2
   ;; NOMNL-NEXT:       (i32.const 2)
   ;; NOMNL-NEXT:      )
@@ -377,10 +322,7 @@
   ;; NOMNL-NEXT:      (local.set $1
   ;; NOMNL-NEXT:       (local.get $3)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $0)
@@ -392,10 +334,9 @@
     ;; proper locals.
     (drop
       (struct.get $struct.A 0
-        (struct.new_with_rtt $struct.A
+        (struct.new $struct.A
           (i32.const 2)
           (f64.const 3.14159)
-          (rtt.canon $struct.A)
         )
       )
     )
@@ -412,11 +353,10 @@
   ;; CHECK-NEXT:      (drop
   ;; CHECK-NEXT:       (unreachable)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (unreachable)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -431,11 +371,10 @@
   ;; NOMNL-NEXT:      (drop
   ;; NOMNL-NEXT:       (unreachable)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:      (unreachable)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (unreachable)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -444,43 +383,60 @@
     ;; remove it.
     (drop
       (struct.get $struct.A 0
-        (struct.new_with_rtt $struct.A
+        (struct.new $struct.A
           (i32.const 2)
           (unreachable)
-          (rtt.canon $struct.A)
         )
       )
     )
   )
 
   ;; CHECK:      (func $nondefaultable
+  ;; CHECK-NEXT:  (local $0 (ref $struct.A))
+  ;; CHECK-NEXT:  (local $1 (ref $struct.A))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.get $struct.nondefaultable 0
-  ;; CHECK-NEXT:    (struct.new_with_rtt $struct.nondefaultable
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:     (rtt.canon $struct.nondefaultable)
+  ;; CHECK-NEXT:   (block (result (ref $struct.A))
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (block (result nullref)
+  ;; CHECK-NEXT:      (local.set $1
+  ;; CHECK-NEXT:       (struct.new_default $struct.A)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (local.set $0
+  ;; CHECK-NEXT:       (local.get $1)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:      (ref.null none)
+  ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $nondefaultable (type $none_=>_none)
+  ;; NOMNL-NEXT:  (local $0 (ref $struct.A))
+  ;; NOMNL-NEXT:  (local $1 (ref $struct.A))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (struct.get $struct.nondefaultable 0
-  ;; NOMNL-NEXT:    (struct.new_with_rtt $struct.nondefaultable
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:     (rtt.canon $struct.nondefaultable)
+  ;; NOMNL-NEXT:   (block (result (ref $struct.A))
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (block (result nullref)
+  ;; NOMNL-NEXT:      (local.set $1
+  ;; NOMNL-NEXT:       (struct.new_default $struct.A)
+  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:      (local.set $0
+  ;; NOMNL-NEXT:       (local.get $1)
+  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:      (ref.null none)
+  ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (local.get $0)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $nondefaultable
-    ;; We do not optimize structs with nondefaultable types that we cannot
-    ;; handle, like rtts.
+    ;; The non-nullable types here can fit in locals.
     (drop
       (struct.get $struct.nondefaultable 0
-        (struct.new_with_rtt $struct.nondefaultable
-          (rtt.canon $struct.A)
-          (rtt.canon $struct.nondefaultable)
+        (struct.new $struct.nondefaultable
+          (struct.new_default $struct.A)
         )
       )
     )
@@ -491,22 +447,19 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.set $1
   ;; CHECK-NEXT:    (i32.const 1)
@@ -518,22 +471,19 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.set $1
   ;; NOMNL-NEXT:    (i32.const 1)
@@ -546,9 +496,7 @@
     ;; to a local. The local.set should not prevent our optimization, and the
     ;; local.set can be turned into a drop.
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.set $struct.A 0
       (local.get $ref)
@@ -561,22 +509,19 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $2)
   ;; CHECK-NEXT:  )
@@ -586,22 +531,19 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $2)
   ;; NOMNL-NEXT:  )
@@ -609,9 +551,7 @@
   (func $simple-one-local-get (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; A simple optimizable allocation only used in one get, via a local.
     (struct.get $struct.A 1
@@ -633,25 +573,22 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.null $struct.A)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $2)
   ;; CHECK-NEXT:  )
@@ -661,25 +598,22 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.null $struct.A)
+  ;; NOMNL-NEXT:   (ref.null none)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $2)
   ;; NOMNL-NEXT:  )
@@ -687,9 +621,7 @@
   (func $safe-to-drop (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; An extra drop does not let the allocation escape.
     (drop
@@ -703,9 +635,7 @@
   ;; CHECK:      (func $escape-via-call (result f64)
   ;; CHECK-NEXT:  (local $ref (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $ref
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (call $send-ref
   ;; CHECK-NEXT:   (local.get $ref)
@@ -717,9 +647,7 @@
   ;; NOMNL:      (func $escape-via-call (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $ref
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (call $send-ref
   ;; NOMNL-NEXT:   (local.get $ref)
@@ -731,9 +659,7 @@
   (func $escape-via-call (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; The allocation escapes into a call.
     (call $send-ref
@@ -749,29 +675,26 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref null $struct.A))
   ;; CHECK-NEXT:    (block (result (ref null $struct.A))
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $2)
   ;; CHECK-NEXT:  )
@@ -781,29 +704,26 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
   ;; NOMNL-NEXT:    (block (result (ref null $struct.A))
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $2)
   ;; NOMNL-NEXT:  )
@@ -811,9 +731,7 @@
   (func $safe-to-drop-multiflow (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; An extra drop + multiple flows through things do not stop us.
     (drop
@@ -833,9 +751,7 @@
   ;; CHECK:      (func $escape-after-multiflow (result f64)
   ;; CHECK-NEXT:  (local $ref (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $ref
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (call $send-ref
   ;; CHECK-NEXT:   (block (result (ref null $struct.A))
@@ -851,9 +767,7 @@
   ;; NOMNL:      (func $escape-after-multiflow (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $ref
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (call $send-ref
   ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
@@ -869,9 +783,7 @@
   (func $escape-after-multiflow (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; An escape after multiple flows.
     (call $send-ref
@@ -892,12 +804,8 @@
   ;; CHECK-NEXT:  (local $ref (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $ref
   ;; CHECK-NEXT:   (select (result (ref $struct.A))
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct.A)
+  ;; CHECK-NEXT:    (struct.new_default $struct.A)
   ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -909,12 +817,8 @@
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $ref
   ;; NOMNL-NEXT:   (select (result (ref $struct.A))
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (struct.new_default $struct.A)
+  ;; NOMNL-NEXT:    (struct.new_default $struct.A)
   ;; NOMNL-NEXT:    (i32.const 1)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
@@ -928,12 +832,8 @@
     ;; to optimize it.
     (local.set $ref
       (select
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
+        (struct.new_default $struct.A)
         (i32.const 1)
       )
     )
@@ -947,25 +847,22 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.null $struct.A)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $2)
   ;; CHECK-NEXT:  )
@@ -975,25 +872,22 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.null $struct.A)
+  ;; NOMNL-NEXT:   (ref.null none)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $2)
   ;; NOMNL-NEXT:  )
@@ -1001,9 +895,7 @@
   (func $local-copies (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; Copying our allocation through locals does not bother us.
     (local.set $ref
@@ -1020,26 +912,23 @@
   ;; CHECK-NEXT:  (local $2 i32)
   ;; CHECK-NEXT:  (local $3 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $3
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.null $struct.A)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $2)
   ;; CHECK-NEXT:   )
@@ -1047,7 +936,7 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $3)
   ;; CHECK-NEXT:   )
@@ -1059,26 +948,23 @@
   ;; NOMNL-NEXT:  (local $2 i32)
   ;; NOMNL-NEXT:  (local $3 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $3
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.null $struct.A)
+  ;; NOMNL-NEXT:   (ref.null none)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $2)
   ;; NOMNL-NEXT:   )
@@ -1086,7 +972,7 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result f64)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $3)
   ;; NOMNL-NEXT:   )
@@ -1096,9 +982,7 @@
     (local $ref (ref null $struct.A))
     (local $ref-2 (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; Copying our allocation through locals does not bother us, even if it's
     ;; another local.
@@ -1122,28 +1006,25 @@
   ;; CHECK-NEXT:  (local $2 i32)
   ;; CHECK-NEXT:  (local $3 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $3
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $3)
   ;; CHECK-NEXT:  )
@@ -1153,28 +1034,25 @@
   ;; NOMNL-NEXT:  (local $2 i32)
   ;; NOMNL-NEXT:  (local $3 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $3
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (if
   ;; NOMNL-NEXT:   (local.get $x)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $3)
   ;; NOMNL-NEXT:  )
@@ -1182,9 +1060,7 @@
   (func $local-copies-conditional (param $x i32) (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; Possibly copying our allocation through locals does not bother us. Note
     ;; that as a result of this the final local.get has two sets that send it
@@ -1204,26 +1080,23 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (block (result (ref null $struct.A))
   ;; CHECK-NEXT:     (call $send-ref
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $2)
@@ -1234,26 +1107,23 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
   ;; NOMNL-NEXT:   (drop
   ;; NOMNL-NEXT:    (block (result (ref null $struct.A))
   ;; NOMNL-NEXT:     (call $send-ref
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $2)
@@ -1262,9 +1132,7 @@
   (func $block-value (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; Returning our allocation from a block does not bother us.
     (struct.get $struct.A 1
@@ -1281,14 +1149,12 @@
   ;; CHECK:      (func $non-exclusive-get (param $x i32) (result f64)
   ;; CHECK-NEXT:  (local $ref (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $ref
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:   (local.set $ref
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.get $struct.A 1
@@ -1298,14 +1164,12 @@
   ;; NOMNL:      (func $non-exclusive-get (type $i32_=>_f64) (param $x i32) (result f64)
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $ref
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (if
   ;; NOMNL-NEXT:   (local.get $x)
   ;; NOMNL-NEXT:   (local.set $ref
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.get $struct.A 1
@@ -1315,9 +1179,7 @@
   (func $non-exclusive-get (param $x i32) (result f64)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (if (local.get $x)
       (local.set $ref
@@ -1336,17 +1198,14 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.get $1)
@@ -1356,17 +1215,14 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (local.get $1)
@@ -1376,9 +1232,7 @@
     (struct.get $struct.A 0
       ;; A tee flows out the value, and we can optimize this allocation.
       (local.tee $ref
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
   )
@@ -1387,36 +1241,30 @@
   ;; CHECK-NEXT:  (local $ref (ref null $struct.recursive))
   ;; CHECK-NEXT:  (local $1 (ref null $struct.recursive))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.recursive))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
-  ;; CHECK-NEXT:     (ref.null $struct.recursive)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.recursive)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.recursive)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $1
-  ;; CHECK-NEXT:   (ref.null $struct.recursive)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $tee-set (type $none_=>_none)
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.recursive))
   ;; NOMNL-NEXT:  (local $1 (ref null $struct.recursive))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.recursive))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
-  ;; NOMNL-NEXT:     (ref.null $struct.recursive)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.recursive)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.recursive)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (local.set $1
-  ;; NOMNL-NEXT:   (ref.null $struct.recursive)
+  ;; NOMNL-NEXT:   (ref.null none)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $tee-set
@@ -1424,46 +1272,38 @@
     ;; As above, but with a set, and also a recursive type.
     (struct.set $struct.recursive 0
       (local.tee $ref
-        (struct.new_default_with_rtt $struct.recursive
-          (rtt.canon $struct.recursive)
-        )
+        (struct.new_default $struct.recursive)
       )
       (ref.null $struct.recursive)
     )
   )
 
-  ;; CHECK:      (func $set-value
+  ;; CHECK:      (func $set-value (param $struct.recursive (ref null $struct.recursive))
   ;; CHECK-NEXT:  (local $ref (ref null $struct.recursive))
   ;; CHECK-NEXT:  (struct.set $struct.recursive 0
-  ;; CHECK-NEXT:   (ref.null $struct.recursive)
+  ;; CHECK-NEXT:   (local.get $struct.recursive)
   ;; CHECK-NEXT:   (local.tee $ref
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct.recursive
-  ;; CHECK-NEXT:     (rtt.canon $struct.recursive)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct.recursive)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $set-value (type $none_=>_none)
+  ;; NOMNL:      (func $set-value (type $ref?|$struct.recursive|_=>_none) (param $struct.recursive (ref null $struct.recursive))
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.recursive))
   ;; NOMNL-NEXT:  (struct.set $struct.recursive 0
-  ;; NOMNL-NEXT:   (ref.null $struct.recursive)
+  ;; NOMNL-NEXT:   (local.get $struct.recursive)
   ;; NOMNL-NEXT:   (local.tee $ref
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct.recursive
-  ;; NOMNL-NEXT:     (rtt.canon $struct.recursive)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (struct.new_default $struct.recursive)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
-  (func $set-value
+  (func $set-value (param $struct.recursive (ref null $struct.recursive))
     (local $ref (ref null $struct.recursive))
     (struct.set $struct.recursive 0
-      (ref.null $struct.recursive)
+      (local.get $struct.recursive)
       ;; As above, but operands reversed: the allocation is now the value, not
       ;; the reference, and so it escapes.
       (local.tee $ref
-        (struct.new_default_with_rtt $struct.recursive
-          (rtt.canon $struct.recursive)
-        )
+        (struct.new_default $struct.recursive)
       )
     )
   )
@@ -1473,25 +1313,20 @@
   ;; CHECK-NEXT:  (local $1 (ref null $struct.recursive))
   ;; CHECK-NEXT:  (local $2 (ref null $struct.recursive))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.recursive))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $2
-  ;; CHECK-NEXT:     (struct.new_default_with_rtt $struct.recursive
-  ;; CHECK-NEXT:      (rtt.canon $struct.recursive)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (struct.new_default $struct.recursive)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (local.get $2)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.recursive)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.recursive)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref null $struct.recursive))
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.recursive)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $1)
   ;; CHECK-NEXT:   )
@@ -1502,25 +1337,20 @@
   ;; NOMNL-NEXT:  (local $1 (ref null $struct.recursive))
   ;; NOMNL-NEXT:  (local $2 (ref null $struct.recursive))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.recursive))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $2
-  ;; NOMNL-NEXT:     (struct.new_default_with_rtt $struct.recursive
-  ;; NOMNL-NEXT:      (rtt.canon $struct.recursive)
-  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:     (struct.new_default $struct.recursive)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (local.get $2)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.recursive)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.recursive)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result (ref null $struct.recursive))
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.recursive)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $1)
   ;; NOMNL-NEXT:   )
@@ -1530,7 +1360,7 @@
     (local $0 (ref null $struct.recursive))
     (local.set $0
       ;; The outer allocation can be optimized, as it does not escape.
-      (struct.new_with_rtt $struct.recursive
+      (struct.new $struct.recursive
         ;; The inner allocation should not prevent the outer one from being
         ;; optimized through some form of confusion.
         ;; After the outer one is optimized, the inner one can be optimized in
@@ -1538,10 +1368,7 @@
         ;; on other optimizations to actually remove the outer allocation (like
         ;; vacuum), and so it cannot be optimized. If we ran vaccum, and then
         ;; additional iterations, this might be handled.
-        (struct.new_default_with_rtt $struct.recursive
-          (rtt.canon $struct.recursive)
-        )
-        (rtt.canon $struct.recursive)
+        (struct.new_default $struct.recursive)
       )
     )
     (drop
@@ -1555,9 +1382,7 @@
   ;; CHECK-NEXT:  (local $ref (ref null $struct.A))
   ;; CHECK-NEXT:  (struct.set $struct.A 0
   ;; CHECK-NEXT:   (local.tee $ref
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct.A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
@@ -1567,9 +1392,7 @@
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.A))
   ;; NOMNL-NEXT:  (struct.set $struct.A 0
   ;; NOMNL-NEXT:   (local.tee $ref
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (struct.new_default $struct.A)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (i32.const 1)
   ;; NOMNL-NEXT:  )
@@ -1579,9 +1402,7 @@
     (local $ref (ref null $struct.A))
     (struct.set $struct.A 0
       (local.tee $ref
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
       (i32.const 1)
     )
@@ -1593,9 +1414,7 @@
   ;; CHECK-NEXT:  (local $ref (ref null $struct.A))
   ;; CHECK-NEXT:  (struct.set $struct.A 0
   ;; CHECK-NEXT:   (local.tee $ref
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct.A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
@@ -1607,9 +1426,7 @@
   ;; NOMNL-NEXT:  (local $ref (ref null $struct.A))
   ;; NOMNL-NEXT:  (struct.set $struct.A 0
   ;; NOMNL-NEXT:   (local.tee $ref
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (struct.new_default $struct.A)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (i32.const 1)
   ;; NOMNL-NEXT:  )
@@ -1621,9 +1438,7 @@
     (local $ref (ref null $struct.A))
     (struct.set $struct.A 0
       (local.tee $ref
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
       (i32.const 1)
     )
@@ -1634,56 +1449,42 @@
   )
 
   ;; CHECK:      (func $non-nullable (param $a (ref $struct.A))
-  ;; CHECK-NEXT:  (local $1 (ref null $struct.A))
-  ;; CHECK-NEXT:  (local $2 (ref null $struct.A))
+  ;; CHECK-NEXT:  (local $1 (ref $struct.A))
+  ;; CHECK-NEXT:  (local $2 (ref $struct.A))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref $struct.A))
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.nonnullable))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $2
   ;; CHECK-NEXT:       (local.get $a)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.set $1
-  ;; CHECK-NEXT:       (ref.as_non_null
-  ;; CHECK-NEXT:        (local.get $2)
-  ;; CHECK-NEXT:       )
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.nonnullable)
+  ;; CHECK-NEXT:       (local.get $2)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.nonnullable)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.as_non_null
-  ;; CHECK-NEXT:     (local.get $1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $non-nullable (type $ref|$struct.A|_=>_none) (param $a (ref $struct.A))
-  ;; NOMNL-NEXT:  (local $1 (ref null $struct.A))
-  ;; NOMNL-NEXT:  (local $2 (ref null $struct.A))
+  ;; NOMNL-NEXT:  (local $1 (ref $struct.A))
+  ;; NOMNL-NEXT:  (local $2 (ref $struct.A))
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result (ref $struct.A))
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.nonnullable))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $2
   ;; NOMNL-NEXT:       (local.get $a)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.set $1
-  ;; NOMNL-NEXT:       (ref.as_non_null
-  ;; NOMNL-NEXT:        (local.get $2)
-  ;; NOMNL-NEXT:       )
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.nonnullable)
+  ;; NOMNL-NEXT:       (local.get $2)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.nonnullable)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.as_non_null
-  ;; NOMNL-NEXT:     (local.get $1)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (local.get $1)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -1692,9 +1493,8 @@
       ;; An optimizable case where the type is non-nullable, which requires
       ;; special handling in locals.
       (struct.get $struct.nonnullable 0
-        (struct.new_with_rtt $struct.nonnullable
+        (struct.new $struct.nonnullable
           (local.get $a)
-          (rtt.canon $struct.nonnullable)
         )
       )
     )
@@ -1708,7 +1508,7 @@
   ;; CHECK-NEXT:  (local $5 f64)
   ;; CHECK-NEXT:  (loop $outer
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:    (block (result nullref)
   ;; CHECK-NEXT:     (local.set $4
   ;; CHECK-NEXT:      (i32.const 2)
   ;; CHECK-NEXT:     )
@@ -1721,16 +1521,13 @@
   ;; CHECK-NEXT:     (local.set $3
   ;; CHECK-NEXT:      (local.get $5)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (rtt.canon $struct.A)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (local.get $2)
   ;; CHECK-NEXT:    )
@@ -1740,14 +1537,14 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (block (result f64)
   ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (ref.null $struct.A)
+  ;; CHECK-NEXT:       (ref.null none)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.get $3)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (block
   ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (local.set $3
   ;; CHECK-NEXT:      (f64.const 42)
@@ -1757,13 +1554,13 @@
   ;; CHECK-NEXT:   (loop $inner
   ;; CHECK-NEXT:    (block
   ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (local.set $2
   ;; CHECK-NEXT:      (i32.add
   ;; CHECK-NEXT:       (block (result i32)
   ;; CHECK-NEXT:        (drop
-  ;; CHECK-NEXT:         (ref.null $struct.A)
+  ;; CHECK-NEXT:         (ref.null none)
   ;; CHECK-NEXT:        )
   ;; CHECK-NEXT:        (local.get $2)
   ;; CHECK-NEXT:       )
@@ -1780,7 +1577,7 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (ref.null $struct.A)
+  ;; CHECK-NEXT:       (ref.null none)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.get $2)
   ;; CHECK-NEXT:     )
@@ -1788,7 +1585,7 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (block (result f64)
   ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (ref.null $struct.A)
+  ;; CHECK-NEXT:       (ref.null none)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.get $3)
   ;; CHECK-NEXT:     )
@@ -1805,7 +1602,7 @@
   ;; NOMNL-NEXT:  (local $5 f64)
   ;; NOMNL-NEXT:  (loop $outer
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:    (block (result nullref)
   ;; NOMNL-NEXT:     (local.set $4
   ;; NOMNL-NEXT:      (i32.const 2)
   ;; NOMNL-NEXT:     )
@@ -1818,16 +1615,13 @@
   ;; NOMNL-NEXT:     (local.set $3
   ;; NOMNL-NEXT:      (local.get $5)
   ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (drop
-  ;; NOMNL-NEXT:      (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (drop
   ;; NOMNL-NEXT:    (block (result i32)
   ;; NOMNL-NEXT:     (drop
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:     (local.get $2)
   ;; NOMNL-NEXT:    )
@@ -1837,14 +1631,14 @@
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (block (result f64)
   ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (ref.null $struct.A)
+  ;; NOMNL-NEXT:       (ref.null none)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.get $3)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (block
   ;; NOMNL-NEXT:     (drop
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:     (local.set $3
   ;; NOMNL-NEXT:      (f64.const 42)
@@ -1854,13 +1648,13 @@
   ;; NOMNL-NEXT:   (loop $inner
   ;; NOMNL-NEXT:    (block
   ;; NOMNL-NEXT:     (drop
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:     (local.set $2
   ;; NOMNL-NEXT:      (i32.add
   ;; NOMNL-NEXT:       (block (result i32)
   ;; NOMNL-NEXT:        (drop
-  ;; NOMNL-NEXT:         (ref.null $struct.A)
+  ;; NOMNL-NEXT:         (ref.null none)
   ;; NOMNL-NEXT:        )
   ;; NOMNL-NEXT:        (local.get $2)
   ;; NOMNL-NEXT:       )
@@ -1877,7 +1671,7 @@
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (block (result i32)
   ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (ref.null $struct.A)
+  ;; NOMNL-NEXT:       (ref.null none)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.get $2)
   ;; NOMNL-NEXT:     )
@@ -1885,7 +1679,7 @@
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (block (result f64)
   ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (ref.null $struct.A)
+  ;; NOMNL-NEXT:       (ref.null none)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.get $3)
   ;; NOMNL-NEXT:     )
@@ -1900,10 +1694,9 @@
     ;; in various ways inside.
     (loop $outer
       (local.set $ref
-        (struct.new_with_rtt $struct.A
+        (struct.new $struct.A
           (i32.const 2)
           (f64.const 2.1828)
-          (rtt.canon $struct.A)
         )
       )
       (drop
@@ -1962,17 +1755,14 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $0
   ;; CHECK-NEXT:       (i32.const 0)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.set $1
   ;; CHECK-NEXT:       (f64.const 0)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $0)
@@ -1981,17 +1771,14 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $2
   ;; CHECK-NEXT:       (i32.const 0)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.set $3
   ;; CHECK-NEXT:       (f64.const 0)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $2)
@@ -2000,17 +1787,14 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result f64)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:     (block (result nullref)
   ;; CHECK-NEXT:      (local.set $4
   ;; CHECK-NEXT:       (i32.const 0)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (local.set $5
   ;; CHECK-NEXT:       (f64.const 0)
   ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (rtt.canon $struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $5)
@@ -2027,17 +1811,14 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $0
   ;; NOMNL-NEXT:       (i32.const 0)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.set $1
   ;; NOMNL-NEXT:       (f64.const 0)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $0)
@@ -2046,17 +1827,14 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $2
   ;; NOMNL-NEXT:       (i32.const 0)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.set $3
   ;; NOMNL-NEXT:       (f64.const 0)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $2)
@@ -2065,17 +1843,14 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result f64)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:     (block (result nullref)
   ;; NOMNL-NEXT:      (local.set $4
   ;; NOMNL-NEXT:       (i32.const 0)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (local.set $5
   ;; NOMNL-NEXT:       (f64.const 0)
   ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (drop
-  ;; NOMNL-NEXT:       (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:      )
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $5)
@@ -2086,23 +1861,17 @@
     ;; Multiple independent things we can optimize.
     (drop
       (struct.get $struct.A 0
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
     (drop
       (struct.get $struct.A 0
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
     (drop
       (struct.get $struct.A 1
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
   )
@@ -2114,45 +1883,39 @@
   ;; CHECK-NEXT:  (local $3 i32)
   ;; CHECK-NEXT:  (local $4 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $3
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $4
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $3)
   ;; CHECK-NEXT:   )
@@ -2165,45 +1928,39 @@
   ;; NOMNL-NEXT:  (local $3 i32)
   ;; NOMNL-NEXT:  (local $4 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $1)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $3
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $4
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $3)
   ;; NOMNL-NEXT:   )
@@ -2214,9 +1971,7 @@
     ;; Multiple independent things we can optimize that use the same local
     ;; index, but they do not conflict in their live ranges.
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (drop
       (struct.get $struct.A 0
@@ -2224,9 +1979,7 @@
       )
     )
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (drop
       (struct.get $struct.A 0
@@ -2243,37 +1996,31 @@
   ;; CHECK-NEXT:  (local $4 i32)
   ;; CHECK-NEXT:  (local $5 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $3
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $4
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $5
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $2)
   ;; CHECK-NEXT:   )
@@ -2281,7 +2028,7 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $4)
   ;; CHECK-NEXT:   )
@@ -2295,37 +2042,31 @@
   ;; NOMNL-NEXT:  (local $4 i32)
   ;; NOMNL-NEXT:  (local $5 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $3
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $4
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $5
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $2)
   ;; NOMNL-NEXT:   )
@@ -2333,7 +2074,7 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.get $4)
   ;; NOMNL-NEXT:   )
@@ -2345,14 +2086,10 @@
     ;; Multiple independent things we can optimize that use different local
     ;; indexes, but whose lifetimes overlap. We should not be confused by that.
     (local.set $ref1
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (local.set $ref2
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (drop
       (struct.get $struct.A 0
@@ -2369,15 +2106,13 @@
   ;; CHECK:      (func $get-through-block (result f64)
   ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $0
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.get $struct.A 1
   ;; CHECK-NEXT:   (block $block (result (ref null $struct.A))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (br_if $block
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:      (i32.const 0)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
@@ -2388,15 +2123,13 @@
   ;; NOMNL:      (func $get-through-block (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $0 (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $0
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.get $struct.A 1
   ;; NOMNL-NEXT:   (block $block (result (ref null $struct.A))
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (br_if $block
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:      (i32.const 0)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
@@ -2407,9 +2140,7 @@
   (func $get-through-block (result f64)
     (local $0 (ref null $struct.A))
     (local.set $0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.get $struct.A 1
       (block $block (result (ref null $struct.A))
@@ -2431,9 +2162,7 @@
   ;; CHECK:      (func $branch-to-block (result f64)
   ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $0
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.get $struct.A 1
   ;; CHECK-NEXT:   (block $block (result (ref null $struct.A))
@@ -2443,16 +2172,14 @@
   ;; CHECK-NEXT:      (i32.const 0)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $branch-to-block (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $0 (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $0
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.get $struct.A 1
   ;; NOMNL-NEXT:   (block $block (result (ref null $struct.A))
@@ -2462,16 +2189,14 @@
   ;; NOMNL-NEXT:      (i32.const 0)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $branch-to-block (result f64)
     (local $0 (ref null $struct.A))
     (local.set $0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.get $struct.A 1
       (block $block (result (ref null $struct.A))
@@ -2493,17 +2218,14 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result f64)
@@ -2511,7 +2233,7 @@
   ;; CHECK-NEXT:    (block $block (result (ref null $struct.A))
   ;; CHECK-NEXT:     (drop
   ;; CHECK-NEXT:      (br_if $block
-  ;; CHECK-NEXT:       (ref.null $struct.A)
+  ;; CHECK-NEXT:       (ref.null none)
   ;; CHECK-NEXT:       (i32.const 0)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:     )
@@ -2528,17 +2250,14 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result f64)
@@ -2546,7 +2265,7 @@
   ;; NOMNL-NEXT:    (block $block (result (ref null $struct.A))
   ;; NOMNL-NEXT:     (drop
   ;; NOMNL-NEXT:      (br_if $block
-  ;; NOMNL-NEXT:       (ref.null $struct.A)
+  ;; NOMNL-NEXT:       (ref.null none)
   ;; NOMNL-NEXT:       (i32.const 0)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:     )
@@ -2561,9 +2280,7 @@
   (func $branch-to-block-no-fallthrough (result f64)
     (local $0 (ref null $struct.A))
     (local.set $0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.get $struct.A 1
       (block $block (result (ref null $struct.A))
@@ -2584,9 +2301,7 @@
   ;; CHECK:      (func $two-branches (result f64)
   ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $0
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.get $struct.A 1
   ;; CHECK-NEXT:   (block $block (result (ref null $struct.A))
@@ -2598,7 +2313,7 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (br_if $block
-  ;; CHECK-NEXT:      (ref.null $struct.A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:      (i32.const 0)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
@@ -2611,9 +2326,7 @@
   ;; NOMNL:      (func $two-branches (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $0 (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $0
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.get $struct.A 1
   ;; NOMNL-NEXT:   (block $block (result (ref null $struct.A))
@@ -2625,7 +2338,7 @@
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (br_if $block
-  ;; NOMNL-NEXT:      (ref.null $struct.A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:      (i32.const 0)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
@@ -2638,9 +2351,7 @@
   (func $two-branches (result f64)
     (local $0 (ref null $struct.A))
     (local.set $0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.get $struct.A 1
       (block $block (result (ref null $struct.A))
@@ -2666,9 +2377,7 @@
   ;; CHECK:      (func $two-branches-b (result f64)
   ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $0
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.get $struct.A 1
   ;; CHECK-NEXT:   (block $block (result (ref null $struct.A))
@@ -2693,9 +2402,7 @@
   ;; NOMNL:      (func $two-branches-b (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $0 (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $0
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.get $struct.A 1
   ;; NOMNL-NEXT:   (block $block (result (ref null $struct.A))
@@ -2720,9 +2427,7 @@
   (func $two-branches-b (result f64)
     (local $0 (ref null $struct.A))
     (local.set $0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.get $struct.A 1
       (block $block (result (ref null $struct.A))
@@ -2749,9 +2454,7 @@
   ;; CHECK:      (func $br_if_flow (result f64)
   ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
   ;; CHECK-NEXT:  (local.set $0
-  ;; CHECK-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; CHECK-NEXT:    (rtt.canon $struct.A)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (struct.new_default $struct.A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.get $struct.A 1
   ;; CHECK-NEXT:   (block $block (result (ref null $struct.A))
@@ -2770,9 +2473,7 @@
   ;; NOMNL:      (func $br_if_flow (type $none_=>_f64) (result f64)
   ;; NOMNL-NEXT:  (local $0 (ref null $struct.A))
   ;; NOMNL-NEXT:  (local.set $0
-  ;; NOMNL-NEXT:   (struct.new_default_with_rtt $struct.A
-  ;; NOMNL-NEXT:    (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (struct.new_default $struct.A)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.get $struct.A 1
   ;; NOMNL-NEXT:   (block $block (result (ref null $struct.A))
@@ -2791,9 +2492,7 @@
   (func $br_if_flow (result f64)
     (local $0 (ref null $struct.A))
     (local.set $0
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.get $struct.A 1
       (block $block (result (ref null $struct.A))
@@ -2816,22 +2515,19 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.set $1
   ;; CHECK-NEXT:    (i32.const 1)
@@ -2839,7 +2535,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -2848,22 +2544,19 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.set $1
   ;; NOMNL-NEXT:    (i32.const 1)
@@ -2871,16 +2564,14 @@
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.as_non_null
-  ;; NOMNL-NEXT:    (ref.null any)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $ref-as-non-null
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     (struct.set $struct.A 0
       ;; We can see that the input to this RefAsNonNull is always non-null, as
@@ -2904,25 +2595,22 @@
   ;; CHECK-NEXT:  (local $1 i32)
   ;; CHECK-NEXT:  (local $2 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (local.set $1
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.set $2
   ;; CHECK-NEXT:     (f64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $struct.A)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.null $struct.A)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block (result i32)
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (ref.null $struct.A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.get $1)
   ;; CHECK-NEXT:  )
@@ -2932,25 +2620,22 @@
   ;; NOMNL-NEXT:  (local $1 i32)
   ;; NOMNL-NEXT:  (local $2 f64)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (local.set $1
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (local.set $2
   ;; NOMNL-NEXT:     (f64.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.null $struct.A)
+  ;; NOMNL-NEXT:   (ref.null none)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (block (result i32)
   ;; NOMNL-NEXT:   (drop
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.get $1)
   ;; NOMNL-NEXT:  )
@@ -2958,9 +2643,7 @@
   (func $ref-as-non-null-through-local (result i32)
     (local $ref (ref null $struct.A))
     (local.set $ref
-      (struct.new_default_with_rtt $struct.A
-        (rtt.canon $struct.A)
-      )
+      (struct.new_default $struct.A)
     )
     ;; Copy the allocation through a ref.as_non_null. This must not trap: it may
     ;; trap if we leave the ref.as_non_null there and also we do not assign
@@ -2988,7 +2671,7 @@
   ;; CHECK-NEXT:   (block $block (result (ref null $struct.A))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (br_if $block
-  ;; CHECK-NEXT:      (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:      (block (result nullref)
   ;; CHECK-NEXT:       (local.set $3
   ;; CHECK-NEXT:        (i32.const 42)
   ;; CHECK-NEXT:       )
@@ -3001,10 +2684,7 @@
   ;; CHECK-NEXT:       (local.set $2
   ;; CHECK-NEXT:        (local.get $4)
   ;; CHECK-NEXT:       )
-  ;; CHECK-NEXT:       (drop
-  ;; CHECK-NEXT:        (rtt.canon $struct.A)
-  ;; CHECK-NEXT:       )
-  ;; CHECK-NEXT:       (ref.null $struct.A)
+  ;; CHECK-NEXT:       (ref.null none)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (i32.const 0)
   ;; CHECK-NEXT:     )
@@ -3026,7 +2706,7 @@
   ;; NOMNL-NEXT:   (block $block (result (ref null $struct.A))
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (br_if $block
-  ;; NOMNL-NEXT:      (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:      (block (result nullref)
   ;; NOMNL-NEXT:       (local.set $3
   ;; NOMNL-NEXT:        (i32.const 42)
   ;; NOMNL-NEXT:       )
@@ -3039,10 +2719,7 @@
   ;; NOMNL-NEXT:       (local.set $2
   ;; NOMNL-NEXT:        (local.get $4)
   ;; NOMNL-NEXT:       )
-  ;; NOMNL-NEXT:       (drop
-  ;; NOMNL-NEXT:        (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:       )
-  ;; NOMNL-NEXT:       (ref.null $struct.A)
+  ;; NOMNL-NEXT:       (ref.null none)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:      (i32.const 0)
   ;; NOMNL-NEXT:     )
@@ -3062,10 +2739,9 @@
           ;; Our allocation flows into a br_if, which therefore has non-nullable
           ;; type, which we must update after optimizing.
           (br_if $block
-            (struct.new_with_rtt $struct.A
+            (struct.new $struct.A
               (i32.const 42)
               (f64.const 13.37)
-              (rtt.canon $struct.A)
             )
             (i32.const 0)
           )
@@ -3075,44 +2751,6 @@
     )
   )
 
-  ;; CHECK:      (func $simple-no-rtt
-  ;; CHECK-NEXT:  (local $0 i32)
-  ;; CHECK-NEXT:  (local $1 f64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $struct.A))
-  ;; CHECK-NEXT:    (local.set $0
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.set $1
-  ;; CHECK-NEXT:     (f64.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $struct.A)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $simple-no-rtt (type $none_=>_none)
-  ;; NOMNL-NEXT:  (local $0 i32)
-  ;; NOMNL-NEXT:  (local $1 f64)
-  ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $struct.A))
-  ;; NOMNL-NEXT:    (local.set $0
-  ;; NOMNL-NEXT:     (i32.const 0)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (local.set $1
-  ;; NOMNL-NEXT:     (f64.const 0)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $struct.A)
-  ;; NOMNL-NEXT:   )
-  ;; NOMNL-NEXT:  )
-  ;; NOMNL-NEXT: )
-  (func $simple-no-rtt
-    (drop
-      ;; This allocation has no rtt, so we have nothing to drop from it when
-      ;; we optimize.
-      (struct.new_default $struct.A)
-    )
-  )
-
   ;; CHECK:      (func $pass-through-loop
   ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
   ;; CHECK-NEXT:  (local $1 i32)
@@ -3122,17 +2760,14 @@
   ;; CHECK-NEXT:    (br_if $loop
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (block (result (ref null $struct.A))
+  ;; CHECK-NEXT:    (block (result nullref)
   ;; CHECK-NEXT:     (local.set $1
   ;; CHECK-NEXT:      (i32.const 0)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (local.set $2
   ;; CHECK-NEXT:      (f64.const 0)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (rtt.canon $struct.A)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (ref.null $struct.A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -3146,17 +2781,14 @@
   ;; NOMNL-NEXT:    (br_if $loop
   ;; NOMNL-NEXT:     (i32.const 0)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (block (result (ref null $struct.A))
+  ;; NOMNL-NEXT:    (block (result nullref)
   ;; NOMNL-NEXT:     (local.set $1
   ;; NOMNL-NEXT:      (i32.const 0)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:     (local.set $2
   ;; NOMNL-NEXT:      (f64.const 0)
   ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (drop
-  ;; NOMNL-NEXT:      (rtt.canon $struct.A)
-  ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (ref.null $struct.A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
@@ -3172,10 +2804,63 @@
         ;; block).
         (br_if $loop (i32.const 0))
         ;; The allocation that will be turned into locals.
-        (struct.new_default_with_rtt $struct.A
-          (rtt.canon $struct.A)
-        )
+        (struct.new_default $struct.A)
       )
     )
   )
+
+  ;; CHECK:      (func $non-nullable-local (result anyref)
+  ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
+  ;; CHECK-NEXT:  (local $1 i32)
+  ;; CHECK-NEXT:  (local $2 f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result nullref)
+  ;; CHECK-NEXT:    (local.set $1
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.set $2
+  ;; CHECK-NEXT:     (f64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT:  (ref.as_non_null
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $non-nullable-local (type $none_=>_anyref) (result anyref)
+  ;; NOMNL-NEXT:  (local $0 (ref null $struct.A))
+  ;; NOMNL-NEXT:  (local $1 i32)
+  ;; NOMNL-NEXT:  (local $2 f64)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (block (result nullref)
+  ;; NOMNL-NEXT:    (local.set $1
+  ;; NOMNL-NEXT:     (i32.const 0)
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (local.set $2
+  ;; NOMNL-NEXT:     (f64.const 0)
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (ref.null none)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (unreachable)
+  ;; NOMNL-NEXT:  (ref.as_non_null
+  ;; NOMNL-NEXT:   (local.get $0)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $non-nullable-local (result anyref)
+   (local $0 (ref $struct.A))
+   ;; The local.get here is in unreachable code, which means we won't do
+   ;; anything to it. But when we remove the local.set during optimization (we
+   ;; can replace it with new locals for the fields of $struct.A), we must make
+   ;; sure that validation still passes, that is, since the local.get is
+   ;; around we must have a local.set for it, or it must become nullable (which
+   ;; is what the fixup will do).
+   (local.set $0
+    (struct.new_default $struct.A)
+   )
+   (unreachable)
+   (local.get $0)
+  )
 )
diff --git a/test/lit/passes/inline-main.wast b/test/lit/passes/inline-main.wast
index 042e327..fac2c69 100644
--- a/test/lit/passes/inline-main.wast
+++ b/test/lit/passes/inline-main.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --inline-main -S -o - | filecheck %s
 
diff --git a/test/lit/passes/inlining-gc.wast b/test/lit/passes/inlining-gc.wast
index 11ac125..cebdbd0 100644
--- a/test/lit/passes/inlining-gc.wast
+++ b/test/lit/passes/inlining-gc.wast
@@ -6,7 +6,7 @@
  ;; CHECK-NEXT:  (local $0 funcref)
  ;; CHECK-NEXT:  (block $__inlined_func$target-nullable
  ;; CHECK-NEXT:   (local.set $0
- ;; CHECK-NEXT:    (ref.null func)
+ ;; CHECK-NEXT:    (ref.null nofunc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (nop)
  ;; CHECK-NEXT:  )
diff --git a/test/lit/passes/inlining-optimizing.wast b/test/lit/passes/inlining-optimizing.wast
index ac29ba1..a7b671f 100644
--- a/test/lit/passes/inlining-optimizing.wast
+++ b/test/lit/passes/inlining-optimizing.wast
@@ -13,7 +13,10 @@
  )
  ;; CHECK:      (func $1
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (call_ref
+ ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
+ ;; CHECK-NEXT:    (drop
+ ;; CHECK-NEXT:     (unreachable)
+ ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -29,13 +32,11 @@
   ;; unreachable.)
   (call $0)
   (drop
-   (call_ref
-    (ref.cast
+   (call_ref $none_=>_i32
+    (ref.cast_static $none_=>_i32
      (ref.func $0)
-     (rtt.canon $none_=>_i32)
     )
    )
   )
  )
 )
-
diff --git a/test/lit/passes/inlining-optimizing_enable-threads.wast b/test/lit/passes/inlining-optimizing_enable-threads.wast
index 135ed95..c3ed107 100644
--- a/test/lit/passes/inlining-optimizing_enable-threads.wast
+++ b/test/lit/passes/inlining-optimizing_enable-threads.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --inlining-optimizing --enable-threads -S -o - | filecheck %s
 
diff --git a/test/lit/passes/inlining-optimizing_optimize-level=3.wast b/test/lit/passes/inlining-optimizing_optimize-level=3.wast
index 632ec16..feaa317 100644
--- a/test/lit/passes/inlining-optimizing_optimize-level=3.wast
+++ b/test/lit/passes/inlining-optimizing_optimize-level=3.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --inlining-optimizing --optimize-level=3 -S -o - | filecheck %s
 
@@ -35,8 +35,6 @@
 
  ;; CHECK:      (import "env" "memory" (memory $0 256 256))
  (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
- ;; CHECK:      (data (i32.const 1024) "emcc_hello_world.asm.js")
-
  ;; CHECK:      (import "env" "table" (table $timport$0 18 18 funcref))
  (import "env" "STACK_MAX" (global $STACK_MAX$asm2wasm$import i32))
  ;; CHECK:      (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
@@ -99,6 +97,8 @@
  (global $tempRet0 (mut i32) (i32.const 0))
  (elem (global.get $tableBase) $b0 $___stdio_close $b1 $b1 $___stdout_write $___stdio_seek $___stdio_write $b1 $b1 $b1 $b2 $b2 $b2 $b2 $b2 $_cleanup $b2 $b2)
  (data (i32.const 1024) "emcc_hello_world.asm.js")
+ ;; CHECK:      (data (i32.const 1024) "emcc_hello_world.asm.js")
+
  ;; CHECK:      (elem (global.get $tableBase) $b0 $___stdio_close $b1 $b1 $___stdout_write $___stdio_seek $___stdio_write $b1 $b1 $b1 $b2 $b2 $b2 $b2 $b2 $_cleanup $b2 $b2)
 
  ;; CHECK:      (export "_i64Subtract" (func $_i64Subtract))
@@ -242,7 +242,7 @@
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (global.get $__THREW__)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (global.set $__THREW__
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
@@ -665,7 +665,7 @@
  ;; CHECK-NEXT:         (local.get $0)
  ;; CHECK-NEXT:         (f64.const 0)
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block (result i32)
+ ;; CHECK-NEXT:        (block (result i32)
  ;; CHECK-NEXT:         (local.set $0
  ;; CHECK-NEXT:          (call $_frexp
  ;; CHECK-NEXT:           (f64.mul
@@ -1041,7 +1041,7 @@
  ;; CHECK-NEXT:     (i32.const 64)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (i32.store
  ;; CHECK-NEXT:     (local.get $3)
  ;; CHECK-NEXT:     (i32.load offset=60
@@ -1220,7 +1220,7 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block (result i32)
+ ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (i32.store
  ;; CHECK-NEXT:      (local.get $0)
  ;; CHECK-NEXT:      (i32.const -1)
@@ -1686,7 +1686,7 @@
  ;; CHECK-NEXT:       (i32.load
  ;; CHECK-NEXT:        (i32.const 16)
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (call $_pthread_cleanup_push
  ;; CHECK-NEXT:         (i32.const 5)
  ;; CHECK-NEXT:         (local.get $0)
@@ -1717,7 +1717,7 @@
  ;; CHECK-NEXT:         (i32.const 0)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block14
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (i32.store
  ;; CHECK-NEXT:         (local.get $9)
  ;; CHECK-NEXT:         (i32.load
@@ -1764,7 +1764,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block16 (result i32)
+ ;; CHECK-NEXT:        (block (result i32)
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (local.get $6)
  ;; CHECK-NEXT:          (local.tee $7
@@ -1799,13 +1799,13 @@
  ;; CHECK-NEXT:          (local.get $5)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block17 (result i32)
+ ;; CHECK-NEXT:        (block (result i32)
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.eq
  ;; CHECK-NEXT:           (local.get $4)
  ;; CHECK-NEXT:           (i32.const 2)
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block19
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (i32.store
  ;; CHECK-NEXT:            (local.get $6)
  ;; CHECK-NEXT:            (i32.add
@@ -3673,7 +3673,7 @@
  ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:    (i32.const -4096)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block (result i32)
+ ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (i32.store
  ;; CHECK-NEXT:     (call $___errno_location)
  ;; CHECK-NEXT:     (i32.sub
@@ -4218,16 +4218,16 @@
  ;; CHECK-NEXT:     (block $label$break$L1
  ;; CHECK-NEXT:      (if
  ;; CHECK-NEXT:       (i32.ge_s
- ;; CHECK-NEXT:        (local.get $18)
+ ;; CHECK-NEXT:        (local.get $17)
  ;; CHECK-NEXT:        (i32.const 0)
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (local.set $18
+ ;; CHECK-NEXT:       (local.set $17
  ;; CHECK-NEXT:        (if (result i32)
  ;; CHECK-NEXT:         (i32.gt_s
  ;; CHECK-NEXT:          (local.get $10)
  ;; CHECK-NEXT:          (i32.sub
  ;; CHECK-NEXT:           (i32.const 2147483647)
- ;; CHECK-NEXT:           (local.get $18)
+ ;; CHECK-NEXT:           (local.get $17)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (block (result i32)
@@ -4239,7 +4239,7 @@
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (i32.add
  ;; CHECK-NEXT:          (local.get $10)
- ;; CHECK-NEXT:          (local.get $18)
+ ;; CHECK-NEXT:          (local.get $17)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
@@ -4264,12 +4264,8 @@
  ;; CHECK-NEXT:           (block $switch-case0
  ;; CHECK-NEXT:            (block $switch-case
  ;; CHECK-NEXT:             (br_table $switch-case0 $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-default $switch-case $switch-default
- ;; CHECK-NEXT:              (i32.shr_s
- ;; CHECK-NEXT:               (i32.shl
- ;; CHECK-NEXT:                (local.get $7)
- ;; CHECK-NEXT:                (i32.const 24)
- ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (i32.const 24)
+ ;; CHECK-NEXT:              (i32.extend8_s
+ ;; CHECK-NEXT:               (local.get $7)
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
@@ -4440,12 +4436,8 @@
  ;; CHECK-NEXT:        (i32.eq
  ;; CHECK-NEXT:         (i32.and
  ;; CHECK-NEXT:          (local.tee $9
- ;; CHECK-NEXT:           (i32.shr_s
- ;; CHECK-NEXT:            (i32.shl
- ;; CHECK-NEXT:             (local.get $6)
- ;; CHECK-NEXT:             (i32.const 24)
- ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (i32.const 24)
+ ;; CHECK-NEXT:           (i32.extend8_s
+ ;; CHECK-NEXT:            (local.get $6)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (i32.const -32)
@@ -4490,12 +4482,8 @@
  ;; CHECK-NEXT:            (i32.shl
  ;; CHECK-NEXT:             (i32.const 1)
  ;; CHECK-NEXT:             (i32.sub
- ;; CHECK-NEXT:              (i32.shr_s
- ;; CHECK-NEXT:               (i32.shl
- ;; CHECK-NEXT:                (local.get $1)
- ;; CHECK-NEXT:                (i32.const 24)
- ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (i32.const 24)
+ ;; CHECK-NEXT:              (i32.extend8_s
+ ;; CHECK-NEXT:               (local.get $1)
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:              (i32.const 32)
  ;; CHECK-NEXT:             )
@@ -4523,9 +4511,6 @@
  ;; CHECK-NEXT:            (i32.const 32)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (local.set $6
- ;; CHECK-NEXT:           (local.get $1)
- ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (local.get $9)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
@@ -4617,7 +4602,7 @@
  ;; CHECK-NEXT:           (if
  ;; CHECK-NEXT:            (local.get $8)
  ;; CHECK-NEXT:            (block
- ;; CHECK-NEXT:             (local.set $18
+ ;; CHECK-NEXT:             (local.set $17
  ;; CHECK-NEXT:              (i32.const -1)
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:             (br $label$break$L1)
@@ -4697,12 +4682,8 @@
  ;; CHECK-NEXT:         (i32.lt_u
  ;; CHECK-NEXT:          (local.tee $6
  ;; CHECK-NEXT:           (i32.sub
- ;; CHECK-NEXT:            (i32.shr_s
- ;; CHECK-NEXT:             (i32.shl
- ;; CHECK-NEXT:              (local.get $6)
- ;; CHECK-NEXT:              (i32.const 24)
- ;; CHECK-NEXT:             )
- ;; CHECK-NEXT:             (i32.const 24)
+ ;; CHECK-NEXT:            (i32.extend8_s
+ ;; CHECK-NEXT:             (local.get $6)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (i32.const 48)
  ;; CHECK-NEXT:           )
@@ -4757,7 +4738,7 @@
  ;; CHECK-NEXT:            (i32.const 0)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (block
- ;; CHECK-NEXT:            (local.set $18
+ ;; CHECK-NEXT:            (local.set $17
  ;; CHECK-NEXT:             (i32.const -1)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (br $label$break$L1)
@@ -4943,7 +4924,7 @@
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (local.get $1)
  ;; CHECK-NEXT:          (block
- ;; CHECK-NEXT:           (local.set $18
+ ;; CHECK-NEXT:           (local.set $17
  ;; CHECK-NEXT:            (i32.const -1)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (br $label$break$L1)
@@ -5010,7 +4991,7 @@
  ;; CHECK-NEXT:         (i32.const 57)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (block
- ;; CHECK-NEXT:         (local.set $18
+ ;; CHECK-NEXT:         (local.set $17
  ;; CHECK-NEXT:          (i32.const -1)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (br $label$break$L1)
@@ -5067,13 +5048,13 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (block
- ;; CHECK-NEXT:        (local.set $18
+ ;; CHECK-NEXT:        (local.set $17
  ;; CHECK-NEXT:         (i32.const -1)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (br $label$break$L1)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (local.set $17
+ ;; CHECK-NEXT:      (local.set $18
  ;; CHECK-NEXT:       (i32.ge_s
  ;; CHECK-NEXT:        (local.get $19)
  ;; CHECK-NEXT:        (i32.const 0)
@@ -5090,9 +5071,9 @@
  ;; CHECK-NEXT:          (i32.const 19)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (if
- ;; CHECK-NEXT:          (local.get $17)
+ ;; CHECK-NEXT:          (local.get $18)
  ;; CHECK-NEXT:          (block
- ;; CHECK-NEXT:           (local.set $18
+ ;; CHECK-NEXT:           (local.set $17
  ;; CHECK-NEXT:            (i32.const -1)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (br $label$break$L1)
@@ -5101,7 +5082,7 @@
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (if
- ;; CHECK-NEXT:           (local.get $17)
+ ;; CHECK-NEXT:           (local.get $18)
  ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store
  ;; CHECK-NEXT:             (i32.add
@@ -5144,7 +5125,7 @@
  ;; CHECK-NEXT:            (local.get $29)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (block
- ;; CHECK-NEXT:            (local.set $18
+ ;; CHECK-NEXT:            (local.set $17
  ;; CHECK-NEXT:             (i32.const 0)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (br $label$break$L1)
@@ -5273,7 +5254,7 @@
  ;; CHECK-NEXT:                                            (i32.load
  ;; CHECK-NEXT:                                             (local.get $13)
  ;; CHECK-NEXT:                                            )
- ;; CHECK-NEXT:                                            (local.get $18)
+ ;; CHECK-NEXT:                                            (local.get $17)
  ;; CHECK-NEXT:                                           )
  ;; CHECK-NEXT:                                           (local.set $5
  ;; CHECK-NEXT:                                            (local.get $10)
@@ -5287,7 +5268,7 @@
  ;; CHECK-NEXT:                                           (i32.load
  ;; CHECK-NEXT:                                            (local.get $13)
  ;; CHECK-NEXT:                                           )
- ;; CHECK-NEXT:                                           (local.get $18)
+ ;; CHECK-NEXT:                                           (local.get $17)
  ;; CHECK-NEXT:                                          )
  ;; CHECK-NEXT:                                          (local.set $5
  ;; CHECK-NEXT:                                           (local.get $10)
@@ -5303,14 +5284,14 @@
  ;; CHECK-NEXT:                                            (local.get $13)
  ;; CHECK-NEXT:                                           )
  ;; CHECK-NEXT:                                          )
- ;; CHECK-NEXT:                                          (local.get $18)
+ ;; CHECK-NEXT:                                          (local.get $17)
  ;; CHECK-NEXT:                                         )
  ;; CHECK-NEXT:                                         (i32.store offset=4
  ;; CHECK-NEXT:                                          (local.get $5)
  ;; CHECK-NEXT:                                          (i32.shr_s
  ;; CHECK-NEXT:                                           (i32.shl
  ;; CHECK-NEXT:                                            (i32.lt_s
- ;; CHECK-NEXT:                                             (local.get $18)
+ ;; CHECK-NEXT:                                             (local.get $17)
  ;; CHECK-NEXT:                                             (i32.const 0)
  ;; CHECK-NEXT:                                            )
  ;; CHECK-NEXT:                                            (i32.const 31)
@@ -5330,7 +5311,7 @@
  ;; CHECK-NEXT:                                         (i32.load
  ;; CHECK-NEXT:                                          (local.get $13)
  ;; CHECK-NEXT:                                         )
- ;; CHECK-NEXT:                                         (local.get $18)
+ ;; CHECK-NEXT:                                         (local.get $17)
  ;; CHECK-NEXT:                                        )
  ;; CHECK-NEXT:                                        (local.set $5
  ;; CHECK-NEXT:                                         (local.get $10)
@@ -5344,7 +5325,7 @@
  ;; CHECK-NEXT:                                        (i32.load
  ;; CHECK-NEXT:                                         (local.get $13)
  ;; CHECK-NEXT:                                        )
- ;; CHECK-NEXT:                                        (local.get $18)
+ ;; CHECK-NEXT:                                        (local.get $17)
  ;; CHECK-NEXT:                                       )
  ;; CHECK-NEXT:                                       (local.set $5
  ;; CHECK-NEXT:                                        (local.get $10)
@@ -5358,7 +5339,7 @@
  ;; CHECK-NEXT:                                       (i32.load
  ;; CHECK-NEXT:                                        (local.get $13)
  ;; CHECK-NEXT:                                       )
- ;; CHECK-NEXT:                                       (local.get $18)
+ ;; CHECK-NEXT:                                       (local.get $17)
  ;; CHECK-NEXT:                                      )
  ;; CHECK-NEXT:                                      (local.set $5
  ;; CHECK-NEXT:                                       (local.get $10)
@@ -5374,14 +5355,14 @@
  ;; CHECK-NEXT:                                        (local.get $13)
  ;; CHECK-NEXT:                                       )
  ;; CHECK-NEXT:                                      )
- ;; CHECK-NEXT:                                      (local.get $18)
+ ;; CHECK-NEXT:                                      (local.get $17)
  ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                     (i32.store offset=4
  ;; CHECK-NEXT:                                      (local.get $5)
  ;; CHECK-NEXT:                                      (i32.shr_s
  ;; CHECK-NEXT:                                       (i32.shl
  ;; CHECK-NEXT:                                        (i32.lt_s
- ;; CHECK-NEXT:                                         (local.get $18)
+ ;; CHECK-NEXT:                                         (local.get $17)
  ;; CHECK-NEXT:                                         (i32.const 0)
  ;; CHECK-NEXT:                                        )
  ;; CHECK-NEXT:                                        (i32.const 31)
@@ -5413,9 +5394,9 @@
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                   (local.set $6
  ;; CHECK-NEXT:                                    (select
- ;; CHECK-NEXT:                                     (local.get $6)
  ;; CHECK-NEXT:                                     (i32.const 8)
- ;; CHECK-NEXT:                                     (i32.gt_u
+ ;; CHECK-NEXT:                                     (local.get $6)
+ ;; CHECK-NEXT:                                     (i32.le_u
  ;; CHECK-NEXT:                                      (local.get $6)
  ;; CHECK-NEXT:                                      (i32.const 8)
  ;; CHECK-NEXT:                                     )
@@ -5928,7 +5909,7 @@
  ;; CHECK-NEXT:                                 (i32.const 9)
  ;; CHECK-NEXT:                                )
  ;; CHECK-NEXT:                                (local.get $30)
- ;; CHECK-NEXT:                                (local.tee $17
+ ;; CHECK-NEXT:                                (local.tee $18
  ;; CHECK-NEXT:                                 (i32.and
  ;; CHECK-NEXT:                                  (local.get $15)
  ;; CHECK-NEXT:                                  (i32.const 32)
@@ -6134,7 +6115,7 @@
  ;; CHECK-NEXT:                                   (i32.const 4075)
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                 )
- ;; CHECK-NEXT:                                 (local.get $17)
+ ;; CHECK-NEXT:                                 (local.get $18)
  ;; CHECK-NEXT:                                )
  ;; CHECK-NEXT:                               )
  ;; CHECK-NEXT:                               (local.set $14
@@ -6453,7 +6434,7 @@
  ;; CHECK-NEXT:                               (local.get $8)
  ;; CHECK-NEXT:                              )
  ;; CHECK-NEXT:                              (loop $while-in62
- ;; CHECK-NEXT:                               (local.set $17
+ ;; CHECK-NEXT:                               (local.set $18
  ;; CHECK-NEXT:                                (select
  ;; CHECK-NEXT:                                 (i32.const 29)
  ;; CHECK-NEXT:                                 (local.get $11)
@@ -6487,7 +6468,7 @@
  ;; CHECK-NEXT:                                        (local.get $11)
  ;; CHECK-NEXT:                                       )
  ;; CHECK-NEXT:                                       (i32.const 0)
- ;; CHECK-NEXT:                                       (local.get $17)
+ ;; CHECK-NEXT:                                       (local.get $18)
  ;; CHECK-NEXT:                                      )
  ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                     (local.tee $12
@@ -6580,7 +6561,7 @@
  ;; CHECK-NEXT:                                  (i32.load
  ;; CHECK-NEXT:                                   (local.get $21)
  ;; CHECK-NEXT:                                  )
- ;; CHECK-NEXT:                                  (local.get $17)
+ ;; CHECK-NEXT:                                  (local.get $18)
  ;; CHECK-NEXT:                                 )
  ;; CHECK-NEXT:                                )
  ;; CHECK-NEXT:                               )
@@ -6638,7 +6619,7 @@
  ;; CHECK-NEXT:                              )
  ;; CHECK-NEXT:                              (local.set $5
  ;; CHECK-NEXT:                               (loop $while-in70 (result i32)
- ;; CHECK-NEXT:                                (local.set $17
+ ;; CHECK-NEXT:                                (local.set $18
  ;; CHECK-NEXT:                                 (select
  ;; CHECK-NEXT:                                  (i32.const 9)
  ;; CHECK-NEXT:                                  (local.tee $7
@@ -6663,7 +6644,7 @@
  ;; CHECK-NEXT:                                   (i32.sub
  ;; CHECK-NEXT:                                    (i32.shl
  ;; CHECK-NEXT:                                     (i32.const 1)
- ;; CHECK-NEXT:                                     (local.get $17)
+ ;; CHECK-NEXT:                                     (local.get $18)
  ;; CHECK-NEXT:                                    )
  ;; CHECK-NEXT:                                    (i32.const 1)
  ;; CHECK-NEXT:                                   )
@@ -6671,7 +6652,7 @@
  ;; CHECK-NEXT:                                  (local.set $34
  ;; CHECK-NEXT:                                   (i32.shr_u
  ;; CHECK-NEXT:                                    (i32.const 1000000000)
- ;; CHECK-NEXT:                                    (local.get $17)
+ ;; CHECK-NEXT:                                    (local.get $18)
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                  (local.set $11
@@ -6691,7 +6672,7 @@
  ;; CHECK-NEXT:                                        (local.get $7)
  ;; CHECK-NEXT:                                       )
  ;; CHECK-NEXT:                                      )
- ;; CHECK-NEXT:                                      (local.get $17)
+ ;; CHECK-NEXT:                                      (local.get $18)
  ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                    )
  ;; CHECK-NEXT:                                   )
@@ -6792,7 +6773,7 @@
  ;; CHECK-NEXT:                                   (i32.load
  ;; CHECK-NEXT:                                    (local.get $21)
  ;; CHECK-NEXT:                                   )
- ;; CHECK-NEXT:                                   (local.get $17)
+ ;; CHECK-NEXT:                                   (local.get $18)
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                 )
  ;; CHECK-NEXT:                                )
@@ -6988,22 +6969,24 @@
  ;; CHECK-NEXT:                                )
  ;; CHECK-NEXT:                               )
  ;; CHECK-NEXT:                               (local.set $8
- ;; CHECK-NEXT:                                (i32.load
- ;; CHECK-NEXT:                                 (local.tee $6
- ;; CHECK-NEXT:                                  (i32.sub
- ;; CHECK-NEXT:                                   (i32.add
- ;; CHECK-NEXT:                                    (local.get $20)
- ;; CHECK-NEXT:                                    (i32.shl
- ;; CHECK-NEXT:                                     (local.get $8)
- ;; CHECK-NEXT:                                     (i32.const 2)
+ ;; CHECK-NEXT:                                (local.tee $18
+ ;; CHECK-NEXT:                                 (i32.load
+ ;; CHECK-NEXT:                                  (local.tee $6
+ ;; CHECK-NEXT:                                   (i32.sub
+ ;; CHECK-NEXT:                                    (i32.add
+ ;; CHECK-NEXT:                                     (local.get $20)
+ ;; CHECK-NEXT:                                     (i32.shl
+ ;; CHECK-NEXT:                                      (local.get $8)
+ ;; CHECK-NEXT:                                      (i32.const 2)
+ ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                    )
+ ;; CHECK-NEXT:                                    (i32.const 4092)
  ;; CHECK-NEXT:                                   )
- ;; CHECK-NEXT:                                   (i32.const 4092)
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                 )
  ;; CHECK-NEXT:                                )
  ;; CHECK-NEXT:                               )
- ;; CHECK-NEXT:                               (local.set $17
+ ;; CHECK-NEXT:                               (local.set $8
  ;; CHECK-NEXT:                                (if (result i32)
  ;; CHECK-NEXT:                                 (local.get $12)
  ;; CHECK-NEXT:                                 (i32.rem_u
@@ -7026,7 +7009,7 @@
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                  (i32.eqz
- ;; CHECK-NEXT:                                   (local.get $17)
+ ;; CHECK-NEXT:                                   (local.get $8)
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                 )
  ;; CHECK-NEXT:                                )
@@ -7035,7 +7018,7 @@
  ;; CHECK-NEXT:                                  (if (result i32)
  ;; CHECK-NEXT:                                   (local.get $12)
  ;; CHECK-NEXT:                                   (i32.div_u
- ;; CHECK-NEXT:                                    (local.get $8)
+ ;; CHECK-NEXT:                                    (local.get $18)
  ;; CHECK-NEXT:                                    (local.get $12)
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                   (i32.const 0)
@@ -7044,7 +7027,7 @@
  ;; CHECK-NEXT:                                 (local.set $14
  ;; CHECK-NEXT:                                  (if (result f64)
  ;; CHECK-NEXT:                                   (i32.lt_u
- ;; CHECK-NEXT:                                    (local.get $17)
+ ;; CHECK-NEXT:                                    (local.get $8)
  ;; CHECK-NEXT:                                    (local.tee $45
  ;; CHECK-NEXT:                                     (i32.div_s
  ;; CHECK-NEXT:                                      (local.get $12)
@@ -7059,7 +7042,7 @@
  ;; CHECK-NEXT:                                    (i32.and
  ;; CHECK-NEXT:                                     (local.get $26)
  ;; CHECK-NEXT:                                     (i32.eq
- ;; CHECK-NEXT:                                      (local.get $17)
+ ;; CHECK-NEXT:                                      (local.get $8)
  ;; CHECK-NEXT:                                      (local.get $45)
  ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                    )
@@ -7103,8 +7086,8 @@
  ;; CHECK-NEXT:                                  (local.get $6)
  ;; CHECK-NEXT:                                  (local.tee $8
  ;; CHECK-NEXT:                                   (i32.sub
+ ;; CHECK-NEXT:                                    (local.get $18)
  ;; CHECK-NEXT:                                    (local.get $8)
- ;; CHECK-NEXT:                                    (local.get $17)
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                 )
@@ -7363,7 +7346,7 @@
  ;; CHECK-NEXT:                                    (drop
  ;; CHECK-NEXT:                                     (br_if $do-once91
  ;; CHECK-NEXT:                                      (local.get $19)
- ;; CHECK-NEXT:                                      (local.tee $17
+ ;; CHECK-NEXT:                                      (local.tee $18
  ;; CHECK-NEXT:                                       (i32.and
  ;; CHECK-NEXT:                                        (local.get $9)
  ;; CHECK-NEXT:                                        (i32.const 8)
@@ -7466,21 +7449,21 @@
  ;; CHECK-NEXT:                                      (i32.const 102)
  ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                     (block (result i32)
- ;; CHECK-NEXT:                                      (local.set $17
+ ;; CHECK-NEXT:                                      (local.set $18
  ;; CHECK-NEXT:                                       (i32.const 0)
  ;; CHECK-NEXT:                                      )
  ;; CHECK-NEXT:                                      (select
  ;; CHECK-NEXT:                                       (local.get $19)
  ;; CHECK-NEXT:                                       (local.tee $5
  ;; CHECK-NEXT:                                        (select
- ;; CHECK-NEXT:                                         (i32.const 0)
  ;; CHECK-NEXT:                                         (local.tee $5
  ;; CHECK-NEXT:                                          (i32.sub
  ;; CHECK-NEXT:                                           (local.get $6)
  ;; CHECK-NEXT:                                           (local.get $5)
  ;; CHECK-NEXT:                                          )
  ;; CHECK-NEXT:                                         )
- ;; CHECK-NEXT:                                         (i32.lt_s
+ ;; CHECK-NEXT:                                         (i32.const 0)
+ ;; CHECK-NEXT:                                         (i32.ge_s
  ;; CHECK-NEXT:                                          (local.get $5)
  ;; CHECK-NEXT:                                          (i32.const 0)
  ;; CHECK-NEXT:                                         )
@@ -7493,14 +7476,13 @@
  ;; CHECK-NEXT:                                      )
  ;; CHECK-NEXT:                                     )
  ;; CHECK-NEXT:                                     (block (result i32)
- ;; CHECK-NEXT:                                      (local.set $17
+ ;; CHECK-NEXT:                                      (local.set $18
  ;; CHECK-NEXT:                                       (i32.const 0)
  ;; CHECK-NEXT:                                      )
  ;; CHECK-NEXT:                                      (select
  ;; CHECK-NEXT:                                       (local.get $19)
  ;; CHECK-NEXT:                                       (local.tee $5
  ;; CHECK-NEXT:                                        (select
- ;; CHECK-NEXT:                                         (i32.const 0)
  ;; CHECK-NEXT:                                         (local.tee $5
  ;; CHECK-NEXT:                                          (i32.sub
  ;; CHECK-NEXT:                                           (i32.add
@@ -7510,7 +7492,8 @@
  ;; CHECK-NEXT:                                           (local.get $5)
  ;; CHECK-NEXT:                                          )
  ;; CHECK-NEXT:                                         )
- ;; CHECK-NEXT:                                         (i32.lt_s
+ ;; CHECK-NEXT:                                         (i32.const 0)
+ ;; CHECK-NEXT:                                         (i32.ge_s
  ;; CHECK-NEXT:                                          (local.get $5)
  ;; CHECK-NEXT:                                          (i32.const 0)
  ;; CHECK-NEXT:                                         )
@@ -7525,7 +7508,7 @@
  ;; CHECK-NEXT:                                    )
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                   (block (result i32)
- ;; CHECK-NEXT:                                    (local.set $17
+ ;; CHECK-NEXT:                                    (local.set $18
  ;; CHECK-NEXT:                                     (i32.and
  ;; CHECK-NEXT:                                      (local.get $9)
  ;; CHECK-NEXT:                                      (i32.const 8)
@@ -7547,7 +7530,7 @@
  ;; CHECK-NEXT:                                 (local.tee $19
  ;; CHECK-NEXT:                                  (i32.or
  ;; CHECK-NEXT:                                   (local.get $5)
- ;; CHECK-NEXT:                                   (local.get $17)
+ ;; CHECK-NEXT:                                   (local.get $18)
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                 )
  ;; CHECK-NEXT:                                 (i32.const 0)
@@ -7934,16 +7917,6 @@
  ;; CHECK-NEXT:                              )
  ;; CHECK-NEXT:                             )
  ;; CHECK-NEXT:                             (block $do-once99
- ;; CHECK-NEXT:                              (local.set $20
- ;; CHECK-NEXT:                               (select
- ;; CHECK-NEXT:                                (local.get $11)
- ;; CHECK-NEXT:                                (i32.add
- ;; CHECK-NEXT:                                 (local.get $12)
- ;; CHECK-NEXT:                                 (i32.const 4)
- ;; CHECK-NEXT:                                )
- ;; CHECK-NEXT:                                (local.get $26)
- ;; CHECK-NEXT:                               )
- ;; CHECK-NEXT:                              )
  ;; CHECK-NEXT:                              (call $_pad
  ;; CHECK-NEXT:                               (local.get $0)
  ;; CHECK-NEXT:                               (i32.const 48)
@@ -7954,9 +7927,19 @@
  ;; CHECK-NEXT:                                  (i32.const 0)
  ;; CHECK-NEXT:                                 )
  ;; CHECK-NEXT:                                 (block (result i32)
- ;; CHECK-NEXT:                                  (local.set $17
+ ;; CHECK-NEXT:                                  (local.set $20
+ ;; CHECK-NEXT:                                   (select
+ ;; CHECK-NEXT:                                    (local.get $11)
+ ;; CHECK-NEXT:                                    (i32.add
+ ;; CHECK-NEXT:                                     (local.get $12)
+ ;; CHECK-NEXT:                                     (i32.const 4)
+ ;; CHECK-NEXT:                                    )
+ ;; CHECK-NEXT:                                    (local.get $26)
+ ;; CHECK-NEXT:                                   )
+ ;; CHECK-NEXT:                                  )
+ ;; CHECK-NEXT:                                  (local.set $18
  ;; CHECK-NEXT:                                   (i32.eqz
- ;; CHECK-NEXT:                                    (local.get $17)
+ ;; CHECK-NEXT:                                    (local.get $18)
  ;; CHECK-NEXT:                                   )
  ;; CHECK-NEXT:                                  )
  ;; CHECK-NEXT:                                  (local.set $6
@@ -8021,7 +8004,7 @@
  ;; CHECK-NEXT:                                      )
  ;; CHECK-NEXT:                                      (br_if $do-once115
  ;; CHECK-NEXT:                                       (i32.and
- ;; CHECK-NEXT:                                        (local.get $17)
+ ;; CHECK-NEXT:                                        (local.get $18)
  ;; CHECK-NEXT:                                        (i32.le_s
  ;; CHECK-NEXT:                                         (local.get $7)
  ;; CHECK-NEXT:                                         (i32.const 0)
@@ -8305,12 +8288,6 @@
  ;; CHECK-NEXT:                         (local.get $23)
  ;; CHECK-NEXT:                        )
  ;; CHECK-NEXT:                       )
- ;; CHECK-NEXT:                       (local.set $11
- ;; CHECK-NEXT:                        (i32.and
- ;; CHECK-NEXT:                         (local.get $15)
- ;; CHECK-NEXT:                         (i32.const 32)
- ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                       )
  ;; CHECK-NEXT:                       (if
  ;; CHECK-NEXT:                        (i32.or
  ;; CHECK-NEXT:                         (local.tee $5
@@ -8325,6 +8302,12 @@
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                        )
  ;; CHECK-NEXT:                        (block
+ ;; CHECK-NEXT:                         (local.set $11
+ ;; CHECK-NEXT:                          (i32.and
+ ;; CHECK-NEXT:                           (local.get $15)
+ ;; CHECK-NEXT:                           (i32.const 32)
+ ;; CHECK-NEXT:                          )
+ ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                         (local.set $8
  ;; CHECK-NEXT:                          (local.get $23)
  ;; CHECK-NEXT:                         )
@@ -8435,7 +8418,7 @@
  ;; CHECK-NEXT:                      (br $__rjti$8)
  ;; CHECK-NEXT:                     )
  ;; CHECK-NEXT:                     (block $label$break$L8
- ;; CHECK-NEXT:                      (block $__rjti$29
+ ;; CHECK-NEXT:                      (block $__rjti$28
  ;; CHECK-NEXT:                       (if
  ;; CHECK-NEXT:                        (i32.and
  ;; CHECK-NEXT:                         (local.tee $9
@@ -8458,15 +8441,15 @@
  ;; CHECK-NEXT:                         (local.set $8
  ;; CHECK-NEXT:                          (local.get $5)
  ;; CHECK-NEXT:                         )
- ;; CHECK-NEXT:                         (loop $while-in12
- ;; CHECK-NEXT:                          (br_if $__rjti$29
+ ;; CHECK-NEXT:                         (loop $while-in9
+ ;; CHECK-NEXT:                          (br_if $__rjti$28
  ;; CHECK-NEXT:                           (i32.eqz
  ;; CHECK-NEXT:                            (i32.load8_u
  ;; CHECK-NEXT:                             (local.get $8)
  ;; CHECK-NEXT:                            )
  ;; CHECK-NEXT:                           )
  ;; CHECK-NEXT:                          )
- ;; CHECK-NEXT:                          (br_if $while-in12
+ ;; CHECK-NEXT:                          (br_if $while-in9
  ;; CHECK-NEXT:                           (i32.and
  ;; CHECK-NEXT:                            (local.tee $9
  ;; CHECK-NEXT:                             (i32.ne
@@ -8499,7 +8482,7 @@
  ;; CHECK-NEXT:                         (local.get $5)
  ;; CHECK-NEXT:                        )
  ;; CHECK-NEXT:                       )
- ;; CHECK-NEXT:                       (br_if $__rjti$29
+ ;; CHECK-NEXT:                       (br_if $__rjti$28
  ;; CHECK-NEXT:                        (local.get $9)
  ;; CHECK-NEXT:                       )
  ;; CHECK-NEXT:                       (local.set $9
@@ -8515,15 +8498,15 @@
  ;; CHECK-NEXT:                        (local.get $8)
  ;; CHECK-NEXT:                       )
  ;; CHECK-NEXT:                       (block
- ;; CHECK-NEXT:                        (block $__rjto$013
- ;; CHECK-NEXT:                         (block $__rjti$014
- ;; CHECK-NEXT:                          (br_if $__rjti$014
+ ;; CHECK-NEXT:                        (block $__rjto$010
+ ;; CHECK-NEXT:                         (block $__rjti$011
+ ;; CHECK-NEXT:                          (br_if $__rjti$011
  ;; CHECK-NEXT:                           (i32.le_u
  ;; CHECK-NEXT:                            (local.get $9)
  ;; CHECK-NEXT:                            (i32.const 3)
  ;; CHECK-NEXT:                           )
  ;; CHECK-NEXT:                          )
- ;; CHECK-NEXT:                          (loop $while-in315
+ ;; CHECK-NEXT:                          (loop $while-in312
  ;; CHECK-NEXT:                           (if
  ;; CHECK-NEXT:                            (i32.eqz
  ;; CHECK-NEXT:                             (i32.and
@@ -8551,7 +8534,7 @@
  ;; CHECK-NEXT:                               (i32.const 4)
  ;; CHECK-NEXT:                              )
  ;; CHECK-NEXT:                             )
- ;; CHECK-NEXT:                             (br_if $while-in315
+ ;; CHECK-NEXT:                             (br_if $while-in312
  ;; CHECK-NEXT:                              (i32.gt_u
  ;; CHECK-NEXT:                               (local.tee $9
  ;; CHECK-NEXT:                                (i32.sub
@@ -8562,11 +8545,11 @@
  ;; CHECK-NEXT:                               (i32.const 3)
  ;; CHECK-NEXT:                              )
  ;; CHECK-NEXT:                             )
- ;; CHECK-NEXT:                             (br $__rjti$014)
+ ;; CHECK-NEXT:                             (br $__rjti$011)
  ;; CHECK-NEXT:                            )
  ;; CHECK-NEXT:                           )
  ;; CHECK-NEXT:                          )
- ;; CHECK-NEXT:                          (br $__rjto$013)
+ ;; CHECK-NEXT:                          (br $__rjto$010)
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                         (if
  ;; CHECK-NEXT:                          (i32.eqz
@@ -8609,7 +8592,7 @@
  ;; CHECK-NEXT:                       )
  ;; CHECK-NEXT:                      )
  ;; CHECK-NEXT:                     )
- ;; CHECK-NEXT:                     (local.set $17
+ ;; CHECK-NEXT:                     (local.set $18
  ;; CHECK-NEXT:                      (i32.eqz
  ;; CHECK-NEXT:                       (local.tee $15
  ;; CHECK-NEXT:                        (select
@@ -8632,7 +8615,7 @@
  ;; CHECK-NEXT:                         (local.get $5)
  ;; CHECK-NEXT:                        )
  ;; CHECK-NEXT:                       )
- ;; CHECK-NEXT:                       (local.get $17)
+ ;; CHECK-NEXT:                       (local.get $18)
  ;; CHECK-NEXT:                      )
  ;; CHECK-NEXT:                     )
  ;; CHECK-NEXT:                     (local.set $8
@@ -8648,7 +8631,7 @@
  ;; CHECK-NEXT:                        (local.get $6)
  ;; CHECK-NEXT:                       )
  ;; CHECK-NEXT:                       (local.get $15)
- ;; CHECK-NEXT:                       (local.get $17)
+ ;; CHECK-NEXT:                       (local.get $18)
  ;; CHECK-NEXT:                      )
  ;; CHECK-NEXT:                     )
  ;; CHECK-NEXT:                    )
@@ -8719,7 +8702,7 @@
  ;; CHECK-NEXT:                      (i32.const 0)
  ;; CHECK-NEXT:                     )
  ;; CHECK-NEXT:                     (block
- ;; CHECK-NEXT:                      (local.set $18
+ ;; CHECK-NEXT:                      (local.set $17
  ;; CHECK-NEXT:                       (i32.const -1)
  ;; CHECK-NEXT:                      )
  ;; CHECK-NEXT:                      (br $label$break$L1)
@@ -8991,7 +8974,7 @@
  ;; CHECK-NEXT:    (i32.eqz
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (local.set $18
+ ;; CHECK-NEXT:    (local.set $17
  ;; CHECK-NEXT:     (if (result i32)
  ;; CHECK-NEXT:      (local.get $1)
  ;; CHECK-NEXT:      (block (result i32)
@@ -9034,7 +9017,7 @@
  ;; CHECK-NEXT:            (i32.const 10)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (local.set $18
+ ;; CHECK-NEXT:          (local.set $17
  ;; CHECK-NEXT:           (i32.const 1)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (br $label$break$L343)
@@ -9058,7 +9041,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (block
- ;; CHECK-NEXT:           (local.set $18
+ ;; CHECK-NEXT:           (local.set $17
  ;; CHECK-NEXT:            (i32.const -1)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (br $label$break$L343)
@@ -9088,7 +9071,7 @@
  ;; CHECK-NEXT:  (global.set $STACKTOP
  ;; CHECK-NEXT:   (local.get $13)
  ;; CHECK-NEXT:  )
- ;; CHECK-NEXT:  (local.get $18)
+ ;; CHECK-NEXT:  (local.get $17)
  ;; CHECK-NEXT: )
  (func $_printf_core (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (result i32)
   (local $5 i32)
@@ -14809,7 +14792,6 @@
   )
  )
  ;; CHECK:      (func $_fmt_u (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
- ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:   (loop $while-in
@@ -14838,9 +14820,6 @@
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (local.set $3
- ;; CHECK-NEXT:     (global.get $tempRet0)
- ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (i32.gt_u
  ;; CHECK-NEXT:      (local.get $1)
@@ -14848,7 +14827,7 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (local.set $1
- ;; CHECK-NEXT:       (local.get $3)
+ ;; CHECK-NEXT:       (global.get $tempRet0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (br $while-in)
  ;; CHECK-NEXT:     )
@@ -15054,7 +15033,7 @@
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $_memset
  ;; CHECK-NEXT:       (local.get $6)
@@ -15091,11 +15070,11 @@
  ;; CHECK-NEXT:       (local.get $5)
  ;; CHECK-NEXT:       (i32.const 255)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block271
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (loop $while-in
  ;; CHECK-NEXT:        (if
  ;; CHECK-NEXT:         (local.get $4)
- ;; CHECK-NEXT:         (block $block273
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (drop
  ;; CHECK-NEXT:           (call $___fwritex
  ;; CHECK-NEXT:            (local.get $6)
@@ -15339,7 +15318,7 @@
  ;; CHECK-NEXT:      (local.get $0)
  ;; CHECK-NEXT:      (i32.const 245)
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (if
  ;; CHECK-NEXT:       (i32.and
  ;; CHECK-NEXT:        (local.tee $5
@@ -15374,7 +15353,7 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (i32.const 3)
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block275
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (local.set $10
  ;; CHECK-NEXT:         (i32.load
  ;; CHECK-NEXT:          (local.tee $1
@@ -15431,7 +15410,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block277
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (if
  ;; CHECK-NEXT:           (i32.lt_u
  ;; CHECK-NEXT:            (local.get $10)
@@ -15453,7 +15432,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (local.get $7)
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block280
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store
  ;; CHECK-NEXT:             (local.get $0)
  ;; CHECK-NEXT:             (local.get $2)
@@ -15510,10 +15489,10 @@
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block282
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (if
  ;; CHECK-NEXT:         (local.get $5)
- ;; CHECK-NEXT:         (block $block284
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (local.set $10
  ;; CHECK-NEXT:           (i32.and
  ;; CHECK-NEXT:            (i32.shr_u
@@ -15657,7 +15636,7 @@
  ;; CHECK-NEXT:            (local.get $10)
  ;; CHECK-NEXT:            (local.get $9)
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block286
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store
  ;; CHECK-NEXT:             (i32.const 176)
  ;; CHECK-NEXT:             (i32.and
@@ -15675,7 +15654,7 @@
  ;; CHECK-NEXT:             (local.get $0)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block287
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (if
  ;; CHECK-NEXT:             (i32.lt_u
  ;; CHECK-NEXT:              (local.get $9)
@@ -15697,7 +15676,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:              (local.get $12)
  ;; CHECK-NEXT:             )
- ;; CHECK-NEXT:             (block $block290
+ ;; CHECK-NEXT:             (block
  ;; CHECK-NEXT:              (i32.store
  ;; CHECK-NEXT:               (local.get $0)
  ;; CHECK-NEXT:               (local.get $10)
@@ -15752,7 +15731,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (if
  ;; CHECK-NEXT:           (local.get $8)
- ;; CHECK-NEXT:           (block $block292
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (local.set $12
  ;; CHECK-NEXT:             (i32.load
  ;; CHECK-NEXT:              (i32.const 196)
@@ -15803,7 +15782,7 @@
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:              (call $_abort)
- ;; CHECK-NEXT:              (block $block295
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (local.set $2
  ;; CHECK-NEXT:                (local.get $3)
  ;; CHECK-NEXT:               )
@@ -15812,7 +15791,7 @@
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
- ;; CHECK-NEXT:             (block $block296
+ ;; CHECK-NEXT:             (block
  ;; CHECK-NEXT:              (i32.store
  ;; CHECK-NEXT:               (i32.const 176)
  ;; CHECK-NEXT:               (i32.or
@@ -15868,7 +15847,7 @@
  ;; CHECK-NEXT:           (i32.const 180)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block298
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (local.set $2
  ;; CHECK-NEXT:           (i32.and
  ;; CHECK-NEXT:            (i32.shr_u
@@ -16001,7 +15980,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block301
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (local.set $10
  ;; CHECK-NEXT:                (local.get $7)
  ;; CHECK-NEXT:               )
@@ -16086,7 +16065,7 @@
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:             (local.get $5)
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block305
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (i32.eqz
  ;; CHECK-NEXT:               (local.tee $1
@@ -16113,7 +16092,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (block $block308
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (local.set $9
  ;; CHECK-NEXT:                 (i32.const 0)
  ;; CHECK-NEXT:                )
@@ -16133,7 +16112,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (block $block310
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (local.set $1
  ;; CHECK-NEXT:                 (local.get $2)
  ;; CHECK-NEXT:                )
@@ -16154,7 +16133,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (block $block312
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (local.set $1
  ;; CHECK-NEXT:                 (local.get $2)
  ;; CHECK-NEXT:                )
@@ -16171,7 +16150,7 @@
  ;; CHECK-NEXT:               (local.get $12)
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:              (call $_abort)
- ;; CHECK-NEXT:              (block $block314
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store
  ;; CHECK-NEXT:                (local.get $0)
  ;; CHECK-NEXT:                (i32.const 0)
@@ -16182,7 +16161,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block315
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (i32.lt_u
  ;; CHECK-NEXT:               (local.tee $7
@@ -16220,7 +16199,7 @@
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:               (local.get $5)
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block319
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store
  ;; CHECK-NEXT:                (local.get $2)
  ;; CHECK-NEXT:                (local.get $0)
@@ -16241,7 +16220,7 @@
  ;; CHECK-NEXT:          (block $do-once8
  ;; CHECK-NEXT:           (if
  ;; CHECK-NEXT:            (local.get $8)
- ;; CHECK-NEXT:            (block $block321
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (i32.eq
  ;; CHECK-NEXT:               (local.get $5)
@@ -16261,7 +16240,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block323
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store
  ;; CHECK-NEXT:                (local.get $0)
  ;; CHECK-NEXT:                (local.get $9)
@@ -16270,7 +16249,7 @@
  ;; CHECK-NEXT:                (i32.eqz
  ;; CHECK-NEXT:                 (local.get $9)
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block325
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (i32.store
  ;; CHECK-NEXT:                  (i32.const 180)
  ;; CHECK-NEXT:                  (i32.and
@@ -16290,7 +16269,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block326
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (if
  ;; CHECK-NEXT:                (i32.lt_u
  ;; CHECK-NEXT:                 (local.get $8)
@@ -16355,7 +16334,7 @@
  ;; CHECK-NEXT:                (local.get $0)
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:               (call $_abort)
- ;; CHECK-NEXT:               (block $block332
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (i32.store offset=16
  ;; CHECK-NEXT:                 (local.get $9)
  ;; CHECK-NEXT:                 (local.get $1)
@@ -16381,7 +16360,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:               (call $_abort)
- ;; CHECK-NEXT:               (block $block335
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (i32.store offset=20
  ;; CHECK-NEXT:                 (local.get $9)
  ;; CHECK-NEXT:                 (local.get $0)
@@ -16401,7 +16380,7 @@
  ;; CHECK-NEXT:            (local.get $10)
  ;; CHECK-NEXT:            (i32.const 16)
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block337
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store offset=4
  ;; CHECK-NEXT:             (local.get $5)
  ;; CHECK-NEXT:             (i32.or
@@ -16432,7 +16411,7 @@
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block338
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store offset=4
  ;; CHECK-NEXT:             (local.get $5)
  ;; CHECK-NEXT:             (i32.or
@@ -16460,7 +16439,7 @@
  ;; CHECK-NEXT:               (i32.const 184)
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
- ;; CHECK-NEXT:             (block $block340
+ ;; CHECK-NEXT:             (block
  ;; CHECK-NEXT:              (local.set $4
  ;; CHECK-NEXT:               (i32.load
  ;; CHECK-NEXT:                (i32.const 196)
@@ -16511,7 +16490,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:                (call $_abort)
- ;; CHECK-NEXT:                (block $block343
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (local.set $6
  ;; CHECK-NEXT:                  (local.get $1)
  ;; CHECK-NEXT:                 )
@@ -16520,7 +16499,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (block $block344
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (i32.store
  ;; CHECK-NEXT:                 (i32.const 176)
  ;; CHECK-NEXT:                 (i32.or
@@ -16592,7 +16571,7 @@
  ;; CHECK-NEXT:      (local.set $0
  ;; CHECK-NEXT:       (i32.const -1)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block346
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $2
  ;; CHECK-NEXT:        (i32.and
  ;; CHECK-NEXT:         (local.tee $0
@@ -16610,7 +16589,7 @@
  ;; CHECK-NEXT:          (i32.const 180)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block348
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (local.set $14
  ;; CHECK-NEXT:          (if (result i32)
  ;; CHECK-NEXT:           (local.tee $0
@@ -16725,7 +16704,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block352
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $6
  ;; CHECK-NEXT:              (i32.const 0)
  ;; CHECK-NEXT:             )
@@ -16774,7 +16753,7 @@
  ;; CHECK-NEXT:                 (local.get $9)
  ;; CHECK-NEXT:                 (local.get $2)
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block355
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (local.set $1
  ;; CHECK-NEXT:                  (local.get $4)
  ;; CHECK-NEXT:                 )
@@ -16783,7 +16762,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                 (br $__rjti$3)
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block356
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (local.set $3
  ;; CHECK-NEXT:                  (local.get $4)
  ;; CHECK-NEXT:                 )
@@ -16843,7 +16822,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:              (if
  ;; CHECK-NEXT:               (local.get $6)
- ;; CHECK-NEXT:               (block $block358
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (local.set $4
  ;; CHECK-NEXT:                 (local.get $0)
  ;; CHECK-NEXT:                )
@@ -16851,7 +16830,7 @@
  ;; CHECK-NEXT:                 (local.get $1)
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (block $block359
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (local.set $6
  ;; CHECK-NEXT:                 (local.get $0)
  ;; CHECK-NEXT:                )
@@ -16866,7 +16845,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block360
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $4
  ;; CHECK-NEXT:              (i32.const 0)
  ;; CHECK-NEXT:             )
@@ -16884,7 +16863,7 @@
  ;; CHECK-NEXT:              (local.get $0)
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block362
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (i32.eqz
  ;; CHECK-NEXT:               (local.tee $1
@@ -16905,7 +16884,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block364
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (local.set $0
  ;; CHECK-NEXT:                (local.get $2)
  ;; CHECK-NEXT:               )
@@ -17014,7 +16993,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (if
  ;; CHECK-NEXT:            (local.get $4)
- ;; CHECK-NEXT:            (block $block366
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $1
  ;; CHECK-NEXT:              (local.get $3)
  ;; CHECK-NEXT:             )
@@ -17066,7 +17045,7 @@
  ;; CHECK-NEXT:              (local.get $3)
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block368
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $3
  ;; CHECK-NEXT:              (local.get $4)
  ;; CHECK-NEXT:             )
@@ -17100,7 +17079,7 @@
  ;; CHECK-NEXT:             (local.get $2)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block371
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (if
  ;; CHECK-NEXT:             (i32.lt_u
  ;; CHECK-NEXT:              (local.get $4)
@@ -17139,7 +17118,7 @@
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:               (local.get $4)
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block375
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (if
  ;; CHECK-NEXT:                (i32.eqz
  ;; CHECK-NEXT:                 (local.tee $1
@@ -17166,7 +17145,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block378
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (local.set $11
  ;; CHECK-NEXT:                   (i32.const 0)
  ;; CHECK-NEXT:                  )
@@ -17186,7 +17165,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block380
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (local.set $1
  ;; CHECK-NEXT:                   (local.get $7)
  ;; CHECK-NEXT:                  )
@@ -17207,7 +17186,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block382
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (local.set $1
  ;; CHECK-NEXT:                   (local.get $7)
  ;; CHECK-NEXT:                  )
@@ -17224,7 +17203,7 @@
  ;; CHECK-NEXT:                 (local.get $12)
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:                (call $_abort)
- ;; CHECK-NEXT:                (block $block384
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (i32.store
  ;; CHECK-NEXT:                  (local.get $0)
  ;; CHECK-NEXT:                  (i32.const 0)
@@ -17235,7 +17214,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block385
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (if
  ;; CHECK-NEXT:                (i32.lt_u
  ;; CHECK-NEXT:                 (local.tee $10
@@ -17273,7 +17252,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                 (local.get $4)
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block389
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (i32.store
  ;; CHECK-NEXT:                  (local.get $7)
  ;; CHECK-NEXT:                  (local.get $0)
@@ -17294,7 +17273,7 @@
  ;; CHECK-NEXT:            (block $do-once21
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (local.get $9)
- ;; CHECK-NEXT:              (block $block391
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (if
  ;; CHECK-NEXT:                (i32.eq
  ;; CHECK-NEXT:                 (local.get $4)
@@ -17314,7 +17293,7 @@
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block393
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (i32.store
  ;; CHECK-NEXT:                  (local.get $0)
  ;; CHECK-NEXT:                  (local.get $11)
@@ -17323,7 +17302,7 @@
  ;; CHECK-NEXT:                  (i32.eqz
  ;; CHECK-NEXT:                   (local.get $11)
  ;; CHECK-NEXT:                  )
- ;; CHECK-NEXT:                  (block $block395
+ ;; CHECK-NEXT:                  (block
  ;; CHECK-NEXT:                   (i32.store
  ;; CHECK-NEXT:                    (i32.const 180)
  ;; CHECK-NEXT:                    (i32.and
@@ -17343,7 +17322,7 @@
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block396
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (if
  ;; CHECK-NEXT:                  (i32.lt_u
  ;; CHECK-NEXT:                   (local.get $9)
@@ -17408,7 +17387,7 @@
  ;; CHECK-NEXT:                  (local.get $0)
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                 (call $_abort)
- ;; CHECK-NEXT:                 (block $block402
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (i32.store offset=16
  ;; CHECK-NEXT:                   (local.get $11)
  ;; CHECK-NEXT:                   (local.get $1)
@@ -17434,7 +17413,7 @@
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                 (call $_abort)
- ;; CHECK-NEXT:                 (block $block405
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (i32.store offset=20
  ;; CHECK-NEXT:                   (local.get $11)
  ;; CHECK-NEXT:                   (local.get $0)
@@ -17455,7 +17434,7 @@
  ;; CHECK-NEXT:               (local.get $3)
  ;; CHECK-NEXT:               (i32.const 16)
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block407
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store offset=4
  ;; CHECK-NEXT:                (local.get $4)
  ;; CHECK-NEXT:                (i32.or
@@ -17486,7 +17465,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block408
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store offset=4
  ;; CHECK-NEXT:                (local.get $4)
  ;; CHECK-NEXT:                (i32.or
@@ -17519,7 +17498,7 @@
  ;; CHECK-NEXT:                 (local.get $3)
  ;; CHECK-NEXT:                 (i32.const 256)
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block410
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (local.set $3
  ;; CHECK-NEXT:                  (i32.add
  ;; CHECK-NEXT:                   (i32.shl
@@ -17560,7 +17539,7 @@
  ;; CHECK-NEXT:                    )
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                   (call $_abort)
- ;; CHECK-NEXT:                   (block $block413
+ ;; CHECK-NEXT:                   (block
  ;; CHECK-NEXT:                    (local.set $13
  ;; CHECK-NEXT:                     (local.get $1)
  ;; CHECK-NEXT:                    )
@@ -17569,7 +17548,7 @@
  ;; CHECK-NEXT:                    )
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
- ;; CHECK-NEXT:                  (block $block414
+ ;; CHECK-NEXT:                  (block
  ;; CHECK-NEXT:                   (i32.store
  ;; CHECK-NEXT:                    (i32.const 176)
  ;; CHECK-NEXT:                    (i32.or
@@ -17745,7 +17724,7 @@
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
- ;; CHECK-NEXT:                (block $block418
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (i32.store
  ;; CHECK-NEXT:                  (i32.const 180)
  ;; CHECK-NEXT:                  (i32.or
@@ -17836,7 +17815,7 @@
  ;; CHECK-NEXT:                     )
  ;; CHECK-NEXT:                    )
  ;; CHECK-NEXT:                   )
- ;; CHECK-NEXT:                   (block $block420
+ ;; CHECK-NEXT:                   (block
  ;; CHECK-NEXT:                    (local.set $7
  ;; CHECK-NEXT:                     (local.get $2)
  ;; CHECK-NEXT:                    )
@@ -17855,7 +17834,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                  (call $_abort)
- ;; CHECK-NEXT:                  (block $block422
+ ;; CHECK-NEXT:                  (block
  ;; CHECK-NEXT:                   (i32.store
  ;; CHECK-NEXT:                    (local.get $7)
  ;; CHECK-NEXT:                    (local.get $6)
@@ -17901,7 +17880,7 @@
  ;; CHECK-NEXT:                   (local.get $1)
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block424
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (i32.store offset=12
  ;; CHECK-NEXT:                   (local.get $2)
  ;; CHECK-NEXT:                   (local.get $6)
@@ -17962,7 +17941,7 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block426
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $2
  ;; CHECK-NEXT:      (i32.load
  ;; CHECK-NEXT:       (i32.const 196)
@@ -17978,7 +17957,7 @@
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (i32.const 15)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block428
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (i32.store
  ;; CHECK-NEXT:        (i32.const 196)
  ;; CHECK-NEXT:        (local.tee $1
@@ -18014,7 +17993,7 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block429
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (i32.store
  ;; CHECK-NEXT:        (i32.const 184)
  ;; CHECK-NEXT:        (i32.const 0)
@@ -18086,7 +18065,7 @@
  ;; CHECK-NEXT:      (local.get $1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (call $_abort)
- ;; CHECK-NEXT:     (block $block432
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (i32.store
  ;; CHECK-NEXT:       (i32.const 656)
  ;; CHECK-NEXT:       (local.get $1)
@@ -18207,7 +18186,7 @@
  ;; CHECK-NEXT:        (i32.const 4)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block437
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (block $label$break$L279
  ;; CHECK-NEXT:        (block $__rjti$5
  ;; CHECK-NEXT:         (block $__rjti$4
@@ -18249,7 +18228,7 @@
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:               (local.get $4)
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block440
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (local.set $4
  ;; CHECK-NEXT:                (local.get $1)
  ;; CHECK-NEXT:               )
@@ -18304,7 +18283,7 @@
  ;; CHECK-NEXT:              (i32.const -1)
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block443
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $2
  ;; CHECK-NEXT:              (local.get $1)
  ;; CHECK-NEXT:             )
@@ -18323,7 +18302,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (i32.const -1)
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block445
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (local.set $3
  ;; CHECK-NEXT:            (if (result i32)
  ;; CHECK-NEXT:             (i32.and
@@ -18381,7 +18360,7 @@
  ;; CHECK-NEXT:              (i32.const 2147483647)
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block448
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (local.tee $2
  ;; CHECK-NEXT:               (i32.load
@@ -18474,7 +18453,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (i32.const -1)
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block453
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (drop
  ;; CHECK-NEXT:             (call $_sbrk
  ;; CHECK-NEXT:              (local.get $4)
@@ -18502,7 +18481,7 @@
  ;; CHECK-NEXT:          (local.get $2)
  ;; CHECK-NEXT:          (i32.const -1)
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block455
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (local.set $1
  ;; CHECK-NEXT:           (local.get $2)
  ;; CHECK-NEXT:          )
@@ -18599,7 +18578,7 @@
  ;; CHECK-NEXT:        (i32.const 200)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block460
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $2
  ;; CHECK-NEXT:        (i32.const 624)
  ;; CHECK-NEXT:       )
@@ -18658,7 +18637,7 @@
  ;; CHECK-NEXT:            (local.get $11)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block463
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (i32.store
  ;; CHECK-NEXT:            (local.get $4)
  ;; CHECK-NEXT:            (i32.add
@@ -18745,7 +18724,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block465
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (i32.const 192)
  ;; CHECK-NEXT:          (local.get $1)
@@ -18774,7 +18753,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (local.get $11)
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block467
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (local.set $5
  ;; CHECK-NEXT:             (local.get $2)
  ;; CHECK-NEXT:            )
@@ -18804,7 +18783,7 @@
  ;; CHECK-NEXT:         (local.set $4
  ;; CHECK-NEXT:          (i32.const 624)
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block469
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (i32.store
  ;; CHECK-NEXT:           (local.get $5)
  ;; CHECK-NEXT:           (local.get $1)
@@ -18897,7 +18876,7 @@
  ;; CHECK-NEXT:             (local.get $5)
  ;; CHECK-NEXT:             (local.get $6)
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block471
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (i32.store
  ;; CHECK-NEXT:              (i32.const 188)
  ;; CHECK-NEXT:              (local.tee $0
@@ -18921,7 +18900,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block472
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (if
  ;; CHECK-NEXT:              (i32.eq
  ;; CHECK-NEXT:               (local.get $5)
@@ -18929,7 +18908,7 @@
  ;; CHECK-NEXT:                (i32.const 196)
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block474
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store
  ;; CHECK-NEXT:                (i32.const 184)
  ;; CHECK-NEXT:                (local.tee $0
@@ -18978,7 +18957,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                   (i32.const 1)
  ;; CHECK-NEXT:                  )
- ;; CHECK-NEXT:                  (block $block476 (result i32)
+ ;; CHECK-NEXT:                  (block (result i32)
  ;; CHECK-NEXT:                   (local.set $11
  ;; CHECK-NEXT:                    (i32.and
  ;; CHECK-NEXT:                     (local.get $0)
@@ -18997,7 +18976,7 @@
  ;; CHECK-NEXT:                      (local.get $0)
  ;; CHECK-NEXT:                      (i32.const 256)
  ;; CHECK-NEXT:                     )
- ;; CHECK-NEXT:                     (block $block478
+ ;; CHECK-NEXT:                     (block
  ;; CHECK-NEXT:                      (local.set $2
  ;; CHECK-NEXT:                       (i32.load offset=12
  ;; CHECK-NEXT:                        (local.get $5)
@@ -19021,7 +19000,7 @@
  ;; CHECK-NEXT:                          )
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                        (block $block480
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (if
  ;; CHECK-NEXT:                          (i32.lt_u
  ;; CHECK-NEXT:                           (local.get $3)
@@ -19046,7 +19025,7 @@
  ;; CHECK-NEXT:                        (local.get $2)
  ;; CHECK-NEXT:                        (local.get $3)
  ;; CHECK-NEXT:                       )
- ;; CHECK-NEXT:                       (block $block483
+ ;; CHECK-NEXT:                       (block
  ;; CHECK-NEXT:                        (i32.store
  ;; CHECK-NEXT:                         (i32.const 176)
  ;; CHECK-NEXT:                         (i32.and
@@ -19077,7 +19056,7 @@
  ;; CHECK-NEXT:                          (i32.const 8)
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                        (block $block485
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (if
  ;; CHECK-NEXT:                          (i32.lt_u
  ;; CHECK-NEXT:                           (local.get $2)
@@ -19097,7 +19076,7 @@
  ;; CHECK-NEXT:                           )
  ;; CHECK-NEXT:                           (local.get $5)
  ;; CHECK-NEXT:                          )
- ;; CHECK-NEXT:                          (block $block488
+ ;; CHECK-NEXT:                          (block
  ;; CHECK-NEXT:                           (local.set $15
  ;; CHECK-NEXT:                            (local.get $0)
  ;; CHECK-NEXT:                           )
@@ -19117,7 +19096,7 @@
  ;; CHECK-NEXT:                       (local.get $3)
  ;; CHECK-NEXT:                      )
  ;; CHECK-NEXT:                     )
- ;; CHECK-NEXT:                     (block $block489
+ ;; CHECK-NEXT:                     (block
  ;; CHECK-NEXT:                      (local.set $6
  ;; CHECK-NEXT:                       (i32.load offset=24
  ;; CHECK-NEXT:                        (local.get $5)
@@ -19133,7 +19112,7 @@
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                         (local.get $5)
  ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                        (block $block491
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (if
  ;; CHECK-NEXT:                          (i32.eqz
  ;; CHECK-NEXT:                           (local.tee $1
@@ -19161,7 +19140,7 @@
  ;; CHECK-NEXT:                           (local.set $0
  ;; CHECK-NEXT:                            (local.get $3)
  ;; CHECK-NEXT:                           )
- ;; CHECK-NEXT:                           (block $block494
+ ;; CHECK-NEXT:                           (block
  ;; CHECK-NEXT:                            (local.set $12
  ;; CHECK-NEXT:                             (i32.const 0)
  ;; CHECK-NEXT:                            )
@@ -19181,7 +19160,7 @@
  ;; CHECK-NEXT:                             )
  ;; CHECK-NEXT:                            )
  ;; CHECK-NEXT:                           )
- ;; CHECK-NEXT:                           (block $block496
+ ;; CHECK-NEXT:                           (block
  ;; CHECK-NEXT:                            (local.set $1
  ;; CHECK-NEXT:                             (local.get $3)
  ;; CHECK-NEXT:                            )
@@ -19202,7 +19181,7 @@
  ;; CHECK-NEXT:                             )
  ;; CHECK-NEXT:                            )
  ;; CHECK-NEXT:                           )
- ;; CHECK-NEXT:                           (block $block498
+ ;; CHECK-NEXT:                           (block
  ;; CHECK-NEXT:                            (local.set $1
  ;; CHECK-NEXT:                             (local.get $3)
  ;; CHECK-NEXT:                            )
@@ -19219,7 +19198,7 @@
  ;; CHECK-NEXT:                           (local.get $4)
  ;; CHECK-NEXT:                          )
  ;; CHECK-NEXT:                          (call $_abort)
- ;; CHECK-NEXT:                          (block $block500
+ ;; CHECK-NEXT:                          (block
  ;; CHECK-NEXT:                           (i32.store
  ;; CHECK-NEXT:                            (local.get $0)
  ;; CHECK-NEXT:                            (i32.const 0)
@@ -19230,7 +19209,7 @@
  ;; CHECK-NEXT:                          )
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                        (block $block501
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (if
  ;; CHECK-NEXT:                          (i32.lt_u
  ;; CHECK-NEXT:                           (local.tee $2
@@ -19268,7 +19247,7 @@
  ;; CHECK-NEXT:                           )
  ;; CHECK-NEXT:                           (local.get $5)
  ;; CHECK-NEXT:                          )
- ;; CHECK-NEXT:                          (block $block505
+ ;; CHECK-NEXT:                          (block
  ;; CHECK-NEXT:                           (i32.store
  ;; CHECK-NEXT:                            (local.get $3)
  ;; CHECK-NEXT:                            (local.get $0)
@@ -19311,7 +19290,7 @@
  ;; CHECK-NEXT:                          )
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                        (block $block507
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (i32.store
  ;; CHECK-NEXT:                          (local.get $0)
  ;; CHECK-NEXT:                          (local.get $12)
@@ -19336,7 +19315,7 @@
  ;; CHECK-NEXT:                         )
  ;; CHECK-NEXT:                         (br $label$break$L331)
  ;; CHECK-NEXT:                        )
- ;; CHECK-NEXT:                        (block $block508
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (if
  ;; CHECK-NEXT:                          (i32.lt_u
  ;; CHECK-NEXT:                           (local.get $6)
@@ -19407,7 +19386,7 @@
  ;; CHECK-NEXT:                         (local.get $1)
  ;; CHECK-NEXT:                        )
  ;; CHECK-NEXT:                        (call $_abort)
- ;; CHECK-NEXT:                        (block $block514
+ ;; CHECK-NEXT:                        (block
  ;; CHECK-NEXT:                         (i32.store offset=16
  ;; CHECK-NEXT:                          (local.get $12)
  ;; CHECK-NEXT:                          (local.get $3)
@@ -19436,7 +19415,7 @@
  ;; CHECK-NEXT:                        )
  ;; CHECK-NEXT:                       )
  ;; CHECK-NEXT:                       (call $_abort)
- ;; CHECK-NEXT:                       (block $block516
+ ;; CHECK-NEXT:                       (block
  ;; CHECK-NEXT:                        (i32.store offset=20
  ;; CHECK-NEXT:                         (local.get $12)
  ;; CHECK-NEXT:                         (local.get $0)
@@ -19499,7 +19478,7 @@
  ;; CHECK-NEXT:               (local.get $7)
  ;; CHECK-NEXT:               (i32.const 256)
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block518
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (local.set $3
  ;; CHECK-NEXT:                (i32.add
  ;; CHECK-NEXT:                 (i32.shl
@@ -19524,7 +19503,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block520
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (if
  ;; CHECK-NEXT:                   (i32.ge_u
  ;; CHECK-NEXT:                    (local.tee $0
@@ -19541,7 +19520,7 @@
  ;; CHECK-NEXT:                     (i32.const 192)
  ;; CHECK-NEXT:                    )
  ;; CHECK-NEXT:                   )
- ;; CHECK-NEXT:                   (block $block522
+ ;; CHECK-NEXT:                   (block
  ;; CHECK-NEXT:                    (local.set $16
  ;; CHECK-NEXT:                     (local.get $1)
  ;; CHECK-NEXT:                    )
@@ -19553,7 +19532,7 @@
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                  (call $_abort)
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block523
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (i32.store
  ;; CHECK-NEXT:                   (i32.const 176)
  ;; CHECK-NEXT:                   (i32.or
@@ -19604,7 +19583,7 @@
  ;; CHECK-NEXT:                     (i32.const 8)
  ;; CHECK-NEXT:                    )
  ;; CHECK-NEXT:                   )
- ;; CHECK-NEXT:                   (block $block525 (result i32)
+ ;; CHECK-NEXT:                   (block (result i32)
  ;; CHECK-NEXT:                    (drop
  ;; CHECK-NEXT:                     (br_if $do-once65
  ;; CHECK-NEXT:                      (i32.const 31)
@@ -19736,7 +19715,7 @@
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
- ;; CHECK-NEXT:              (block $block527
+ ;; CHECK-NEXT:              (block
  ;; CHECK-NEXT:               (i32.store
  ;; CHECK-NEXT:                (i32.const 180)
  ;; CHECK-NEXT:                (i32.or
@@ -19827,7 +19806,7 @@
  ;; CHECK-NEXT:                   )
  ;; CHECK-NEXT:                  )
  ;; CHECK-NEXT:                 )
- ;; CHECK-NEXT:                 (block $block529
+ ;; CHECK-NEXT:                 (block
  ;; CHECK-NEXT:                  (local.set $2
  ;; CHECK-NEXT:                   (local.get $3)
  ;; CHECK-NEXT:                  )
@@ -19846,7 +19825,7 @@
  ;; CHECK-NEXT:                 )
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:                (call $_abort)
- ;; CHECK-NEXT:                (block $block531
+ ;; CHECK-NEXT:                (block
  ;; CHECK-NEXT:                 (i32.store
  ;; CHECK-NEXT:                  (local.get $2)
  ;; CHECK-NEXT:                  (local.get $8)
@@ -19892,7 +19871,7 @@
  ;; CHECK-NEXT:                 (local.get $1)
  ;; CHECK-NEXT:                )
  ;; CHECK-NEXT:               )
- ;; CHECK-NEXT:               (block $block533
+ ;; CHECK-NEXT:               (block
  ;; CHECK-NEXT:                (i32.store offset=12
  ;; CHECK-NEXT:                 (local.get $2)
  ;; CHECK-NEXT:                 (local.get $8)
@@ -20152,7 +20131,7 @@
  ;; CHECK-NEXT:         (local.get $11)
  ;; CHECK-NEXT:         (local.get $6)
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block536
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (local.get $4)
  ;; CHECK-NEXT:          (i32.and
@@ -20189,7 +20168,7 @@
  ;; CHECK-NEXT:           (local.get $5)
  ;; CHECK-NEXT:           (i32.const 256)
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block538
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (local.set $2
  ;; CHECK-NEXT:            (i32.add
  ;; CHECK-NEXT:             (i32.shl
@@ -20230,7 +20209,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:             (call $_abort)
- ;; CHECK-NEXT:             (block $block541
+ ;; CHECK-NEXT:             (block
  ;; CHECK-NEXT:              (local.set $17
  ;; CHECK-NEXT:               (local.get $3)
  ;; CHECK-NEXT:              )
@@ -20239,7 +20218,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block542
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (i32.store
  ;; CHECK-NEXT:              (i32.const 176)
  ;; CHECK-NEXT:              (i32.or
@@ -20410,7 +20389,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block546
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (i32.store
  ;; CHECK-NEXT:            (i32.const 180)
  ;; CHECK-NEXT:            (i32.or
@@ -20501,7 +20480,7 @@
  ;; CHECK-NEXT:               )
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
- ;; CHECK-NEXT:             (block $block548
+ ;; CHECK-NEXT:             (block
  ;; CHECK-NEXT:              (local.set $4
  ;; CHECK-NEXT:               (local.get $2)
  ;; CHECK-NEXT:              )
@@ -20520,7 +20499,7 @@
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (call $_abort)
- ;; CHECK-NEXT:            (block $block550
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (i32.store
  ;; CHECK-NEXT:              (local.get $4)
  ;; CHECK-NEXT:              (local.get $6)
@@ -20566,7 +20545,7 @@
  ;; CHECK-NEXT:             (local.get $3)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block552
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store offset=12
  ;; CHECK-NEXT:             (local.get $4)
  ;; CHECK-NEXT:             (local.get $6)
@@ -20594,7 +20573,7 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block553
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (if
  ;; CHECK-NEXT:        (i32.or
  ;; CHECK-NEXT:         (i32.eqz
@@ -26345,7 +26324,7 @@
  ;; CHECK-NEXT:     (local.get $7)
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $2
  ;; CHECK-NEXT:      (local.get $1)
  ;; CHECK-NEXT:     )
@@ -26353,7 +26332,7 @@
  ;; CHECK-NEXT:      (local.get $0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block558
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $7
  ;; CHECK-NEXT:      (i32.load
  ;; CHECK-NEXT:       (local.get $1)
@@ -26393,7 +26372,7 @@
  ;; CHECK-NEXT:        (i32.const 196)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block562
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (if
  ;; CHECK-NEXT:        (i32.ne
  ;; CHECK-NEXT:         (i32.and
@@ -26411,7 +26390,7 @@
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (i32.const 3)
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block564
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (local.set $2
  ;; CHECK-NEXT:          (local.get $1)
  ;; CHECK-NEXT:         )
@@ -26460,7 +26439,7 @@
  ;; CHECK-NEXT:       (local.get $7)
  ;; CHECK-NEXT:       (i32.const 256)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block566
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $6
  ;; CHECK-NEXT:        (i32.load offset=12
  ;; CHECK-NEXT:         (local.get $1)
@@ -26483,7 +26462,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block568
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.lt_u
  ;; CHECK-NEXT:           (local.get $2)
@@ -26507,7 +26486,7 @@
  ;; CHECK-NEXT:         (local.get $6)
  ;; CHECK-NEXT:         (local.get $2)
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block572
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (i32.const 176)
  ;; CHECK-NEXT:          (i32.and
@@ -26543,7 +26522,7 @@
  ;; CHECK-NEXT:          (i32.const 8)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block574
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.lt_u
  ;; CHECK-NEXT:           (local.get $6)
@@ -26602,7 +26581,7 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (local.get $1)
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block578
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (if
  ;; CHECK-NEXT:         (i32.eqz
  ;; CHECK-NEXT:          (local.tee $5
@@ -26630,7 +26609,7 @@
  ;; CHECK-NEXT:          (local.set $4
  ;; CHECK-NEXT:           (local.get $7)
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block581
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (local.set $6
  ;; CHECK-NEXT:            (i32.const 0)
  ;; CHECK-NEXT:           )
@@ -26650,7 +26629,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block583
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (local.set $5
  ;; CHECK-NEXT:            (local.get $7)
  ;; CHECK-NEXT:           )
@@ -26671,7 +26650,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block585
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (local.set $5
  ;; CHECK-NEXT:            (local.get $7)
  ;; CHECK-NEXT:           )
@@ -26688,7 +26667,7 @@
  ;; CHECK-NEXT:          (local.get $11)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (call $_abort)
- ;; CHECK-NEXT:         (block $block587
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (i32.store
  ;; CHECK-NEXT:           (local.get $4)
  ;; CHECK-NEXT:           (i32.const 0)
@@ -26699,7 +26678,7 @@
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block588
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (if
  ;; CHECK-NEXT:         (i32.lt_u
  ;; CHECK-NEXT:          (local.tee $10
@@ -26737,7 +26716,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (local.get $1)
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block592
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (i32.store
  ;; CHECK-NEXT:           (local.get $7)
  ;; CHECK-NEXT:           (local.get $4)
@@ -26757,7 +26736,7 @@
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (if
  ;; CHECK-NEXT:      (local.get $12)
- ;; CHECK-NEXT:      (block $block594
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (if
  ;; CHECK-NEXT:        (i32.eq
  ;; CHECK-NEXT:         (local.get $1)
@@ -26777,7 +26756,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block596
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (local.get $4)
  ;; CHECK-NEXT:          (local.get $6)
@@ -26786,7 +26765,7 @@
  ;; CHECK-NEXT:          (i32.eqz
  ;; CHECK-NEXT:           (local.get $6)
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block598
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (i32.store
  ;; CHECK-NEXT:            (i32.const 180)
  ;; CHECK-NEXT:            (i32.and
@@ -26812,7 +26791,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block599
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.lt_u
  ;; CHECK-NEXT:           (local.get $12)
@@ -26847,7 +26826,7 @@
  ;; CHECK-NEXT:          (i32.eqz
  ;; CHECK-NEXT:           (local.get $6)
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block603
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (local.set $2
  ;; CHECK-NEXT:            (local.get $1)
  ;; CHECK-NEXT:           )
@@ -26891,7 +26870,7 @@
  ;; CHECK-NEXT:          (local.get $5)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (call $_abort)
- ;; CHECK-NEXT:         (block $block607
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (i32.store offset=16
  ;; CHECK-NEXT:           (local.get $6)
  ;; CHECK-NEXT:           (local.get $7)
@@ -26917,7 +26896,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (call $_abort)
- ;; CHECK-NEXT:         (block $block610
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (i32.store offset=20
  ;; CHECK-NEXT:           (local.get $6)
  ;; CHECK-NEXT:           (local.get $4)
@@ -26934,7 +26913,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block611
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (local.set $2
  ;; CHECK-NEXT:          (local.get $1)
  ;; CHECK-NEXT:         )
@@ -26944,7 +26923,7 @@
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block612
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $2
  ;; CHECK-NEXT:        (local.get $1)
  ;; CHECK-NEXT:       )
@@ -26986,7 +26965,7 @@
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block616
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (i32.store
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:     (i32.and
@@ -27009,7 +26988,7 @@
  ;; CHECK-NEXT:     (local.get $3)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block617
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (i32.eq
  ;; CHECK-NEXT:      (local.get $8)
@@ -27017,7 +26996,7 @@
  ;; CHECK-NEXT:       (i32.const 200)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block619
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (i32.store
  ;; CHECK-NEXT:       (i32.const 188)
  ;; CHECK-NEXT:       (local.tee $0
@@ -27067,7 +27046,7 @@
  ;; CHECK-NEXT:       (i32.const 196)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block622
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (i32.store
  ;; CHECK-NEXT:       (i32.const 184)
  ;; CHECK-NEXT:       (local.tee $0
@@ -27121,7 +27100,7 @@
  ;; CHECK-NEXT:       (local.get $1)
  ;; CHECK-NEXT:       (i32.const 256)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block624
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $4
  ;; CHECK-NEXT:        (i32.load offset=12
  ;; CHECK-NEXT:         (local.get $8)
@@ -27144,7 +27123,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block626
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.lt_u
  ;; CHECK-NEXT:           (local.get $1)
@@ -27170,7 +27149,7 @@
  ;; CHECK-NEXT:         (local.get $4)
  ;; CHECK-NEXT:         (local.get $1)
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block630
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (i32.const 176)
  ;; CHECK-NEXT:          (i32.and
@@ -27200,7 +27179,7 @@
  ;; CHECK-NEXT:          (i32.const 8)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block632
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.lt_u
  ;; CHECK-NEXT:           (local.get $4)
@@ -27238,7 +27217,7 @@
  ;; CHECK-NEXT:        (local.get $1)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block635
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $6
  ;; CHECK-NEXT:        (i32.load offset=24
  ;; CHECK-NEXT:         (local.get $8)
@@ -27254,7 +27233,7 @@
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (local.get $8)
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block637
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (if
  ;; CHECK-NEXT:           (i32.eqz
  ;; CHECK-NEXT:            (local.tee $3
@@ -27282,7 +27261,7 @@
  ;; CHECK-NEXT:            (local.set $0
  ;; CHECK-NEXT:             (local.get $1)
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block640
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $9
  ;; CHECK-NEXT:              (i32.const 0)
  ;; CHECK-NEXT:             )
@@ -27302,7 +27281,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block642
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $3
  ;; CHECK-NEXT:              (local.get $1)
  ;; CHECK-NEXT:             )
@@ -27323,7 +27302,7 @@
  ;; CHECK-NEXT:              )
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block644
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (local.set $3
  ;; CHECK-NEXT:              (local.get $1)
  ;; CHECK-NEXT:             )
@@ -27342,7 +27321,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (call $_abort)
- ;; CHECK-NEXT:           (block $block646
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store
  ;; CHECK-NEXT:             (local.get $0)
  ;; CHECK-NEXT:             (i32.const 0)
@@ -27353,7 +27332,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block647
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (if
  ;; CHECK-NEXT:           (i32.lt_u
  ;; CHECK-NEXT:            (local.tee $4
@@ -27393,7 +27372,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:            (local.get $8)
  ;; CHECK-NEXT:           )
- ;; CHECK-NEXT:           (block $block651
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store
  ;; CHECK-NEXT:             (local.get $1)
  ;; CHECK-NEXT:             (local.get $0)
@@ -27413,7 +27392,7 @@
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (if
  ;; CHECK-NEXT:        (local.get $6)
- ;; CHECK-NEXT:        (block $block653
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (i32.eq
  ;; CHECK-NEXT:           (local.get $8)
@@ -27433,7 +27412,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block655
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (i32.store
  ;; CHECK-NEXT:            (local.get $0)
  ;; CHECK-NEXT:            (local.get $9)
@@ -27442,7 +27421,7 @@
  ;; CHECK-NEXT:            (i32.eqz
  ;; CHECK-NEXT:             (local.get $9)
  ;; CHECK-NEXT:            )
- ;; CHECK-NEXT:            (block $block657
+ ;; CHECK-NEXT:            (block
  ;; CHECK-NEXT:             (i32.store
  ;; CHECK-NEXT:              (i32.const 180)
  ;; CHECK-NEXT:              (i32.and
@@ -27462,7 +27441,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
- ;; CHECK-NEXT:          (block $block658
+ ;; CHECK-NEXT:          (block
  ;; CHECK-NEXT:           (if
  ;; CHECK-NEXT:            (i32.lt_u
  ;; CHECK-NEXT:             (local.get $6)
@@ -27532,7 +27511,7 @@
  ;; CHECK-NEXT:            (local.get $3)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (call $_abort)
- ;; CHECK-NEXT:           (block $block664
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store offset=16
  ;; CHECK-NEXT:             (local.get $9)
  ;; CHECK-NEXT:             (local.get $1)
@@ -27558,7 +27537,7 @@
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (call $_abort)
- ;; CHECK-NEXT:           (block $block667
+ ;; CHECK-NEXT:           (block
  ;; CHECK-NEXT:            (i32.store offset=20
  ;; CHECK-NEXT:             (local.get $9)
  ;; CHECK-NEXT:             (local.get $0)
@@ -27596,7 +27575,7 @@
  ;; CHECK-NEXT:       (i32.const 196)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block669
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (i32.store
  ;; CHECK-NEXT:       (i32.const 184)
  ;; CHECK-NEXT:       (local.get $5)
@@ -27620,7 +27599,7 @@
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:    (i32.const 256)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block671
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (i32.add
  ;; CHECK-NEXT:      (i32.shl
@@ -27661,7 +27640,7 @@
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (call $_abort)
- ;; CHECK-NEXT:      (block $block674
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $15
  ;; CHECK-NEXT:        (local.get $3)
  ;; CHECK-NEXT:       )
@@ -27670,7 +27649,7 @@
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block675
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (i32.store
  ;; CHECK-NEXT:       (i32.const 176)
  ;; CHECK-NEXT:       (i32.or
@@ -27840,7 +27819,7 @@
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block679
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $5
  ;; CHECK-NEXT:      (i32.shl
  ;; CHECK-NEXT:       (local.get $3)
@@ -27905,7 +27884,7 @@
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
- ;; CHECK-NEXT:         (block $block681
+ ;; CHECK-NEXT:         (block
  ;; CHECK-NEXT:          (local.set $5
  ;; CHECK-NEXT:           (local.get $4)
  ;; CHECK-NEXT:          )
@@ -27924,7 +27903,7 @@
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (call $_abort)
- ;; CHECK-NEXT:        (block $block683
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store
  ;; CHECK-NEXT:          (local.get $5)
  ;; CHECK-NEXT:          (local.get $2)
@@ -27970,7 +27949,7 @@
  ;; CHECK-NEXT:         (local.get $3)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
- ;; CHECK-NEXT:       (block $block685
+ ;; CHECK-NEXT:       (block
  ;; CHECK-NEXT:        (i32.store offset=12
  ;; CHECK-NEXT:         (local.get $4)
  ;; CHECK-NEXT:         (local.get $2)
@@ -27996,7 +27975,7 @@
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block686
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (i32.store
  ;; CHECK-NEXT:      (i32.const 180)
  ;; CHECK-NEXT:      (i32.or
@@ -29950,7 +29929,7 @@
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (i32.and
  ;; CHECK-NEXT:      (local.get $1)
@@ -29964,7 +29943,7 @@
  ;; CHECK-NEXT:       (i32.const 3)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (block $block689
+ ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (local.set $3
  ;; CHECK-NEXT:       (i32.sub
  ;; CHECK-NEXT:        (i32.add
@@ -29980,7 +29959,7 @@
  ;; CHECK-NEXT:         (local.get $0)
  ;; CHECK-NEXT:         (local.get $3)
  ;; CHECK-NEXT:        )
- ;; CHECK-NEXT:        (block $block691
+ ;; CHECK-NEXT:        (block
  ;; CHECK-NEXT:         (i32.store8
  ;; CHECK-NEXT:          (local.get $0)
  ;; CHECK-NEXT:          (local.get $1)
@@ -30030,7 +30009,7 @@
  ;; CHECK-NEXT:       (local.get $0)
  ;; CHECK-NEXT:       (local.get $5)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block693
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (i32.store
  ;; CHECK-NEXT:        (local.get $0)
  ;; CHECK-NEXT:        (local.get $3)
@@ -30053,7 +30032,7 @@
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:     (local.get $4)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block695
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (i32.store8
  ;; CHECK-NEXT:      (local.get $0)
  ;; CHECK-NEXT:      (local.get $1)
@@ -30217,7 +30196,7 @@
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:    (i32.const 32)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (global.set $tempRet0
  ;; CHECK-NEXT:     (i32.shr_u
  ;; CHECK-NEXT:      (local.get $1)
@@ -30317,7 +30296,7 @@
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:    (i32.const 32)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (global.set $tempRet0
  ;; CHECK-NEXT:     (i32.or
  ;; CHECK-NEXT:      (i32.shl
@@ -30452,14 +30431,14 @@
  ;; CHECK-NEXT:     (i32.const 3)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (loop $while-in
  ;; CHECK-NEXT:     (if
  ;; CHECK-NEXT:      (i32.and
  ;; CHECK-NEXT:       (local.get $0)
  ;; CHECK-NEXT:       (i32.const 3)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block698
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (if
  ;; CHECK-NEXT:        (i32.eqz
  ;; CHECK-NEXT:         (local.get $2)
@@ -30502,7 +30481,7 @@
  ;; CHECK-NEXT:       (local.get $2)
  ;; CHECK-NEXT:       (i32.const 4)
  ;; CHECK-NEXT:      )
- ;; CHECK-NEXT:      (block $block701
+ ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (i32.store
  ;; CHECK-NEXT:        (local.get $0)
  ;; CHECK-NEXT:        (i32.load
@@ -30539,7 +30518,7 @@
  ;; CHECK-NEXT:     (local.get $2)
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (block $block703
+ ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (i32.store8
  ;; CHECK-NEXT:      (local.get $0)
  ;; CHECK-NEXT:      (i32.load8_s
diff --git a/test/lit/passes/inlining-unreachable.wast b/test/lit/passes/inlining-unreachable.wast
new file mode 100644
index 0000000..4cec297
--- /dev/null
+++ b/test/lit/passes/inlining-unreachable.wast
@@ -0,0 +1,68 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt --inlining -S -o - | filecheck %s
+
+;; Test that we inline functions with unreachable bodies. This is important to
+;; propagate the trap to the caller (where it might lead to DCE).
+
+(module
+  (func $trap
+    (unreachable)
+  )
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (func $call-trap
+  ;; CHECK-NEXT:  (block $__inlined_func$trap
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:   (br $__inlined_func$trap)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-trap
+    ;; In this case the call had type none, but the inlined code is unreachable,
+    ;; so we'll add a br to the new block to keep the type as none (the br is
+    ;; not actually reached, and other opts will remove it).
+    (call $trap)
+  )
+
+  (func $trap-result (result i32)
+    ;; As above, but now there is a declared result.
+    (unreachable)
+  )
+
+  ;; CHECK:      (func $call-trap-result (result i32)
+  ;; CHECK-NEXT:  (block $__inlined_func$trap-result (result i32)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-trap-result (result i32)
+    (call $trap-result)
+  )
+
+  (func $contents-then-trap
+    ;; Add some contents in addition to the trap.
+    (nop)
+    (drop
+      (i32.const 1337)
+    )
+    (nop)
+    (unreachable)
+  )
+  ;; CHECK:      (func $call-contents-then-trap
+  ;; CHECK-NEXT:  (block $__inlined_func$contents-then-trap
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (i32.const 1337)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (br $__inlined_func$contents-then-trap)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $call-contents-then-trap
+    (call $contents-then-trap)
+  )
+)
diff --git a/test/lit/passes/inlining_all-features.wast b/test/lit/passes/inlining_all-features.wast
index 051201b..9b3a426 100644
--- a/test/lit/passes/inlining_all-features.wast
+++ b/test/lit/passes/inlining_all-features.wast
@@ -135,29 +135,19 @@
  (export "func_36_invoker" (func $1))
 
  (func $0
-  (return_call_ref
+  (return_call_ref $none_=>_none
    (ref.null $none_=>_none)
   )
  )
  ;; CHECK:      (func $1
  ;; CHECK-NEXT:  (block $__inlined_func$0
- ;; CHECK-NEXT:   (block
- ;; CHECK-NEXT:    (call_ref
- ;; CHECK-NEXT:     (ref.null $none_=>_none)
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (br $__inlined_func$0)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (br $__inlined_func$0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $1 (type $none_=>_none)
  ;; NOMNL-NEXT:  (block $__inlined_func$0
- ;; NOMNL-NEXT:   (block
- ;; NOMNL-NEXT:    (call_ref
- ;; NOMNL-NEXT:     (ref.null $none_=>_none)
- ;; NOMNL-NEXT:    )
- ;; NOMNL-NEXT:    (br $__inlined_func$0)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (unreachable)
  ;; NOMNL-NEXT:   (br $__inlined_func$0)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
@@ -177,14 +167,12 @@
  ;; CHECK:      (elem declare func $1)
 
  ;; CHECK:      (func $1 (result (ref func))
- ;; CHECK-NEXT:  (local $0 funcref)
+ ;; CHECK-NEXT:  (local $0 (ref func))
  ;; CHECK-NEXT:  (block $__inlined_func$0 (result (ref func))
  ;; CHECK-NEXT:   (local.set $0
  ;; CHECK-NEXT:    (ref.func $1)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.get $0)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (type $none_=>_ref|func| (func_subtype (result (ref func)) func))
@@ -192,14 +180,12 @@
  ;; NOMNL:      (elem declare func $1)
 
  ;; NOMNL:      (func $1 (type $none_=>_ref|func|) (result (ref func))
- ;; NOMNL-NEXT:  (local $0 funcref)
+ ;; NOMNL-NEXT:  (local $0 (ref func))
  ;; NOMNL-NEXT:  (block $__inlined_func$0 (result (ref func))
  ;; NOMNL-NEXT:   (local.set $0
  ;; NOMNL-NEXT:    (ref.func $1)
  ;; NOMNL-NEXT:   )
- ;; NOMNL-NEXT:   (ref.as_non_null
- ;; NOMNL-NEXT:    (local.get $0)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (local.get $0)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $1 (result (ref func))
@@ -208,41 +194,3 @@
   )
  )
 )
-
-;; never inline an rtt parameter, as those cannot be handled as locals
-(module
- ;; CHECK:      (type $struct (struct ))
- ;; NOMNL:      (type $struct (struct_subtype  data))
- (type $struct (struct))
- ;; CHECK:      (type $rtt_$struct_=>_none (func (param (rtt $struct))))
-
- ;; CHECK:      (type $none_=>_none (func))
-
- ;; CHECK:      (func $0 (param $rtt (rtt $struct))
- ;; CHECK-NEXT:  (nop)
- ;; CHECK-NEXT: )
- ;; NOMNL:      (type $rtt_$struct_=>_none (func_subtype (param (rtt $struct)) func))
-
- ;; NOMNL:      (type $none_=>_none (func_subtype func))
-
- ;; NOMNL:      (func $0 (type $rtt_$struct_=>_none) (param $rtt (rtt $struct))
- ;; NOMNL-NEXT:  (nop)
- ;; NOMNL-NEXT: )
- (func $0 (param $rtt (rtt $struct))
- )
- ;; CHECK:      (func $1
- ;; CHECK-NEXT:  (call $0
- ;; CHECK-NEXT:   (rtt.canon $struct)
- ;; CHECK-NEXT:  )
- ;; CHECK-NEXT: )
- ;; NOMNL:      (func $1 (type $none_=>_none)
- ;; NOMNL-NEXT:  (call $0
- ;; NOMNL-NEXT:   (rtt.canon $struct)
- ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT: )
- (func $1
-  (call $0
-   (rtt.canon $struct)
-  )
- )
-)
diff --git a/test/lit/passes/inlining_enable-tail-call.wast b/test/lit/passes/inlining_enable-tail-call.wast
index 0c460b5..3b80f84 100644
--- a/test/lit/passes/inlining_enable-tail-call.wast
+++ b/test/lit/passes/inlining_enable-tail-call.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --inlining --enable-tail-call -S -o - | filecheck %s
 
@@ -289,7 +289,7 @@
  ;; CHECK-NEXT:  (local $7 f64)
  ;; CHECK-NEXT:  (local $8 i32)
  ;; CHECK-NEXT:  (loop $label$0 (result i32)
- ;; CHECK-NEXT:   (block $block
+ ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (i32.eqz
  ;; CHECK-NEXT:      (global.get $hangLimit)
diff --git a/test/lit/passes/inlining_optimize-level=3.wast b/test/lit/passes/inlining_optimize-level=3.wast
index ec39b90..416cf7c 100644
--- a/test/lit/passes/inlining_optimize-level=3.wast
+++ b/test/lit/passes/inlining_optimize-level=3.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --inlining --optimize-level=3 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/inlining_splitting.wast b/test/lit/passes/inlining_splitting.wast
index 599bb9c..c114d45 100644
--- a/test/lit/passes/inlining_splitting.wast
+++ b/test/lit/passes/inlining_splitting.wast
@@ -14,8 +14,6 @@
   ;; CHECK:      (type $struct (struct ))
   (type $struct (struct))
 
-  ;; CHECK:      (type $i32_rtt_$struct_=>_none (func (param i32 (rtt $struct))))
-
   ;; CHECK:      (type $i64_i32_f64_=>_none (func (param i64 i32 f64)))
 
   ;; CHECK:      (import "out" "func" (func $import))
@@ -163,12 +161,10 @@
   ;; CHECK-NEXT:  (block $toplevel
   ;; CHECK-NEXT:   (if
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (block $block
-  ;; CHECK-NEXT:     (if
-  ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:      (br $toplevel)
-  ;; CHECK-NEXT:      (call $import)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    (if
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (br $toplevel)
+  ;; CHECK-NEXT:     (call $import)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -203,19 +199,8 @@
     (call $br-to-toplevel (i32.const 2))
   )
 
-  ;; CHECK:      (func $nondefaultable-param (param $x i32) (param $y (rtt $struct))
-  ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (return)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (loop $l
-  ;; CHECK-NEXT:   (call $import)
-  ;; CHECK-NEXT:   (br $l)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $nondefaultable-param (param $x i32) (param $y (rtt $struct))
-    ;; The RTT param here prevents us from even being inlined, even with
-    ;; splitting.
+  (func $nondefaultable-param (param $x i32) (param $y (ref $struct))
+    ;; We can inline despite the non-initial, non-defaultable param.
     (if
       (local.get $x)
       (return)
@@ -227,13 +212,30 @@
   )
 
   ;; CHECK:      (func $call-nondefaultable-param
-  ;; CHECK-NEXT:  (call $nondefaultable-param
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (rtt.canon $struct)
+  ;; CHECK-NEXT:  (local $0 i32)
+  ;; CHECK-NEXT:  (local $1 (ref $struct))
+  ;; CHECK-NEXT:  (block $__inlined_func$nondefaultable-param
+  ;; CHECK-NEXT:   (local.set $0
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.set $1
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (block
+  ;; CHECK-NEXT:    (if
+  ;; CHECK-NEXT:     (local.get $0)
+  ;; CHECK-NEXT:     (br $__inlined_func$nondefaultable-param)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (loop $l
+  ;; CHECK-NEXT:     (call $import)
+  ;; CHECK-NEXT:     (br $l)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (br $__inlined_func$nondefaultable-param)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $call-nondefaultable-param
-    (call $nondefaultable-param (i32.const 0) (rtt.canon $struct))
+    (call $nondefaultable-param (i32.const 0) (struct.new $struct))
   )
 
   (func $many-params (param $x i64) (param $y i32) (param $z f64)
@@ -430,7 +432,7 @@
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (block $__inlined_func$byn-split-inlineable-A$condition-ref.is
   ;; CHECK-NEXT:    (local.set $0
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (i32.eqz
@@ -447,7 +449,7 @@
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (block $__inlined_func$byn-split-inlineable-A$condition-ref.is0
   ;; CHECK-NEXT:    (local.set $1
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (i32.eqz
@@ -831,7 +833,7 @@
   ;; CHECK-NEXT:   (block (result anyref)
   ;; CHECK-NEXT:    (block $__inlined_func$byn-split-inlineable-B$error-if-null (result anyref)
   ;; CHECK-NEXT:     (local.set $0
-  ;; CHECK-NEXT:      (ref.null any)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block (result anyref)
   ;; CHECK-NEXT:      (if
@@ -853,7 +855,7 @@
   ;; CHECK-NEXT:   (block (result anyref)
   ;; CHECK-NEXT:    (block $__inlined_func$byn-split-inlineable-B$error-if-null0 (result anyref)
   ;; CHECK-NEXT:     (local.set $1
-  ;; CHECK-NEXT:      (ref.null any)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block (result anyref)
   ;; CHECK-NEXT:      (if
@@ -882,7 +884,7 @@
   ;; CHECK-NEXT:   (ref.is_null
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (call $import)
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
@@ -907,12 +909,12 @@
   ;; CHECK:      (func $call-too-many
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $too-many
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $too-many
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -926,7 +928,7 @@
   ;; CHECK-NEXT:   (ref.is_null
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (call $import)
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
@@ -949,12 +951,12 @@
   ;; CHECK:      (func $call-tail-not-simple
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $tail-not-simple
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $tail-not-simple
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -985,7 +987,7 @@
   ;; CHECK-NEXT:   (block (result anyref)
   ;; CHECK-NEXT:    (block $__inlined_func$byn-split-inlineable-B$reachable-if-body (result anyref)
   ;; CHECK-NEXT:     (local.set $0
-  ;; CHECK-NEXT:      (ref.null any)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block (result anyref)
   ;; CHECK-NEXT:      (if
@@ -1008,7 +1010,7 @@
   ;; CHECK-NEXT:   (block (result anyref)
   ;; CHECK-NEXT:    (block $__inlined_func$byn-split-inlineable-B$reachable-if-body0 (result anyref)
   ;; CHECK-NEXT:     (local.set $1
-  ;; CHECK-NEXT:      (ref.null any)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block (result anyref)
   ;; CHECK-NEXT:      (if
@@ -1085,12 +1087,12 @@
   ;; CHECK:      (func $call-reachable-if-body-return
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $reachable-if-body-return
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $reachable-if-body-return
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -1119,7 +1121,7 @@
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (block $__inlined_func$byn-split-inlineable-B$unreachable-if-body-no-result
   ;; CHECK-NEXT:    (local.set $0
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (ref.is_null
@@ -1134,7 +1136,7 @@
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (block $__inlined_func$byn-split-inlineable-B$unreachable-if-body-no-result0
   ;; CHECK-NEXT:    (local.set $1
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (ref.is_null
@@ -1183,7 +1185,7 @@
   ;; CHECK-NEXT:   (block (result anyref)
   ;; CHECK-NEXT:    (block $__inlined_func$byn-split-inlineable-B$multi-if (result anyref)
   ;; CHECK-NEXT:     (local.set $0
-  ;; CHECK-NEXT:      (ref.null any)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block (result anyref)
   ;; CHECK-NEXT:      (if
@@ -1214,7 +1216,7 @@
   ;; CHECK-NEXT:   (block (result anyref)
   ;; CHECK-NEXT:    (block $__inlined_func$byn-split-inlineable-B$multi-if0 (result anyref)
   ;; CHECK-NEXT:     (local.set $1
-  ;; CHECK-NEXT:      (ref.null func)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (block (result anyref)
   ;; CHECK-NEXT:      (if
@@ -1244,7 +1246,7 @@
   ;; CHECK-NEXT: )
   (func $call-multi-if
     (drop (call $multi-if (ref.null any)))
-    (drop (call $multi-if (ref.null func)))
+    (drop (call $multi-if (ref.null data)))
   )
 
   ;; CHECK:      (func $too-many-ifs (param $x anyref) (result anyref)
@@ -1318,18 +1320,18 @@
   ;; CHECK:      (func $call-too-many-ifs
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $too-many-ifs
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $too-many-ifs
-  ;; CHECK-NEXT:    (ref.null func)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $call-too-many-ifs
     (drop (call $too-many-ifs (ref.null any)))
-    (drop (call $too-many-ifs (ref.null func)))
+    (drop (call $too-many-ifs (ref.null data)))
   )
 )
 
@@ -1392,17 +1394,13 @@
 ;; CHECK-NEXT: )
 
 ;; CHECK:      (func $byn-split-outlined-B$error-if-null (param $x anyref) (result anyref)
-;; CHECK-NEXT:  (block $block
-;; CHECK-NEXT:   (call $import)
-;; CHECK-NEXT:   (unreachable)
-;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $import)
+;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 
 ;; CHECK:      (func $byn-split-outlined-B$unreachable-if-body-no-result (param $x anyref)
-;; CHECK-NEXT:  (block $block
-;; CHECK-NEXT:   (call $import)
-;; CHECK-NEXT:   (unreachable)
-;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $import)
+;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 
 ;; CHECK:      (func $byn-split-outlined-B$multi-if_0 (param $x anyref)
diff --git a/test/lit/passes/inlining_vacuum_optimize-instructions.wast b/test/lit/passes/inlining_vacuum_optimize-instructions.wast
new file mode 100644
index 0000000..839a588
--- /dev/null
+++ b/test/lit/passes/inlining_vacuum_optimize-instructions.wast
@@ -0,0 +1,43 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt %s --inlining --vacuum --optimize-instructions -all -S -o - | filecheck %s
+
+;; Check for a specific bug involving inlining first turning a struct.get's
+;; input into a null, then vacuum gets rid of intervening blocks, and then
+;; optimize-instructions runs into the following situation: first it turns the
+;; struct.get of a null into an unreachable, then it makes a note to itself to
+;; refinalize at the end, but before the end it tries to optimize the ref.cast.
+;; The ref.cast's type has not been updated to unreachable yet, but its ref has,
+;; which is temporarily inconsistent. We must be careful to avoid confusion
+;; there.
+(module
+ ;; CHECK:      (type $B (struct ))
+
+ ;; CHECK:      (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+ ;; CHECK:      (type $A (struct (field (ref null $B))))
+ (type $A (struct_subtype (field (ref null $B)) data))
+ (type $B (struct_subtype  data))
+ ;; CHECK:      (func $target (param $0 (ref null $A))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.cast_static $B
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $target (param $0 (ref null $A))
+  (drop
+   (ref.cast_static $B
+    (struct.get $A 0
+     (call $get-null)
+    )
+   )
+  )
+  (unreachable)
+ )
+ (func $get-null (result (ref null $A))
+  (ref.null none)
+ )
+)
+
diff --git a/test/lit/passes/instrument-locals_all-features_disable-typed-function-references.wast b/test/lit/passes/instrument-locals_all-features_disable-gc.wast
similarity index 83%
rename from test/lit/passes/instrument-locals_all-features_disable-typed-function-references.wast
rename to test/lit/passes/instrument-locals_all-features_disable-gc.wast
index e736253..f9cd164 100644
--- a/test/lit/passes/instrument-locals_all-features_disable-typed-function-references.wast
+++ b/test/lit/passes/instrument-locals_all-features_disable-gc.wast
@@ -1,7 +1,7 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
-;; RUN: foreach %s %t wasm-opt --instrument-locals --all-features --disable-typed-function-references -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --instrument-locals --all-features --disable-gc -S -o - | filecheck %s
 
 (module
   ;; CHECK:      (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
@@ -14,13 +14,7 @@
 
   ;; CHECK:      (type $i32_i32_funcref_=>_funcref (func (param i32 i32 funcref) (result funcref)))
 
-  ;; CHECK:      (type $i32_i32_anyref_=>_anyref (func (param i32 i32 anyref) (result anyref)))
-
-  ;; CHECK:      (type $i32_i32_eqref_=>_eqref (func (param i32 i32 eqref) (result eqref)))
-
-  ;; CHECK:      (type $i32_i32_i31ref_=>_i31ref (func (param i32 i32 i31ref) (result i31ref)))
-
-  ;; CHECK:      (type $i32_i32_dataref_=>_dataref (func (param i32 i32 dataref) (result dataref)))
+  ;; CHECK:      (type $i32_i32_externref_=>_externref (func (param i32 i32 externref) (result externref)))
 
   ;; CHECK:      (type $i32_i32_v128_=>_v128 (func (param i32 i32 v128) (result v128)))
 
@@ -46,21 +40,9 @@
 
   ;; CHECK:      (import "env" "set_funcref" (func $set_funcref (param i32 i32 funcref) (result funcref)))
 
-  ;; CHECK:      (import "env" "get_anyref" (func $get_anyref (param i32 i32 anyref) (result anyref)))
-
-  ;; CHECK:      (import "env" "set_anyref" (func $set_anyref (param i32 i32 anyref) (result anyref)))
-
-  ;; CHECK:      (import "env" "get_eqref" (func $get_eqref (param i32 i32 eqref) (result eqref)))
-
-  ;; CHECK:      (import "env" "set_eqref" (func $set_eqref (param i32 i32 eqref) (result eqref)))
-
-  ;; CHECK:      (import "env" "get_i31ref" (func $get_i31ref (param i32 i32 i31ref) (result i31ref)))
-
-  ;; CHECK:      (import "env" "set_i31ref" (func $set_i31ref (param i32 i32 i31ref) (result i31ref)))
-
-  ;; CHECK:      (import "env" "get_dataref" (func $get_dataref (param i32 i32 dataref) (result dataref)))
+  ;; CHECK:      (import "env" "get_externref" (func $get_externref (param i32 i32 externref) (result externref)))
 
-  ;; CHECK:      (import "env" "set_dataref" (func $set_dataref (param i32 i32 dataref) (result dataref)))
+  ;; CHECK:      (import "env" "set_externref" (func $set_externref (param i32 i32 externref) (result externref)))
 
   ;; CHECK:      (import "env" "get_v128" (func $get_v128 (param i32 i32 v128) (result v128)))
 
@@ -74,7 +56,7 @@
   ;; CHECK-NEXT:  (local $z f32)
   ;; CHECK-NEXT:  (local $w f64)
   ;; CHECK-NEXT:  (local $F funcref)
-  ;; CHECK-NEXT:  (local $X anyref)
+  ;; CHECK-NEXT:  (local $X externref)
   ;; CHECK-NEXT:  (local $S v128)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $get_i32
@@ -108,7 +90,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $get_anyref
+  ;; CHECK-NEXT:   (call $get_externref
   ;; CHECK-NEXT:    (i32.const 4)
   ;; CHECK-NEXT:    (i32.const 5)
   ;; CHECK-NEXT:    (local.get $X)
@@ -146,7 +128,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $get_anyref
+  ;; CHECK-NEXT:   (call $get_externref
   ;; CHECK-NEXT:    (i32.const 9)
   ;; CHECK-NEXT:    (i32.const 5)
   ;; CHECK-NEXT:    (local.get $X)
@@ -180,10 +162,10 @@
   ;; CHECK-NEXT:   (ref.func $test)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $X
-  ;; CHECK-NEXT:   (call $set_anyref
+  ;; CHECK-NEXT:   (call $set_externref
   ;; CHECK-NEXT:    (i32.const 14)
   ;; CHECK-NEXT:    (i32.const 5)
-  ;; CHECK-NEXT:    (call $get_anyref
+  ;; CHECK-NEXT:    (call $get_externref
   ;; CHECK-NEXT:     (i32.const 13)
   ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:     (local.get $X)
@@ -226,10 +208,10 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $X
-  ;; CHECK-NEXT:   (call $set_anyref
+  ;; CHECK-NEXT:   (call $set_externref
   ;; CHECK-NEXT:    (i32.const 21)
   ;; CHECK-NEXT:    (i32.const 5)
-  ;; CHECK-NEXT:    (call $get_anyref
+  ;; CHECK-NEXT:    (call $get_externref
   ;; CHECK-NEXT:     (i32.const 20)
   ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:     (local.get $X)
diff --git a/test/lit/passes/instrument-memory-gc.wast b/test/lit/passes/instrument-memory-gc.wast
index 9720961..f412684 100644
--- a/test/lit/passes/instrument-memory-gc.wast
+++ b/test/lit/passes/instrument-memory-gc.wast
@@ -27,22 +27,16 @@
     (field f32)
     (field $named f64)
   ))
+  ;; CHECK:      (type $array (array (mut f64)))
+  ;; NOMNL:      (type $array (array_subtype (mut f64) data))
+  (type $array (array (mut f64)))
+
   ;; CHECK:      (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
 
   ;; CHECK:      (type $ref|$struct|_=>_none (func (param (ref $struct))))
 
   ;; CHECK:      (type $ref|$array|_=>_none (func (param (ref $array))))
 
-  ;; CHECK:      (type $array (array (mut f64)))
-  ;; NOMNL:      (type $i32_i32_i32_i32_=>_i32 (func_subtype (param i32 i32 i32 i32) (result i32) func))
-
-  ;; NOMNL:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
-
-  ;; NOMNL:      (type $ref|$array|_=>_none (func_subtype (param (ref $array)) func))
-
-  ;; NOMNL:      (type $array (array_subtype (mut f64) data))
-  (type $array (array (mut f64)))
-
   ;; CHECK:      (import "env" "load_ptr" (func $load_ptr (param i32 i32 i32 i32) (result i32)))
 
   ;; CHECK:      (import "env" "load_val_i32" (func $load_val_i32 (param i32 i32) (result i32)))
@@ -132,6 +126,12 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
+  ;; NOMNL:      (type $i32_i32_i32_i32_=>_i32 (func_subtype (param i32 i32 i32 i32) (result i32) func))
+
+  ;; NOMNL:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
+  ;; NOMNL:      (type $ref|$array|_=>_none (func_subtype (param (ref $array)) func))
+
   ;; NOMNL:      (import "env" "load_ptr" (func $load_ptr (param i32 i32 i32 i32) (result i32)))
 
   ;; NOMNL:      (import "env" "load_val_i32" (func $load_val_i32 (param i32 i32) (result i32)))
diff --git a/test/lit/passes/instrument-memory.wast b/test/lit/passes/instrument-memory.wast
index a5712e9..0973ab4 100644
--- a/test/lit/passes/instrument-memory.wast
+++ b/test/lit/passes/instrument-memory.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --instrument-memory -S -o - | filecheck %s
 
diff --git a/test/lit/passes/instrument-memory64.wast b/test/lit/passes/instrument-memory64.wast
index a8b20cc..de4df4a 100644
--- a/test/lit/passes/instrument-memory64.wast
+++ b/test/lit/passes/instrument-memory64.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --instrument-memory --enable-memory64 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/intrinsic-lowering.wast b/test/lit/passes/intrinsic-lowering.wast
index 01bafbf..34e994f 100644
--- a/test/lit/passes/intrinsic-lowering.wast
+++ b/test/lit/passes/intrinsic-lowering.wast
@@ -17,9 +17,9 @@
   ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $cwe-n (param funcref)))
   (import "binaryen-intrinsics" "call.without.effects" (func $cwe-n (param funcref)))
 
-  ;; CHECK:      (func $test (result i32)
+  ;; CHECK:      (func $test (param $none (ref null $none))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $test)
+  ;; CHECK-NEXT:   (call $make-i32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (call $dif
@@ -27,19 +27,22 @@
   ;; CHECK-NEXT:    (i32.const 42)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $none)
+  ;; CHECK-NEXT:  (call_ref $none
+  ;; CHECK-NEXT:   (local.get $none)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (i32.const 1)
   ;; CHECK-NEXT: )
-  (func $test (result i32)
+  (func $test (param $none (ref null $none))
     ;; These will be lowered into calls.
-    (drop (call $cwe-v (ref.func $test)))
+    (drop (call $cwe-v (ref.func $make-i32)))
     (drop (call $cwe-dif (f64.const 3.14159) (i32.const 42) (ref.func $dif)))
     ;; The last must be a call_ref, as we don't see a constant ref.func
-    (call $cwe-n
-      (ref.null $none)
-    )
+    (call $cwe-n (local.get $none))
+  )
+
+  ;; CHECK:      (func $make-i32 (result i32)
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: )
+  (func $make-i32 (result i32)
     (i32.const 1)
   )
 
diff --git a/test/lit/passes/jspi-args.wast b/test/lit/passes/jspi-args.wast
new file mode 100644
index 0000000..237b312
--- /dev/null
+++ b/test/lit/passes/jspi-args.wast
@@ -0,0 +1,69 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s --jspi --pass-arg=jspi-imports@js.sleep_async --pass-arg=jspi-exports@update_state_async -all -S -o - | filecheck %s
+
+(module
+  ;; sleep_async should have a wrapper function built.
+  ;; CHECK:      (type $f64_=>_i32 (func (param f64) (result i32)))
+
+  ;; CHECK:      (type $externref_f64_=>_i32 (func (param externref f64) (result i32)))
+
+  ;; CHECK:      (import "js" "sleep_sync" (func $sleep_sync (param f64) (result i32)))
+  (import "js" "sleep_async" (func $sleep_async (param f64) (result i32)))
+  ;; CHECK:      (import "js" "sleep_async" (func $import$sleep_async (param externref f64) (result i32)))
+  (import "js" "sleep_sync" (func $sleep_sync (param f64) (result i32)))
+  ;; CHECK:      (global $suspender (mut externref) (ref.null noextern))
+
+  ;; CHECK:      (export "update_state_async" (func $export$update_state_async))
+  (export "update_state_async" (func $update_state_async))
+  ;; CHECK:      (export "update_state_sync" (func $update_state_sync))
+  (export "update_state_sync" (func $update_state_sync))
+  ;; This function calls an async sleep so a wrapper should be created for it.
+  ;; CHECK:      (func $update_state_async (param $param f64) (result i32)
+  ;; CHECK-NEXT:  (call $sleep_async
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const 1.1)
+  ;; CHECK-NEXT:    (local.get $param)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $update_state_async (param $param f64) (result i32)
+    (call $sleep_async (f64.sub (f64.const 1.1) (local.get $param)))
+  )
+  ;; CHECK:      (func $update_state_sync (param $param f64) (result i32)
+  ;; CHECK-NEXT:  (call $sleep_sync
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const 1.1)
+  ;; CHECK-NEXT:    (local.get $param)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $update_state_sync (param $param f64) (result i32)
+    (call $sleep_sync (f64.sub (f64.const 1.1) (local.get $param)))
+  )
+)
+;; CHECK:      (func $export$update_state_async (param $susp externref) (param $param f64) (result i32)
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $susp)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $update_state_async
+;; CHECK-NEXT:   (local.get $param)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $sleep_async (param $0 f64) (result i32)
+;; CHECK-NEXT:  (local $1 externref)
+;; CHECK-NEXT:  (local $2 i32)
+;; CHECK-NEXT:  (local.set $1
+;; CHECK-NEXT:   (global.get $suspender)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.set $2
+;; CHECK-NEXT:   (call $import$sleep_async
+;; CHECK-NEXT:    (global.get $suspender)
+;; CHECK-NEXT:    (local.get $0)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $2)
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/jspi.wast b/test/lit/passes/jspi.wast
new file mode 100644
index 0000000..5b8669b
--- /dev/null
+++ b/test/lit/passes/jspi.wast
@@ -0,0 +1,166 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt %s --jspi -all -S -o - | filecheck %s
+
+(module
+
+  ;; CHECK:      (type $externref_f64_=>_i32 (func (param externref f64) (result i32)))
+
+  ;; CHECK:      (type $f64_=>_i32 (func (param f64) (result i32)))
+
+  ;; CHECK:      (type $externref_i32_=>_i32 (func (param externref i32) (result i32)))
+
+  ;; CHECK:      (type $f64_=>_none (func (param f64)))
+
+  ;; CHECK:      (type $i32_=>_i32 (func (param i32) (result i32)))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $externref_i32_=>_none (func (param externref i32)))
+
+  ;; CHECK:      (import "js" "compute_delta" (func $import$compute_delta (param externref f64) (result i32)))
+  (import "js" "compute_delta" (func $compute_delta (param f64) (result i32)))
+  ;; CHECK:      (import "js" "import_and_export" (func $import$import_and_export (param externref i32) (result i32)))
+  (import "js" "import_and_export" (func $import_and_export (param i32) (result i32)))
+  ;; CHECK:      (import "js" "import_void_return" (func $import$import_void_return (param externref i32)))
+  (import "js" "import_void_return" (func $import_void_return (param i32)))
+  ;; CHECK:      (global $suspender (mut externref) (ref.null noextern))
+
+  ;; CHECK:      (export "update_state_void" (func $export$update_state_void))
+  (export "update_state_void" (func $update_state_void))
+  ;; CHECK:      (export "update_state" (func $export$update_state))
+  (export "update_state" (func $update_state))
+  ;; Test duplicating an export.
+  ;; CHECK:      (export "update_state_again" (func $export$update_state))
+  (export "update_state_again" (func $update_state))
+  ;; Test that a name collision on the parameters is handled.
+  ;; CHECK:      (export "update_state_param_collision" (func $export$update_state_param_collision))
+  (export "update_state_param_collision" (func $update_state_param_collision))
+  ;; Test function that is imported and exported.
+  ;; CHECK:      (export "import_and_export" (func $export$import_and_export))
+  (export "import_and_export" (func $import_and_export))
+
+
+  ;; CHECK:      (func $update_state (param $param f64) (result i32)
+  ;; CHECK-NEXT:  (call $compute_delta
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const 1.1)
+  ;; CHECK-NEXT:    (local.get $param)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $update_state (param $param f64) (result i32)
+    (call $compute_delta (f64.sub (f64.const 1.1) (local.get $param)))
+  )
+
+  ;; CHECK:      (func $update_state_void (param $0 f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $compute_delta
+  ;; CHECK-NEXT:    (f64.const 1.1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $update_state_void (param f64)
+    ;; This function doesn't return anything, but the JSPI pass should add a
+    ;; fake return value to make v8 happy.
+    (drop (call $compute_delta (f64.const 1.1)))
+  )
+
+  ;; CHECK:      (func $update_state_param_collision (param $susp f64) (result i32)
+  ;; CHECK-NEXT:  (call $update_state_param_collision
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const 1.1)
+  ;; CHECK-NEXT:    (local.get $susp)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $update_state_param_collision (param $susp f64) (result i32)
+    (call $update_state_param_collision (f64.sub (f64.const 1.1) (local.get $susp)))
+  )
+)
+;; CHECK:      (func $export$update_state_void (param $susp externref) (param $0 f64) (result i32)
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $susp)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $update_state_void
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.const 0)
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $export$update_state (param $susp externref) (param $param f64) (result i32)
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $susp)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $update_state
+;; CHECK-NEXT:   (local.get $param)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $export$update_state_param_collision (param $susp_0 externref) (param $susp f64) (result i32)
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $susp_0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $update_state_param_collision
+;; CHECK-NEXT:   (local.get $susp)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $export$import_and_export (param $susp externref) (param $0 i32) (result i32)
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $susp)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $import_and_export
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $compute_delta (param $0 f64) (result i32)
+;; CHECK-NEXT:  (local $1 externref)
+;; CHECK-NEXT:  (local $2 i32)
+;; CHECK-NEXT:  (local.set $1
+;; CHECK-NEXT:   (global.get $suspender)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.set $2
+;; CHECK-NEXT:   (call $import$compute_delta
+;; CHECK-NEXT:    (global.get $suspender)
+;; CHECK-NEXT:    (local.get $0)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $2)
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $import_and_export (param $0 i32) (result i32)
+;; CHECK-NEXT:  (local $1 externref)
+;; CHECK-NEXT:  (local $2 i32)
+;; CHECK-NEXT:  (local.set $1
+;; CHECK-NEXT:   (global.get $suspender)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.set $2
+;; CHECK-NEXT:   (call $import$import_and_export
+;; CHECK-NEXT:    (global.get $suspender)
+;; CHECK-NEXT:    (local.get $0)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $2)
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $import_void_return (param $0 i32)
+;; CHECK-NEXT:  (local $1 externref)
+;; CHECK-NEXT:  (local.set $1
+;; CHECK-NEXT:   (global.get $suspender)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $import$import_void_return
+;; CHECK-NEXT:   (global.get $suspender)
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $suspender
+;; CHECK-NEXT:   (local.get $1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/legalize-js-interface-exported-helpers.wast b/test/lit/passes/legalize-js-interface-exported-helpers.wast
new file mode 100644
index 0000000..253c3a5
--- /dev/null
+++ b/test/lit/passes/legalize-js-interface-exported-helpers.wast
@@ -0,0 +1,73 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_test.py and could be cleaned up.
+
+;; Check that legalize-js-interface-exported-helpers ends up using the
+;; existing __set_temp_ret/__get_temp_ret helpers.
+;; RUN: wasm-opt %s --legalize-js-interface --pass-arg=legalize-js-interface-exported-helpers -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $none_=>_i64 (func (result i64)))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (import "env" "imported" (func $legalimport$imported (result i32)))
+
+  ;; CHECK:      (export "get_i64" (func $legalstub$get_i64))
+  (export "get_i64" (func $get_i64))
+  (import "env" "imported" (func $imported (result i64)))
+  (export "__set_temp_ret" (func $__set_temp_ret))
+  (export "__get_temp_ret" (func $__get_temp_ret))
+  ;; CHECK:      (func $get_i64 (result i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $legalfunc$imported)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i64.const 0)
+  ;; CHECK-NEXT: )
+  (func $get_i64 (result i64)
+    (drop (call $imported))
+    (i64.const 0)
+  )
+  ;; CHECK:      (func $__set_temp_ret (param $0 i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $__set_temp_ret (param i32))
+  ;; CHECK:      (func $__get_temp_ret (result i32)
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $__get_temp_ret (result i32)
+    (i32.const 0)
+  )
+)
+;; CHECK:      (func $legalstub$get_i64 (result i32)
+;; CHECK-NEXT:  (local $0 i64)
+;; CHECK-NEXT:  (local.set $0
+;; CHECK-NEXT:   (call $get_i64)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $__set_temp_ret
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $0)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.wrap_i64
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalfunc$imported (result i64)
+;; CHECK-NEXT:  (i64.or
+;; CHECK-NEXT:   (i64.extend_i32_u
+;; CHECK-NEXT:    (call $legalimport$imported)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i64.shl
+;; CHECK-NEXT:    (i64.extend_i32_u
+;; CHECK-NEXT:     (call $__get_temp_ret)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.const 32)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/legalize-js-interface-minimally.wast b/test/lit/passes/legalize-js-interface-minimally.wast
new file mode 100644
index 0000000..6c2c471
--- /dev/null
+++ b/test/lit/passes/legalize-js-interface-minimally.wast
@@ -0,0 +1,84 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
+
+;; RUN: foreach %s %t wasm-opt --legalize-js-interface-minimally -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $none_=>_i64 (func (result i64)))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $i64_=>_none (func (param i64)))
+
+  ;; CHECK:      (type $i32_i32_=>_none (func (param i32 i32)))
+
+  ;; CHECK:      (import "env" "imported" (func $imported (result i64)))
+  (import "env" "imported" (func $imported (result i64)))
+  ;; CHECK:      (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
+  (import "env" "invoke_vj" (func $invoke_vj (param i64)))
+  ;; CHECK:      (import "env" "invoke_vj" (func $legalimport$invoke_vj (param i32 i32)))
+
+  ;; CHECK:      (export "func" (func $func))
+  (export "func" (func $func))
+  ;; CHECK:      (export "dynCall_foo" (func $legalstub$dyn))
+  (export "dynCall_foo" (func $dyn))
+  ;; CHECK:      (func $func (result i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $imported)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $legalfunc$invoke_vj
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $func (result i64)
+    (drop (call $imported))
+    (call $invoke_vj (i64.const 0))
+    (unreachable)
+  )
+  ;; CHECK:      (func $dyn (result i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $imported)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $dyn (result i64)
+    (drop (call $imported))
+    (unreachable)
+  )
+)
+;; CHECK:      (func $legalstub$dyn (result i32)
+;; CHECK-NEXT:  (local $0 i64)
+;; CHECK-NEXT:  (local.set $0
+;; CHECK-NEXT:   (call $dyn)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $setTempRet0
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $0)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.wrap_i64
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalfunc$invoke_vj (param $0 i64)
+;; CHECK-NEXT:  (call $legalimport$invoke_vj
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (local.get $0)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $0)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+(module)
+
diff --git a/test/lit/passes/legalize-js-interface_all-features.wast b/test/lit/passes/legalize-js-interface_all-features.wast
new file mode 100644
index 0000000..3ffe2ac
--- /dev/null
+++ b/test/lit/passes/legalize-js-interface_all-features.wast
@@ -0,0 +1,195 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
+
+;; RUN: foreach %s %t wasm-opt --legalize-js-interface --all-features -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $none_=>_i64 (func (result i64)))
+
+  ;; CHECK:      (type $i32_i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32 i32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $i32_i64_i64_=>_none (func (param i32 i64 i64)))
+
+  ;; CHECK:      (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
+  (import "env" "imported" (func $imported (result i64)))
+  ;; CHECK:      (import "env" "getTempRet0" (func $getTempRet0 (result i32)))
+  (import "env" "other" (func $other (param i32) (param i64) (param i64)))
+  ;; CHECK:      (import "env" "imported" (func $legalimport$imported (result i32)))
+  (import "env" "ref-func-arg" (func $ref-func-arg (result i64)))
+  ;; CHECK:      (import "env" "other" (func $legalimport$other (param i32 i32 i32 i32 i32)))
+
+  ;; CHECK:      (import "env" "ref-func-arg" (func $legalimport$ref-func-arg (result i32)))
+
+  ;; CHECK:      (elem declare func $legalfunc$ref-func-arg)
+
+  ;; CHECK:      (export "func" (func $legalstub$func))
+  (export "func" (func $func))
+  ;; CHECK:      (export "ref-func-test" (func $ref-func-test))
+  (export "ref-func-test" (func $ref-func-test))
+  ;; CHECK:      (export "imported" (func $legalstub$imported))
+  (export "imported" (func $imported))
+  ;; CHECK:      (export "imported_again" (func $legalstub$imported))
+  (export "imported_again" (func $imported))
+  ;; CHECK:      (export "other" (func $legalstub$other))
+  (export "other" (func $other))
+  ;; CHECK:      (func $func (result i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $legalfunc$imported)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $legalfunc$other
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $func (result i64)
+    (drop (call $imported))
+    (call $other
+      (i32.const 0)
+      (i64.const 0)
+      (i64.const 0)
+    )
+    (unreachable)
+  )
+
+  ;; ref.func must also be updated.
+  ;; CHECK:      (func $ref-func-test
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $legalfunc$ref-func-arg)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.func $legalfunc$ref-func-arg)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref-func-test
+    (drop
+      (call $ref-func-arg)
+    )
+    (drop
+      (ref.func $ref-func-arg)
+    )
+  )
+)
+;; CHECK:      (func $legalstub$func (result i32)
+;; CHECK-NEXT:  (local $0 i64)
+;; CHECK-NEXT:  (local.set $0
+;; CHECK-NEXT:   (call $func)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $setTempRet0
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $0)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.wrap_i64
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalstub$imported (result i32)
+;; CHECK-NEXT:  (local $0 i64)
+;; CHECK-NEXT:  (local.set $0
+;; CHECK-NEXT:   (call $legalfunc$imported)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $setTempRet0
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $0)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.wrap_i64
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalstub$other (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32)
+;; CHECK-NEXT:  (call $legalfunc$other
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:   (i64.or
+;; CHECK-NEXT:    (i64.extend_i32_u
+;; CHECK-NEXT:     (local.get $1)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.shl
+;; CHECK-NEXT:     (i64.extend_i32_u
+;; CHECK-NEXT:      (local.get $2)
+;; CHECK-NEXT:     )
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i64.or
+;; CHECK-NEXT:    (i64.extend_i32_u
+;; CHECK-NEXT:     (local.get $3)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.shl
+;; CHECK-NEXT:     (i64.extend_i32_u
+;; CHECK-NEXT:      (local.get $4)
+;; CHECK-NEXT:     )
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalfunc$imported (result i64)
+;; CHECK-NEXT:  (i64.or
+;; CHECK-NEXT:   (i64.extend_i32_u
+;; CHECK-NEXT:    (call $legalimport$imported)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i64.shl
+;; CHECK-NEXT:    (i64.extend_i32_u
+;; CHECK-NEXT:     (call $getTempRet0)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.const 32)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalfunc$other (param $0 i32) (param $1 i64) (param $2 i64)
+;; CHECK-NEXT:  (call $legalimport$other
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (local.get $1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $1)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (local.get $2)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $2)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $legalfunc$ref-func-arg (result i64)
+;; CHECK-NEXT:  (i64.or
+;; CHECK-NEXT:   (i64.extend_i32_u
+;; CHECK-NEXT:    (call $legalimport$ref-func-arg)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (i64.shl
+;; CHECK-NEXT:    (i64.extend_i32_u
+;; CHECK-NEXT:     (call $getTempRet0)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i64.const 32)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+(module)
diff --git a/test/lit/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.wast b/test/lit/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.wast
new file mode 100644
index 0000000..abf6aa1
--- /dev/null
+++ b/test/lit/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.wast
@@ -0,0 +1,43 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
+
+;; RUN: foreach %s %t wasm-opt --legalize-js-interface --pass-arg=legalize-js-interface-export-originals -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $none_=>_i64 (func (result i64)))
+
+  ;; CHECK:      (type $i32_=>_none (func (param i32)))
+
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
+
+  ;; CHECK:      (export "func" (func $legalstub$func))
+  (export "func" (func $func))
+  ;; CHECK:      (export "orig$func" (func $func))
+
+  ;; CHECK:      (func $func (result i64)
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $func (result i64)
+    (unreachable)
+  )
+)
+
+;; CHECK:      (func $legalstub$func (result i32)
+;; CHECK-NEXT:  (local $0 i64)
+;; CHECK-NEXT:  (local.set $0
+;; CHECK-NEXT:   (call $func)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $setTempRet0
+;; CHECK-NEXT:   (i32.wrap_i64
+;; CHECK-NEXT:    (i64.shr_u
+;; CHECK-NEXT:     (local.get $0)
+;; CHECK-NEXT:     (i64.const 32)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.wrap_i64
+;; CHECK-NEXT:   (local.get $0)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/local-cse.wast b/test/lit/passes/local-cse.wast
index 6d1b8a8..a2e7285 100644
--- a/test/lit/passes/local-cse.wast
+++ b/test/lit/passes/local-cse.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --local-cse -S -o - | filecheck %s
 
diff --git a/test/lit/passes/local-cse_all-features.wast b/test/lit/passes/local-cse_all-features.wast
index 8ff3bc2..22b6b19 100644
--- a/test/lit/passes/local-cse_all-features.wast
+++ b/test/lit/passes/local-cse_all-features.wast
@@ -1,10 +1,11 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --local-cse --all-features -S -o - | filecheck %s
 
 (module
-  ;; CHECK:      (type $i32_=>_i32 (func (param i32) (result i32)))
+  ;; CHECK:      (type $f (func (param i32) (result i32)))
+  (type $f (func (param i32) (result i32)))
 
   ;; CHECK:      (type $none_=>_none (func))
 
@@ -12,13 +13,13 @@
 
   ;; CHECK:      (func $calls (param $x i32) (result i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call_ref
+  ;; CHECK-NEXT:   (call_ref $f
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (ref.func $calls)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call_ref
+  ;; CHECK-NEXT:   (call_ref $f
   ;; CHECK-NEXT:    (i32.const 10)
   ;; CHECK-NEXT:    (ref.func $calls)
   ;; CHECK-NEXT:   )
@@ -28,10 +29,10 @@
   (func $calls (param $x i32) (result i32)
     ;; The side effects of calls prevent optimization.
     (drop
-      (call_ref (i32.const 10) (ref.func $calls))
+      (call_ref $f (i32.const 10) (ref.func $calls))
     )
     (drop
-      (call_ref (i32.const 10) (ref.func $calls))
+      (call_ref $f (i32.const 10) (ref.func $calls))
     )
     (i32.const 20)
   )
@@ -148,22 +149,18 @@
   )
 
   ;; CHECK:      (func $non-nullable-value (param $ref (ref $A))
-  ;; CHECK-NEXT:  (local $1 (ref null $A))
+  ;; CHECK-NEXT:  (local $1 (ref $A))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (local.tee $1
-  ;; CHECK-NEXT:     (select (result (ref $A))
-  ;; CHECK-NEXT:      (local.get $ref)
-  ;; CHECK-NEXT:      (local.get $ref)
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (select (result (ref $A))
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $non-nullable-value (param $ref (ref $A))
@@ -187,29 +184,25 @@
 
   ;; CHECK:      (func $creations
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $A
+  ;; CHECK-NEXT:   (struct.new $A
   ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (rtt.canon $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.new_with_rtt $A
+  ;; CHECK-NEXT:   (struct.new $A
   ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (rtt.canon $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (array.new_with_rtt $B
+  ;; CHECK-NEXT:   (array.new $B
   ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (rtt.canon $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (array.new_with_rtt $B
+  ;; CHECK-NEXT:   (array.new $B
   ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (rtt.canon $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -217,29 +210,25 @@
     ;; Allocating GC data has no side effects, but each allocation is unique
     ;; and so we cannot replace separate allocations with a single one.
     (drop
-      (struct.new_with_rtt $A
+      (struct.new $A
         (i32.const 1)
-        (rtt.canon $A)
       )
     )
     (drop
-      (struct.new_with_rtt $A
+      (struct.new $A
         (i32.const 1)
-        (rtt.canon $A)
       )
     )
     (drop
-      (array.new_with_rtt $B
+      (array.new $B
         (i32.const 1)
         (i32.const 1)
-        (rtt.canon $B)
       )
     )
     (drop
-      (array.new_with_rtt $B
+      (array.new $B
         (i32.const 1)
         (i32.const 1)
-        (rtt.canon $B)
       )
     )
   )
diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast
index 9b68a64..a343005 100644
--- a/test/lit/passes/local-subtyping-nn.wast
+++ b/test/lit/passes/local-subtyping-nn.wast
@@ -5,8 +5,6 @@
 ;; RUN:   | filecheck %s --check-prefix=NOMNL
 
 (module
-  ;; CHECK:      (type $struct (struct ))
-  ;; NOMNL:      (type $struct (struct_subtype  data))
   (type $struct (struct))
 
   ;; CHECK:      (import "out" "i32" (func $i32 (result i32)))
@@ -14,11 +12,11 @@
   (import "out" "i32" (func $i32 (result i32)))
 
   ;; CHECK:      (func $non-nullable
-  ;; CHECK-NEXT:  (local $x (ref $struct))
+  ;; CHECK-NEXT:  (local $x (ref none))
   ;; CHECK-NEXT:  (local $y (ref $none_=>_i32))
   ;; CHECK-NEXT:  (local.set $x
   ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $y
@@ -29,11 +27,11 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $non-nullable (type $none_=>_none)
-  ;; NOMNL-NEXT:  (local $x (ref $struct))
+  ;; NOMNL-NEXT:  (local $x (ref none))
   ;; NOMNL-NEXT:  (local $y (ref $none_=>_i32))
   ;; NOMNL-NEXT:  (local.set $x
   ;; NOMNL-NEXT:   (ref.as_non_null
-  ;; NOMNL-NEXT:    (ref.null $struct)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (local.set $y
@@ -45,7 +43,7 @@
   ;; NOMNL-NEXT: )
   (func $non-nullable
     (local $x (ref null $struct))
-    (local $y anyref)
+    (local $y funcref)
     ;; x is assigned a value that is non-nullable.
     (local.set $x
       (ref.as_non_null (ref.null $struct))
@@ -62,12 +60,12 @@
   )
 
   ;; CHECK:      (func $uses-default (param $i i32)
-  ;; CHECK-NEXT:  (local $x (ref null $struct))
+  ;; CHECK-NEXT:  (local $x nullref)
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (local.get $i)
   ;; CHECK-NEXT:   (local.set $x
   ;; CHECK-NEXT:    (ref.as_non_null
-  ;; CHECK-NEXT:     (ref.null $struct)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -76,12 +74,12 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $uses-default (type $i32_=>_none) (param $i i32)
-  ;; NOMNL-NEXT:  (local $x (ref null $struct))
+  ;; NOMNL-NEXT:  (local $x nullref)
   ;; NOMNL-NEXT:  (if
   ;; NOMNL-NEXT:   (local.get $i)
   ;; NOMNL-NEXT:   (local.set $x
   ;; NOMNL-NEXT:    (ref.as_non_null
-  ;; NOMNL-NEXT:     (ref.null $struct)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast
index 99a59d0..e281d5c 100644
--- a/test/lit/passes/local-subtyping.wast
+++ b/test/lit/passes/local-subtyping.wast
@@ -1,14 +1,20 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --local-subtyping -all -S -o - \
+;; RUN: wasm-opt %s --remove-unused-names --local-subtyping -all -S -o - \
 ;; RUN:   | filecheck %s
 
+;; --remove-unused-names is run to avoid adding names to blocks. Block names
+;; can prevent non-nullable local validation (we emit named blocks in the binary
+;; format, if we need them, but never emit unnamed ones), which affects some
+;; testcases.
+
 (module
   ;; CHECK:      (type ${} (struct ))
   (type ${} (struct_subtype data))
 
-  ;; CHECK:      (type ${i32} (struct (field i32)))
   (type ${i32} (struct_subtype (field i32) data))
 
+  (type $array (array_subtype i8 data))
+
   ;; CHECK:      (import "out" "i32" (func $i32 (result i32)))
   (import "out" "i32" (func $i32 (result i32)))
   ;; CHECK:      (import "out" "i64" (func $i64 (result i64)))
@@ -18,18 +24,26 @@
   ;; not the optimal LUB.
   ;; CHECK:      (func $refinalize (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (if (result (ref func))
+  ;; CHECK-NEXT:   (if (result (ref i31))
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (ref.func $i32)
-  ;; CHECK-NEXT:    (ref.func $i64)
+  ;; CHECK-NEXT:    (i31.new
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i31.new
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block $block (result (ref func))
+  ;; CHECK-NEXT:   (block $block (result (ref i31))
   ;; CHECK-NEXT:    (br $block
-  ;; CHECK-NEXT:     (ref.func $i32)
+  ;; CHECK-NEXT:     (i31.new
+  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i31.new
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.func $i64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -37,16 +51,16 @@
     (drop
       (if (result anyref)
         (local.get $x)
-        (ref.func $i32)
-        (ref.func $i64)
+        (i31.new (i32.const 0))
+        (i31.new (i32.const 1))
       )
     )
     (drop
       (block $block (result anyref)
         (br $block
-          (ref.func $i32)
+          (i31.new (i32.const 0))
         )
-        (ref.func $i64)
+        (i31.new (i32.const 1))
       )
     )
   )
@@ -54,9 +68,9 @@
   ;; A simple case where a local has a single assignment that we can use as a
   ;; more specific type. A similar thing with a parameter, however, is not a
   ;; thing we can optimize. Also, ignore a local with zero assignments.
-  ;; CHECK:      (func $simple-local-but-not-param (param $x anyref)
-  ;; CHECK-NEXT:  (local $y (ref null $none_=>_i32))
-  ;; CHECK-NEXT:  (local $unused anyref)
+  ;; CHECK:      (func $simple-local-but-not-param (param $x funcref)
+  ;; CHECK-NEXT:  (local $y (ref $none_=>_i32))
+  ;; CHECK-NEXT:  (local $unused funcref)
   ;; CHECK-NEXT:  (local.set $x
   ;; CHECK-NEXT:   (ref.func $i32)
   ;; CHECK-NEXT:  )
@@ -64,9 +78,9 @@
   ;; CHECK-NEXT:   (ref.func $i32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $simple-local-but-not-param (param $x anyref)
-    (local $y anyref)
-    (local $unused anyref)
+  (func $simple-local-but-not-param (param $x funcref)
+    (local $y funcref)
+    (local $unused funcref)
     (local.set $x
       (ref.func $i32)
     )
@@ -75,28 +89,34 @@
     )
   )
 
-  ;; CHECK:      (func $locals-with-multiple-assignments
-  ;; CHECK-NEXT:  (local $x funcref)
-  ;; CHECK-NEXT:  (local $y (ref null $none_=>_i32))
-  ;; CHECK-NEXT:  (local $z (ref null $none_=>_i64))
-  ;; CHECK-NEXT:  (local $w funcref)
+  ;; CHECK:      (func $locals-with-multiple-assignments (param $data dataref)
+  ;; CHECK-NEXT:  (local $x eqref)
+  ;; CHECK-NEXT:  (local $y (ref i31))
+  ;; CHECK-NEXT:  (local $z dataref)
+  ;; CHECK-NEXT:  (local $w (ref func))
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.func $i32)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.func $i64)
+  ;; CHECK-NEXT:   (local.get $data)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $y
-  ;; CHECK-NEXT:   (ref.func $i32)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $y
-  ;; CHECK-NEXT:   (ref.func $i32)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $z
-  ;; CHECK-NEXT:   (ref.func $i64)
+  ;; CHECK-NEXT:   (local.get $data)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $z
-  ;; CHECK-NEXT:   (ref.func $i64)
+  ;; CHECK-NEXT:   (local.get $data)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $w
   ;; CHECK-NEXT:   (ref.func $i32)
@@ -105,33 +125,34 @@
   ;; CHECK-NEXT:   (ref.func $i64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $locals-with-multiple-assignments
+  (func $locals-with-multiple-assignments (param $data (ref null data))
     (local $x anyref)
     (local $y anyref)
     (local $z anyref)
     (local $w funcref)
     ;; x is assigned two different types with a new LUB possible
     (local.set $x
-      (ref.func $i32)
+      (i31.new (i32.const 0))
     )
     (local.set $x
-      (ref.func $i64)
+      (local.get $data)
     )
     ;; y and z are assigned the same more specific type twice
     (local.set $y
-      (ref.func $i32)
+      (i31.new (i32.const 0))
     )
     (local.set $y
-      (ref.func $i32)
+      (i31.new (i32.const 1))
     )
     (local.set $z
-      (ref.func $i64)
+      (local.get $data)
     )
     (local.set $z
-      (ref.func $i64)
+      (local.get $data)
     )
-    ;; w is assigned two different types *without* a new LUB possible, as it
-    ;; already had the optimal LUB
+    ;; w is assigned two different types *without* a new LUB heap type possible,
+    ;; as it already had the optimal LUB heap type (but it can become non-
+    ;; nullable).
     (local.set $w
       (ref.func $i32)
     )
@@ -143,9 +164,9 @@
   ;; In some cases multiple iterations are necessary, as one inferred new type
   ;; applies to a get which then allows another inference.
   ;; CHECK:      (func $multiple-iterations
-  ;; CHECK-NEXT:  (local $x (ref null $none_=>_i32))
-  ;; CHECK-NEXT:  (local $y (ref null $none_=>_i32))
-  ;; CHECK-NEXT:  (local $z (ref null $none_=>_i32))
+  ;; CHECK-NEXT:  (local $x (ref $none_=>_i32))
+  ;; CHECK-NEXT:  (local $y (ref $none_=>_i32))
+  ;; CHECK-NEXT:  (local $z (ref $none_=>_i32))
   ;; CHECK-NEXT:  (local.set $x
   ;; CHECK-NEXT:   (ref.func $i32)
   ;; CHECK-NEXT:  )
@@ -157,9 +178,9 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $multiple-iterations
-    (local $x anyref)
-    (local $y anyref)
-    (local $z anyref)
+    (local $x funcref)
+    (local $y funcref)
+    (local $z funcref)
     (local.set $x
       (ref.func $i32)
     )
@@ -173,9 +194,9 @@
 
   ;; Sometimes a refinalize is necessary in between the iterations.
   ;; CHECK:      (func $multiple-iterations-refinalize (param $i i32)
-  ;; CHECK-NEXT:  (local $x (ref null $none_=>_i32))
-  ;; CHECK-NEXT:  (local $y (ref null $none_=>_i64))
-  ;; CHECK-NEXT:  (local $z funcref)
+  ;; CHECK-NEXT:  (local $x (ref $none_=>_i32))
+  ;; CHECK-NEXT:  (local $y (ref $none_=>_i64))
+  ;; CHECK-NEXT:  (local $z (ref func))
   ;; CHECK-NEXT:  (local.set $x
   ;; CHECK-NEXT:   (ref.func $i32)
   ;; CHECK-NEXT:  )
@@ -183,7 +204,7 @@
   ;; CHECK-NEXT:   (ref.func $i64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $z
-  ;; CHECK-NEXT:   (select (result funcref)
+  ;; CHECK-NEXT:   (select (result (ref func))
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:    (local.get $i)
@@ -191,9 +212,9 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $multiple-iterations-refinalize (param $i i32)
-    (local $x anyref)
-    (local $y anyref)
-    (local $z anyref)
+    (local $x funcref)
+    (local $y funcref)
+    (local $z funcref)
     (local.set $x
       (ref.func $i32)
     )
@@ -210,7 +231,7 @@
   )
 
   ;; CHECK:      (func $nondefaultable
-  ;; CHECK-NEXT:  (local $x (anyref anyref))
+  ;; CHECK-NEXT:  (local $x (funcref funcref))
   ;; CHECK-NEXT:  (local.set $x
   ;; CHECK-NEXT:   (tuple.make
   ;; CHECK-NEXT:    (ref.func $i32)
@@ -219,7 +240,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $nondefaultable
-    (local $x (anyref anyref))
+    (local $x (funcref funcref))
     ;; This tuple is assigned non-nullable values, which means the subtype is
     ;; nondefaultable, and we must not apply it.
     (local.set $x
@@ -243,10 +264,10 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $uses-default (param $i i32)
-    (local $x anyref)
+    (local $x funcref)
     (if
       (local.get $i)
-      ;; The only set to this local uses a more specific type than anyref.
+      ;; The only set to this local uses a more specific type than funcref.
       (local.set $x (ref.func $uses-default))
     )
     (drop
@@ -257,13 +278,13 @@
   )
 
   ;; CHECK:      (func $unreachables (result funcref)
-  ;; CHECK-NEXT:  (local $temp (ref null $none_=>_funcref))
+  ;; CHECK-NEXT:  (local $temp (ref $none_=>_funcref))
   ;; CHECK-NEXT:  (local.set $temp
   ;; CHECK-NEXT:   (ref.func $unreachables)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block $block (result (ref null $none_=>_funcref))
+  ;; CHECK-NEXT:   (block (result (ref $none_=>_funcref))
   ;; CHECK-NEXT:    (local.tee $temp
   ;; CHECK-NEXT:     (ref.func $unreachables)
   ;; CHECK-NEXT:    )
@@ -292,7 +313,7 @@
   )
 
   ;; CHECK:      (func $incompatible-sets (result i32)
-  ;; CHECK-NEXT:  (local $temp (ref null $none_=>_i32))
+  ;; CHECK-NEXT:  (local $temp (ref $none_=>_i32))
   ;; CHECK-NEXT:  (local.set $temp
   ;; CHECK-NEXT:   (ref.func $incompatible-sets)
   ;; CHECK-NEXT:  )
@@ -301,7 +322,7 @@
   ;; CHECK-NEXT:   (local.tee $temp
   ;; CHECK-NEXT:    (block
   ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (ref.null func)
+  ;; CHECK-NEXT:      (ref.null nofunc)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
@@ -310,7 +331,7 @@
   ;; CHECK-NEXT:  (local.tee $temp
   ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null func)
+  ;; CHECK-NEXT:     (ref.null nofunc)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
@@ -343,25 +364,25 @@
   ;; CHECK:      (func $update-nulls
   ;; CHECK-NEXT:  (local $x (ref null ${}))
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.null ${})
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.null ${})
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
   ;; CHECK-NEXT:   (struct.new_default ${})
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.null ${})
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.null ${})
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.null ${})
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (ref.null ${i32})
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $update-nulls
@@ -373,13 +394,136 @@
     (local.set $x (struct.new ${}))
     (local.set $x (ref.null data))
     ;; Note that this func null is even of a type that is incompatible with the
-    ;; new lub (func vs data). Still, we can just update it along with the
+    ;; new lub (array vs struct). Still, we can just update it along with the
     ;; others.
-    (local.set $x (ref.null func))
+    (local.set $x (ref.null $array))
     ;; This null is equal to the LUB we'll find, and will not change.
     (local.set $x (ref.null ${}))
     ;; This null is more specific than the LUB we'll find, and will not change,
     ;; as there is no point to making something less specific in type.
     (local.set $x (ref.null ${i32}))
   )
+
+  ;; CHECK:      (func $become-non-nullable
+  ;; CHECK-NEXT:  (local $x (ref $none_=>_none))
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (ref.func $become-non-nullable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $become-non-nullable
+    (local $x (ref null func))
+    (local.set $x
+      (ref.func $become-non-nullable)
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $already-non-nullable
+  ;; CHECK-NEXT:  (local $x (ref $none_=>_none))
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (ref.func $already-non-nullable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $already-non-nullable
+    (local $x (ref func))
+    (local.set $x
+      (ref.func $already-non-nullable)
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $cannot-become-non-nullable
+  ;; CHECK-NEXT:  (local $x (ref null $none_=>_none))
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $become-non-nullable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $cannot-become-non-nullable
+    (local $x (ref null func))
+    ;; The set is in a nested scope, so we should not make the local non-
+    ;; nullable, as it would not validate. (We can refine the heap type,
+    ;; though.)
+    (if
+      (i32.const 1)
+      (local.set $x
+        (ref.func $become-non-nullable)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $cannot-become-non-nullable-block
+  ;; CHECK-NEXT:  (local $x (ref null $none_=>_none))
+  ;; CHECK-NEXT:  (block $name
+  ;; CHECK-NEXT:   (br_if $name
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $become-non-nullable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $cannot-become-non-nullable-block
+    (local $x (ref null func))
+    ;; A named block prevents us from optimizing here, the same as above.
+    (block $name
+      ;; Add a br_if to avoid the name being removed.
+      (br_if $name
+        (i32.const 1)
+      )
+      (local.set $x
+        (ref.func $become-non-nullable)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $become-non-nullable-block-unnamed
+  ;; CHECK-NEXT:  (local $x (ref $none_=>_none))
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $become-non-nullable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $become-non-nullable-block-unnamed
+    (local $x (ref null func))
+    ;; An named block does *not* prevent us from optimizing here. Unlike above,
+    ;; an unnamed block is never emitted in the binary format, so it does not
+    ;; prevent validation.
+    (block
+      (local.set $x
+        (ref.func $become-non-nullable)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
 )
diff --git a/test/lit/passes/memory-packing-gc.wast b/test/lit/passes/memory-packing-gc.wast
new file mode 100644
index 0000000..d16fe3a
--- /dev/null
+++ b/test/lit/passes/memory-packing-gc.wast
@@ -0,0 +1,173 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Test that memory-packing does the right thing in the presence of array.new_data.
+
+;; RUN: foreach %s %t wasm-opt --memory-packing --all-features -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $array (array i8))
+  (type $array (array i8))
+
+  ;; CHECK:      (type $none_=>_ref|$array| (func (result (ref $array))))
+
+  ;; CHECK:      (data "hello")
+  (data "hello")
+
+  ;; CHECK:      (func $array-new-data (result (ref $array))
+  ;; CHECK-NEXT:  (array.new_data $array 0
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 5)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $array-new-data (result (ref $array))
+    ;; The segment is referenced by array.new_data, so it should not be optimized out.
+    (array.new_data $array 0
+      (i32.const 0)
+      (i32.const 5)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $array (array i8))
+  (type $array (array i8))
+
+  ;; CHECK:      (type $none_=>_ref|$array| (func (result (ref $array))))
+
+  ;; CHECK:      (data "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00hello\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
+  (data "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00hello\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
+
+  ;; CHECK:      (func $no-drop-ends (result (ref $array))
+  ;; CHECK-NEXT:  (array.new_data $array 0
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 5)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-drop-ends (result (ref $array))
+    ;; The referenced segment should not have its zeros trimmed.
+    (array.new_data $array 0
+      (i32.const 0)
+      (i32.const 5)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $array (array i8))
+  (type $array (array i8))
+
+  ;; CHECK:      (type $none_=>_ref|$array| (func (result (ref $array))))
+
+  ;; CHECK:      (memory $mem 1 1)
+  (memory $mem 1 1)
+
+  ;; CHECK:      (data "optimize\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+  (data "optimize\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+
+  ;; CHECK:      (func $no-split (result (ref $array))
+  ;; CHECK-NEXT:  (array.new_data $array 0
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 8)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-split (result (ref $array))
+    ;; The referenced segment should not be split.
+    (array.new_data $array 0
+      (i32.const 0)
+      (i32.const 8)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $array (array i8))
+  (type $array (array i8))
+
+  ;; CHECK:      (type $none_=>_ref|$array| (func (result (ref $array))))
+
+  ;; CHECK:      (memory $mem 1 1)
+  (memory $mem 1 1)
+
+  (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00optimize\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
+
+  ;; CHECK:      (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00optimize\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
+
+  ;; CHECK:      (func $no-split-active (result (ref $array))
+  ;; CHECK-NEXT:  (array.new_data $array 0
+  ;; CHECK-NEXT:   (i32.const 16)
+  ;; CHECK-NEXT:   (i32.const 8)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-split-active (result (ref $array))
+    ;; The segment should still not be optimized, even though it is an active segment.
+    ;; TODO: We could optimize this better by realizing the array.new_data will trap.
+    (array.new_data $array 0
+      (i32.const 16)
+      (i32.const 8)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $array (array i8))
+  (type $array (array i8))
+
+  (data (i32.const 0) "optimize\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+
+  ;; CHECK:      (type $none_=>_ref|$array| (func (result (ref $array))))
+
+  ;; CHECK:      (memory $mem 1 1)
+
+  ;; CHECK:      (data (i32.const 0) "optimize")
+
+  ;; CHECK:      (data (i32.const 24) "me")
+
+  ;; CHECK:      (data "but not\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+  (data "but not\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+
+  (memory $mem 1 1)
+
+  ;; CHECK:      (func $renumber-segment (result (ref $array))
+  ;; CHECK-NEXT:  (array.new_data $array 2
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 7)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $renumber-segment (result (ref $array))
+    ;; Segment 0 is optimized out, so the segment referenced here should be updated.
+    (array.new_data $array 1
+      (i32.const 0)
+      (i32.const 7)
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $array (array i8))
+  (type $array (array i8))
+
+  (data "dead\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00segment")
+
+  ;; CHECK:      (type $none_=>_ref|$array| (func (result (ref $array))))
+
+  ;; CHECK:      (memory $mem 1 1)
+
+  ;; CHECK:      (data "but not\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+  (data "but not\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00me")
+
+  (memory $mem 1 1)
+
+  ;; CHECK:      (func $renumber-segment (result (ref $array))
+  ;; CHECK-NEXT:  (array.new_data $array 0
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 7)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $renumber-segment (result (ref $array))
+    ;; Segment 0 is split in two, so the segment referenced here should be updated.
+    (array.new_data $array 1
+      (i32.const 0)
+      (i32.const 7)
+    )
+  )
+)
diff --git a/test/lit/passes/memory-packing_all-features.wast b/test/lit/passes/memory-packing_all-features.wast
index 6bee38c..62a12fd 100644
--- a/test/lit/passes/memory-packing_all-features.wast
+++ b/test/lit/passes/memory-packing_all-features.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --memory-packing --all-features -S -o - | filecheck %s
 
diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast
index d6ff8b3..58ba6f1 100644
--- a/test/lit/passes/merge-blocks.wast
+++ b/test/lit/passes/merge-blocks.wast
@@ -8,22 +8,23 @@
 (module
  (type $anyref_=>_none (func (param anyref)))
 
+ ;; CHECK:      (type $array (array (mut i32)))
+
  ;; CHECK:      (type $struct (struct (field (mut i32))))
  (type $struct (struct (field (mut i32))))
 
- ;; CHECK:      (type $array (array (mut i32)))
  (type $array (array (mut i32)))
 
  ;; CHECK:      (func $br_on_to_drop
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (block $label$1 (result (ref null i31))
+ ;; CHECK-NEXT:   (block $label$1 (result i31ref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (br_on_i31 $label$1
- ;; CHECK-NEXT:      (ref.null any)
+ ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (ref.null i31)
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
@@ -42,24 +43,24 @@
   )
  )
 
- ;; CHECK:      (func $struct.set
+ ;; CHECK:      (func $struct.set (param $struct (ref null $struct))
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1234)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
- ;; CHECK-NEXT:   (ref.null $struct)
+ ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (i32.const 5)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
- (func $struct.set
+ (func $struct.set (param $struct (ref null $struct))
   (block
    (nop)
    (struct.set $struct 0
     (block (result (ref null $struct))
      (drop (i32.const 1234))
-     (ref.null $struct)
+     (local.get $struct)
     )
     (i32.const 5)
    )
@@ -67,26 +68,26 @@
   )
  )
 
- ;; CHECK:      (func $struct.get
+ ;; CHECK:      (func $struct.get (param $struct (ref null $struct))
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1234)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 0
- ;; CHECK-NEXT:    (ref.null $struct)
+ ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
- (func $struct.get
+ (func $struct.get (param $struct (ref null $struct))
   (block
    (nop)
    (drop
     (struct.get $struct 0
      (block (result (ref null $struct))
       (drop (i32.const 1234))
-      (ref.null $struct)
+      (local.get $struct)
      )
     )
    )
diff --git a/test/lit/passes/merge-similar-functions.wast b/test/lit/passes/merge-similar-functions.wast
index a51400b..885fb62 100644
--- a/test/lit/passes/merge-similar-functions.wast
+++ b/test/lit/passes/merge-similar-functions.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; RUN: foreach %s %t wasm-opt --enable-reference-types --enable-typed-function-references --merge-similar-functions -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --enable-reference-types --enable-gc --merge-similar-functions -S -o - | filecheck %s
 
 (module
   ;; CHECK:      (type $none_=>_i32 (func (result i32)))
@@ -244,7 +244,7 @@
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT:  (call $callee-take-arg-0
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
@@ -283,7 +283,7 @@
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT:  (call $callee-take-arg-1
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
@@ -328,7 +328,7 @@
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
-;; CHECK-NEXT:  (call_ref
+;; CHECK-NEXT:  (call_ref $none_=>_i32
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
@@ -352,7 +352,7 @@
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
-;; CHECK-NEXT:  (call_ref
+;; CHECK-NEXT:  (call_ref $i32_=>_i32
 ;; CHECK-NEXT:   (local.get $1)
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
diff --git a/test/lit/passes/merge-similar-functions_all-features.wast b/test/lit/passes/merge-similar-functions_all-features.wast
index deda3b3..bcd9c70 100644
--- a/test/lit/passes/merge-similar-functions_all-features.wast
+++ b/test/lit/passes/merge-similar-functions_all-features.wast
@@ -5,7 +5,7 @@
   ;; CHECK:      (type $[i8] (array i8))
   (type $[i8] (array i8))
 
-  ;; CHECK:      (func $take-ref-null-data (param $0 (ref null data))
+  ;; CHECK:      (func $take-ref-null-data (param $0 dataref)
   ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
   (func $take-ref-null-data (param (ref null data))
diff --git a/test/lit/passes/merge-similar-functions_types.wast b/test/lit/passes/merge-similar-functions_types.wast
index 497c734..5cf5e47 100644
--- a/test/lit/passes/merge-similar-functions_types.wast
+++ b/test/lit/passes/merge-similar-functions_types.wast
@@ -151,7 +151,7 @@
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
-;; CHECK-NEXT:  (call_ref
+;; CHECK-NEXT:  (call_ref $type$0
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (nop)
@@ -287,7 +287,7 @@
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (nop)
-;; CHECK-NEXT:  (call_ref
+;; CHECK-NEXT:  (call_ref $type$0
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (nop)
@@ -309,7 +309,7 @@
 ;; NOMNL-NEXT:  (nop)
 ;; NOMNL-NEXT:  (nop)
 ;; NOMNL-NEXT:  (nop)
-;; NOMNL-NEXT:  (call_ref
+;; NOMNL-NEXT:  (call_ref $type$1
 ;; NOMNL-NEXT:   (local.get $0)
 ;; NOMNL-NEXT:  )
 ;; NOMNL-NEXT:  (nop)
diff --git a/test/lit/passes/monomorphize.wast b/test/lit/passes/monomorphize.wast
new file mode 100644
index 0000000..1cd219d
--- /dev/null
+++ b/test/lit/passes/monomorphize.wast
@@ -0,0 +1,590 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Test in both "always" mode, which always monomorphizes, and in "careful"
+;; mode which does it only when it appears to actually help.
+
+;; RUN: foreach %s %t wasm-opt --nominal --monomorphize-always -all -S -o - | filecheck %s --check-prefix ALWAYS
+;; RUN: foreach %s %t wasm-opt --nominal --monomorphize        -all -S -o - | filecheck %s --check-prefix CAREFUL
+
+(module
+  ;; ALWAYS:      (type $A (struct_subtype  data))
+  ;; CAREFUL:      (type $A (struct_subtype  data))
+  (type $A (struct_subtype data))
+  ;; ALWAYS:      (type $B (struct_subtype  $A))
+  ;; CAREFUL:      (type $B (struct_subtype  $A))
+  (type $B (struct_subtype $A))
+
+  ;; ALWAYS:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; ALWAYS:      (type $none_=>_none (func_subtype func))
+
+  ;; ALWAYS:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
+
+  ;; ALWAYS:      (import "a" "b" (func $import (param (ref $A))))
+  ;; CAREFUL:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; CAREFUL:      (type $none_=>_none (func_subtype func))
+
+  ;; CAREFUL:      (import "a" "b" (func $import (param (ref $A))))
+  (import "a" "b" (func $import (param (ref $A))))
+
+  ;; ALWAYS:      (func $calls (type $none_=>_none)
+  ;; ALWAYS-NEXT:  (call $refinable
+  ;; ALWAYS-NEXT:   (struct.new_default $A)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable
+  ;; ALWAYS-NEXT:   (struct.new_default $A)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $calls (type $none_=>_none)
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $A)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $A)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $calls
+    ;; Two calls with $A, two with $B. The calls to $B should both go to the
+    ;; same new function which has a refined parameter of $B.
+    ;;
+    ;; However, in CAREFUL mode we won't do that, as there is no helpful
+    ;; improvement in the target functions even with the refined types.
+    (call $refinable
+      (struct.new $A)
+    )
+    (call $refinable
+      (struct.new $A)
+    )
+    (call $refinable
+      (struct.new $B)
+    )
+    (call $refinable
+      (struct.new $B)
+    )
+  )
+
+  ;; ALWAYS:      (func $call-import (type $none_=>_none)
+  ;; ALWAYS-NEXT:  (call $import
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $call-import (type $none_=>_none)
+  ;; CAREFUL-NEXT:  (call $import
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $call-import
+    ;; Calls to imports are left as they are.
+    (call $import
+      (struct.new $B)
+    )
+  )
+
+  ;; ALWAYS:      (func $refinable (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; ALWAYS-NEXT:  (drop
+  ;; ALWAYS-NEXT:   (local.get $ref)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $refinable (type $ref|$A|_=>_none) (param $0 (ref $A))
+  ;; CAREFUL-NEXT:  (nop)
+  ;; CAREFUL-NEXT: )
+  (func $refinable (param $ref (ref $A))
+    ;; Helper function for the above. Use the parameter to see we update types
+    ;; etc when we make a refined version of the function (if we didn't,
+    ;; validation would error).
+    ;;
+    ;; In CAREFUL mode we optimize to check if refined types help, which has the
+    ;; side effect of optimizing the body of this function into a nop.
+    (drop
+      (local.get $ref)
+    )
+  )
+)
+
+
+;; ALWAYS:      (func $refinable_0 (type $ref|$B|_=>_none) (param $ref (ref $B))
+;; ALWAYS-NEXT:  (drop
+;; ALWAYS-NEXT:   (local.get $ref)
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT: )
+(module
+  ;; As above, but now the refinable function uses the local in a way that
+  ;; requires a fixup.
+
+  ;; ALWAYS:      (type $A (struct_subtype  data))
+  ;; CAREFUL:      (type $none_=>_none (func_subtype func))
+
+  ;; CAREFUL:      (type $A (struct_subtype  data))
+  (type $A (struct_subtype data))
+  ;; ALWAYS:      (type $B (struct_subtype  $A))
+  ;; CAREFUL:      (type $B (struct_subtype  $A))
+  (type $B (struct_subtype $A))
+
+
+
+  ;; ALWAYS:      (type $none_=>_none (func_subtype func))
+
+  ;; ALWAYS:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; ALWAYS:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
+
+  ;; ALWAYS:      (func $calls (type $none_=>_none)
+  ;; ALWAYS-NEXT:  (call $refinable_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; CAREFUL:      (func $calls (type $none_=>_none)
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $calls
+    (call $refinable
+      (struct.new $B)
+    )
+  )
+
+  ;; ALWAYS:      (func $refinable (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; ALWAYS-NEXT:  (local $unref (ref $A))
+  ;; ALWAYS-NEXT:  (local.set $unref
+  ;; ALWAYS-NEXT:   (local.get $ref)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (local.set $ref
+  ;; ALWAYS-NEXT:   (local.get $unref)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $refinable (type $ref|$A|_=>_none) (param $0 (ref $A))
+  ;; CAREFUL-NEXT:  (nop)
+  ;; CAREFUL-NEXT: )
+  (func $refinable (param $ref (ref $A))
+    (local $unref (ref $A))
+    (local.set $unref
+      (local.get $ref)
+    )
+    ;; If we refine $ref then this set will be invalid - we'd be setting a less-
+    ;; refined type into a local/param that is more refined. We should fix this
+    ;; up by using a temp local.
+    (local.set $ref
+      (local.get $unref)
+    )
+  )
+)
+
+
+;; ALWAYS:      (func $refinable_0 (type $ref|$B|_=>_none) (param $ref (ref $B))
+;; ALWAYS-NEXT:  (local $unref (ref $A))
+;; ALWAYS-NEXT:  (local $2 (ref $A))
+;; ALWAYS-NEXT:  (local.set $2
+;; ALWAYS-NEXT:   (local.get $ref)
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT:  (block
+;; ALWAYS-NEXT:   (local.set $unref
+;; ALWAYS-NEXT:    (local.get $2)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:   (local.set $2
+;; ALWAYS-NEXT:    (local.get $unref)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT: )
+(module
+  ;; Multiple refinings of the same function, and of different functions.
+
+  ;; ALWAYS:      (type $A (struct_subtype  data))
+  ;; CAREFUL:      (type $none_=>_none (func_subtype func))
+
+  ;; CAREFUL:      (type $A (struct_subtype  data))
+  (type $A (struct_subtype data))
+  ;; ALWAYS:      (type $B (struct_subtype  $A))
+  ;; CAREFUL:      (type $B (struct_subtype  $A))
+  (type $B (struct_subtype $A))
+
+  ;; ALWAYS:      (type $none_=>_none (func_subtype func))
+
+  ;; ALWAYS:      (type $C (struct_subtype  $B))
+  ;; CAREFUL:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; CAREFUL:      (type $C (struct_subtype  $B))
+  (type $C (struct_subtype $B))
+
+  ;; ALWAYS:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; ALWAYS:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
+
+  ;; ALWAYS:      (type $ref|$C|_=>_none (func_subtype (param (ref $C)) func))
+
+  ;; ALWAYS:      (func $calls1 (type $none_=>_none)
+  ;; ALWAYS-NEXT:  (call $refinable1
+  ;; ALWAYS-NEXT:   (struct.new_default $A)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable1_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $calls1 (type $none_=>_none)
+  ;; CAREFUL-NEXT:  (call $refinable1
+  ;; CAREFUL-NEXT:   (struct.new_default $A)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable1
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $calls1
+    (call $refinable1
+      (struct.new $A)
+    )
+    (call $refinable1
+      (struct.new $B)
+    )
+  )
+
+  ;; ALWAYS:      (func $calls2 (type $none_=>_none)
+  ;; ALWAYS-NEXT:  (call $refinable1_1
+  ;; ALWAYS-NEXT:   (struct.new_default $C)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable2_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $calls2 (type $none_=>_none)
+  ;; CAREFUL-NEXT:  (call $refinable1
+  ;; CAREFUL-NEXT:   (struct.new_default $C)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable2
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $calls2
+    (call $refinable1
+      (struct.new $C)
+    )
+    (call $refinable2
+      (struct.new $B)
+    )
+  )
+
+  ;; ALWAYS:      (func $refinable1 (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; ALWAYS-NEXT:  (drop
+  ;; ALWAYS-NEXT:   (local.get $ref)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $refinable1 (type $ref|$A|_=>_none) (param $0 (ref $A))
+  ;; CAREFUL-NEXT:  (nop)
+  ;; CAREFUL-NEXT: )
+  (func $refinable1 (param $ref (ref $A))
+    (drop
+      (local.get $ref)
+    )
+  )
+
+  ;; ALWAYS:      (func $refinable2 (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; ALWAYS-NEXT:  (drop
+  ;; ALWAYS-NEXT:   (local.get $ref)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $refinable2 (type $ref|$A|_=>_none) (param $0 (ref $A))
+  ;; CAREFUL-NEXT:  (nop)
+  ;; CAREFUL-NEXT: )
+  (func $refinable2 (param $ref (ref $A))
+    (drop
+      (local.get $ref)
+    )
+  )
+)
+
+;; ALWAYS:      (func $refinable1_0 (type $ref|$B|_=>_none) (param $ref (ref $B))
+;; ALWAYS-NEXT:  (drop
+;; ALWAYS-NEXT:   (local.get $ref)
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT: )
+
+;; ALWAYS:      (func $refinable1_1 (type $ref|$C|_=>_none) (param $ref (ref $C))
+;; ALWAYS-NEXT:  (drop
+;; ALWAYS-NEXT:   (local.get $ref)
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT: )
+
+;; ALWAYS:      (func $refinable2_0 (type $ref|$B|_=>_none) (param $ref (ref $B))
+;; ALWAYS-NEXT:  (drop
+;; ALWAYS-NEXT:   (local.get $ref)
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT: )
+(module
+  ;; A case where even CAREFUL mode will monomorphize, as it helps the target
+  ;; function get optimized better.
+
+  ;; ALWAYS:      (type $A (struct_subtype  data))
+  ;; CAREFUL:      (type $A (struct_subtype  data))
+  (type $A (struct_subtype data))
+
+  ;; ALWAYS:      (type $B (struct_subtype  $A))
+  ;; CAREFUL:      (type $B (struct_subtype  $A))
+  (type $B (struct_subtype $A))
+
+  ;; ALWAYS:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
+
+  ;; ALWAYS:      (type $none_=>_none (func_subtype func))
+
+  ;; ALWAYS:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; ALWAYS:      (import "a" "b" (func $import (param (ref $B))))
+
+  ;; ALWAYS:      (global $global (mut i32) (i32.const 1))
+  ;; CAREFUL:      (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func))
+
+  ;; CAREFUL:      (type $none_=>_none (func_subtype func))
+
+  ;; CAREFUL:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; CAREFUL:      (import "a" "b" (func $import (param (ref $B))))
+
+  ;; CAREFUL:      (global $global (mut i32) (i32.const 1))
+  (global $global (mut i32) (i32.const 1))
+
+  (import "a" "b" (func $import (param (ref $B))))
+
+  ;; ALWAYS:      (func $calls (type $none_=>_none)
+  ;; ALWAYS-NEXT:  (call $refinable
+  ;; ALWAYS-NEXT:   (struct.new_default $A)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable
+  ;; ALWAYS-NEXT:   (struct.new_default $A)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $refinable_0
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $calls (type $none_=>_none)
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $A)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable
+  ;; CAREFUL-NEXT:   (struct.new_default $A)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable_0
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $refinable_0
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $calls
+    ;; The calls sending $B will switch to calling a refined version, as the
+    ;; refined version is better, even in CAREFUL mode.
+    (call $refinable
+      (struct.new $A)
+    )
+    (call $refinable
+      (struct.new $A)
+    )
+    (call $refinable
+      (struct.new $B)
+    )
+    (call $refinable
+      (struct.new $B)
+    )
+  )
+
+  ;; ALWAYS:      (func $refinable (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; ALWAYS-NEXT:  (local $x (ref $A))
+  ;; ALWAYS-NEXT:  (call $import
+  ;; ALWAYS-NEXT:   (ref.cast_static $B
+  ;; ALWAYS-NEXT:    (local.get $ref)
+  ;; ALWAYS-NEXT:   )
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (local.set $x
+  ;; ALWAYS-NEXT:   (select (result (ref $A))
+  ;; ALWAYS-NEXT:    (local.get $ref)
+  ;; ALWAYS-NEXT:    (struct.new_default $B)
+  ;; ALWAYS-NEXT:    (global.get $global)
+  ;; ALWAYS-NEXT:   )
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $import
+  ;; ALWAYS-NEXT:   (ref.cast_static $B
+  ;; ALWAYS-NEXT:    (local.get $x)
+  ;; ALWAYS-NEXT:   )
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $import
+  ;; ALWAYS-NEXT:   (ref.cast_static $B
+  ;; ALWAYS-NEXT:    (local.get $x)
+  ;; ALWAYS-NEXT:   )
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT:  (call $import
+  ;; ALWAYS-NEXT:   (ref.cast_static $B
+  ;; ALWAYS-NEXT:    (local.get $ref)
+  ;; ALWAYS-NEXT:   )
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $refinable (type $ref|$A|_=>_none) (param $0 (ref $A))
+  ;; CAREFUL-NEXT:  (local $1 (ref $A))
+  ;; CAREFUL-NEXT:  (call $import
+  ;; CAREFUL-NEXT:   (ref.cast_static $B
+  ;; CAREFUL-NEXT:    (local.get $0)
+  ;; CAREFUL-NEXT:   )
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $import
+  ;; CAREFUL-NEXT:   (ref.cast_static $B
+  ;; CAREFUL-NEXT:    (local.tee $1
+  ;; CAREFUL-NEXT:     (select (result (ref $A))
+  ;; CAREFUL-NEXT:      (local.get $0)
+  ;; CAREFUL-NEXT:      (struct.new_default $B)
+  ;; CAREFUL-NEXT:      (global.get $global)
+  ;; CAREFUL-NEXT:     )
+  ;; CAREFUL-NEXT:    )
+  ;; CAREFUL-NEXT:   )
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $import
+  ;; CAREFUL-NEXT:   (ref.cast_static $B
+  ;; CAREFUL-NEXT:    (local.get $1)
+  ;; CAREFUL-NEXT:   )
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT:  (call $import
+  ;; CAREFUL-NEXT:   (ref.cast_static $B
+  ;; CAREFUL-NEXT:    (local.get $0)
+  ;; CAREFUL-NEXT:   )
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $refinable (param $ref (ref $A))
+    (local $x (ref $A))
+    ;; The refined version of this function will not have the cast, since
+    ;; optimizations manage to remove it using the more refined type.
+    ;;
+    ;; (That is the case in CAREFUL mode, which optimizes; in ALWAYS mode the
+    ;; cast will remain since we monomorphize without bothering to optimize and
+    ;; see if there is any benefit.)
+    (call $import
+      (ref.cast_static $B
+        (local.get $ref)
+      )
+    )
+    ;; Also copy the param into a local. The local should get refined to $B in
+    ;; the refined function in CAREFUL mode.
+    (local.set $x
+      ;; Use a select here so optimizations don't just merge $x and $ref.
+      (select (result (ref $A))
+        (local.get $ref)
+        (struct.new $B)
+        (global.get $global)
+      )
+    )
+    (call $import
+      (ref.cast_static $B
+        (local.get $x)
+      )
+    )
+    (call $import
+      (ref.cast_static $B
+        (local.get $x)
+      )
+    )
+    ;; Another use of $ref, also to avoid opts merging $x and $ref.
+    (call $import
+      (ref.cast_static $B
+        (local.get $ref)
+      )
+    )
+  )
+)
+
+;; ALWAYS:      (func $refinable_0 (type $ref|$B|_=>_none) (param $ref (ref $B))
+;; ALWAYS-NEXT:  (local $x (ref $A))
+;; ALWAYS-NEXT:  (call $import
+;; ALWAYS-NEXT:   (ref.cast_static $B
+;; ALWAYS-NEXT:    (local.get $ref)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT:  (local.set $x
+;; ALWAYS-NEXT:   (select (result (ref $B))
+;; ALWAYS-NEXT:    (local.get $ref)
+;; ALWAYS-NEXT:    (struct.new_default $B)
+;; ALWAYS-NEXT:    (global.get $global)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT:  (call $import
+;; ALWAYS-NEXT:   (ref.cast_static $B
+;; ALWAYS-NEXT:    (local.get $x)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT:  (call $import
+;; ALWAYS-NEXT:   (ref.cast_static $B
+;; ALWAYS-NEXT:    (local.get $x)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT:  (call $import
+;; ALWAYS-NEXT:   (ref.cast_static $B
+;; ALWAYS-NEXT:    (local.get $ref)
+;; ALWAYS-NEXT:   )
+;; ALWAYS-NEXT:  )
+;; ALWAYS-NEXT: )
+
+;; CAREFUL:      (func $refinable_0 (type $ref|$B|_=>_none) (param $0 (ref $B))
+;; CAREFUL-NEXT:  (local $1 (ref $B))
+;; CAREFUL-NEXT:  (call $import
+;; CAREFUL-NEXT:   (local.get $0)
+;; CAREFUL-NEXT:  )
+;; CAREFUL-NEXT:  (call $import
+;; CAREFUL-NEXT:   (local.tee $1
+;; CAREFUL-NEXT:    (select (result (ref $B))
+;; CAREFUL-NEXT:     (local.get $0)
+;; CAREFUL-NEXT:     (struct.new_default $B)
+;; CAREFUL-NEXT:     (global.get $global)
+;; CAREFUL-NEXT:    )
+;; CAREFUL-NEXT:   )
+;; CAREFUL-NEXT:  )
+;; CAREFUL-NEXT:  (call $import
+;; CAREFUL-NEXT:   (local.get $1)
+;; CAREFUL-NEXT:  )
+;; CAREFUL-NEXT:  (call $import
+;; CAREFUL-NEXT:   (local.get $0)
+;; CAREFUL-NEXT:  )
+;; CAREFUL-NEXT: )
+(module
+  ;; Test that we avoid recursive calls.
+
+  ;; ALWAYS:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; ALWAYS:      (type $A (struct_subtype  data))
+  ;; CAREFUL:      (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func))
+
+  ;; CAREFUL:      (type $A (struct_subtype  data))
+  (type $A (struct_subtype data))
+  ;; ALWAYS:      (type $B (struct_subtype  $A))
+  ;; CAREFUL:      (type $B (struct_subtype  $A))
+  (type $B (struct_subtype $A))
+
+
+  ;; ALWAYS:      (func $calls (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; ALWAYS-NEXT:  (call $calls
+  ;; ALWAYS-NEXT:   (struct.new_default $B)
+  ;; ALWAYS-NEXT:  )
+  ;; ALWAYS-NEXT: )
+  ;; CAREFUL:      (func $calls (type $ref|$A|_=>_none) (param $ref (ref $A))
+  ;; CAREFUL-NEXT:  (call $calls
+  ;; CAREFUL-NEXT:   (struct.new_default $B)
+  ;; CAREFUL-NEXT:  )
+  ;; CAREFUL-NEXT: )
+  (func $calls (param $ref (ref $A))
+    ;; We should change nothing in this recursive call, even though we are
+    ;; sending a more refined type, so we could try to monomorphize in theory.
+    (call $calls
+      (struct.new $B)
+    )
+  )
+)
diff --git a/test/lit/passes/multi-memory-lowering.wast b/test/lit/passes/multi-memory-lowering.wast
new file mode 100644
index 0000000..fc402d8
--- /dev/null
+++ b/test/lit/passes/multi-memory-lowering.wast
@@ -0,0 +1,265 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s --enable-multi-memories --multi-memory-lowering --enable-bulk-memory --enable-extended-const -S -o - | filecheck %s
+
+(module
+  (memory $memory1 1)
+  (memory $memory2 2)
+  (memory $memory3 3)
+  (data (memory $memory1) (i32.const 0) "a")
+  (data (memory $memory3) (i32.const 1) "123")
+  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK:      (type $i32_=>_i32 (func (param i32) (result i32)))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (global $memory2_byte_offset (mut i32) (i32.const 65536))
+
+  ;; CHECK:      (global $memory3_byte_offset (mut i32) (i32.const 196608))
+
+  ;; CHECK:      (memory $combined_memory 6)
+
+  ;; CHECK:      (data (i32.const 0) "a")
+
+  ;; CHECK:      (data (i32.add
+  ;; CHECK-NEXT:  (global.get $memory3_byte_offset)
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: ) "123")
+
+  ;; CHECK:      (func $loads
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (global.get $memory2_byte_offset)
+  ;; CHECK-NEXT:     (i32.const 11)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (global.get $memory3_byte_offset)
+  ;; CHECK-NEXT:     (i32.const 12)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $loads
+  (drop
+   (i32.load $memory1
+    (i32.const 10)
+   )
+  )
+  (drop
+   (i32.load $memory2
+    (i32.const 11)
+   )
+  )
+  (drop
+   (i32.load $memory3
+    (i32.const 12)
+   )
+  )
+  )
+  ;; CHECK:      (func $stores
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 10)
+  ;; CHECK-NEXT:   (i32.const 115)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (global.get $memory2_byte_offset)
+  ;; CHECK-NEXT:    (i32.const 11)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 115)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (global.get $memory3_byte_offset)
+  ;; CHECK-NEXT:    (i32.const 12)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 115)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $stores
+  (i32.store $memory1
+   (i32.const 10)
+   (i32.const 115)
+  )
+  (i32.store $memory2
+   (i32.const 11)
+   (i32.const 115)
+  )
+  (i32.store $memory3
+   (i32.const 12)
+   (i32.const 115)
+  )
+  )
+)
+
+;; CHECK:      (func $memory1_size (result i32)
+;; CHECK-NEXT:  (return
+;; CHECK-NEXT:   (i32.div_u
+;; CHECK-NEXT:    (global.get $memory2_byte_offset)
+;; CHECK-NEXT:    (i32.const 65536)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $memory2_size (result i32)
+;; CHECK-NEXT:  (return
+;; CHECK-NEXT:   (i32.sub
+;; CHECK-NEXT:    (i32.div_u
+;; CHECK-NEXT:     (global.get $memory3_byte_offset)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.div_u
+;; CHECK-NEXT:     (global.get $memory2_byte_offset)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $memory3_size (result i32)
+;; CHECK-NEXT:  (return
+;; CHECK-NEXT:   (i32.sub
+;; CHECK-NEXT:    (memory.size)
+;; CHECK-NEXT:    (i32.div_u
+;; CHECK-NEXT:     (global.get $memory3_byte_offset)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $memory1_grow (param $page_delta i32) (result i32)
+;; CHECK-NEXT:  (local $return_size i32)
+;; CHECK-NEXT:  (local $memory_size i32)
+;; CHECK-NEXT:  (local.set $return_size
+;; CHECK-NEXT:   (call $memory1_size)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.set $memory_size
+;; CHECK-NEXT:   (memory.size)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.eq
+;; CHECK-NEXT:    (memory.grow
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.const -1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (return
+;; CHECK-NEXT:    (i32.const -1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (memory.copy
+;; CHECK-NEXT:   (i32.add
+;; CHECK-NEXT:    (global.get $memory2_byte_offset)
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (global.get $memory2_byte_offset)
+;; CHECK-NEXT:   (i32.sub
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $memory_size)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (global.get $memory2_byte_offset)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $memory2_byte_offset
+;; CHECK-NEXT:   (i32.add
+;; CHECK-NEXT:    (global.get $memory2_byte_offset)
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $memory3_byte_offset
+;; CHECK-NEXT:   (i32.add
+;; CHECK-NEXT:    (global.get $memory3_byte_offset)
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $return_size)
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $memory2_grow (param $page_delta i32) (result i32)
+;; CHECK-NEXT:  (local $return_size i32)
+;; CHECK-NEXT:  (local $memory_size i32)
+;; CHECK-NEXT:  (local.set $return_size
+;; CHECK-NEXT:   (call $memory2_size)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.set $memory_size
+;; CHECK-NEXT:   (memory.size)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.eq
+;; CHECK-NEXT:    (memory.grow
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.const -1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (return
+;; CHECK-NEXT:    (i32.const -1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (memory.copy
+;; CHECK-NEXT:   (i32.add
+;; CHECK-NEXT:    (global.get $memory3_byte_offset)
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (global.get $memory3_byte_offset)
+;; CHECK-NEXT:   (i32.sub
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $memory_size)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (global.get $memory3_byte_offset)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (global.set $memory3_byte_offset
+;; CHECK-NEXT:   (i32.add
+;; CHECK-NEXT:    (global.get $memory3_byte_offset)
+;; CHECK-NEXT:    (i32.mul
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:     (i32.const 65536)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $return_size)
+;; CHECK-NEXT: )
+
+;; CHECK:      (func $memory3_grow (param $page_delta i32) (result i32)
+;; CHECK-NEXT:  (local $return_size i32)
+;; CHECK-NEXT:  (local.set $return_size
+;; CHECK-NEXT:   (call $memory3_size)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.eq
+;; CHECK-NEXT:    (memory.grow
+;; CHECK-NEXT:     (local.get $page_delta)
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (i32.const -1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (return
+;; CHECK-NEXT:    (i32.const -1)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $return_size)
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/once-reduction.wast b/test/lit/passes/once-reduction.wast
index 6cd0c9d..d5f6a95 100644
--- a/test/lit/passes/once-reduction.wast
+++ b/test/lit/passes/once-reduction.wast
@@ -68,7 +68,7 @@
   ;; CHECK:      (func $caller-if-1
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (call $once)
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:    (nop)
@@ -97,7 +97,7 @@
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:   (call $once)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (call $once)
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:   )
diff --git a/test/lit/passes/opt_flatten.wast b/test/lit/passes/opt_flatten.wast
index 048ccd6..0e7ab9a 100644
--- a/test/lit/passes/opt_flatten.wast
+++ b/test/lit/passes/opt_flatten.wast
@@ -9,8 +9,8 @@
  (export "foo" (func $foo))
  ;; CHECK:      (func $foo (result funcref)
  ;; CHECK-NEXT:  (local $0 funcref)
- ;; CHECK-NEXT:  (local $1 (ref null $none_=>_funcref))
- ;; CHECK-NEXT:  (local $2 (ref null $none_=>_funcref))
+ ;; CHECK-NEXT:  (local $1 (ref $none_=>_funcref))
+ ;; CHECK-NEXT:  (local $2 (ref $none_=>_funcref))
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $0
@@ -23,14 +23,10 @@
  ;; CHECK-NEXT:    (ref.func $foo)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $2
- ;; CHECK-NEXT:    (ref.as_non_null
- ;; CHECK-NEXT:     (local.get $1)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (return
- ;; CHECK-NEXT:    (ref.as_non_null
- ;; CHECK-NEXT:     (local.get $2)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
diff --git a/test/lit/passes/optimize-added-constants-memory64.wast b/test/lit/passes/optimize-added-constants-memory64.wast
new file mode 100644
index 0000000..e44bc41
--- /dev/null
+++ b/test/lit/passes/optimize-added-constants-memory64.wast
@@ -0,0 +1,57 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+
+;; RUN: wasm-opt %s -all --roundtrip --optimize-added-constants --low-memory-unused -S -o - | filecheck %s
+
+(module
+ ;; CHECK:      (memory $0 i64 1 4294967296)
+ (memory $0 i64 1 4294967296)
+
+
+  ;; CHECK:      (func $load_i64 (result i64)
+  ;; CHECK-NEXT:  (i64.load
+  ;; CHECK-NEXT:   (i64.const 579)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $load_i64 (result i64)
+    (i64.load offset=123
+      (i64.const 456)
+    )
+  )
+
+  ;; CHECK:      (func $load_overflow_i64 (result i64)
+  ;; CHECK-NEXT:  (i64.load offset=32
+  ;; CHECK-NEXT:   (i64.const -16)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $load_overflow_i64 (result i64)
+    (i64.load offset=32
+      (i64.const 0xfffffffffffffff0)
+    )
+  )
+
+  ;; CHECK:      (func $store
+  ;; CHECK-NEXT:  (i64.store
+  ;; CHECK-NEXT:   (i64.const 579)
+  ;; CHECK-NEXT:   (i64.const 123)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $store (result)
+    (i64.store offset=123
+      (i64.const 456)
+      (i64.const 123)
+    )
+  )
+
+  ;; CHECK:      (func $store_overflow
+  ;; CHECK-NEXT:  (i64.store offset=32
+  ;; CHECK-NEXT:   (i64.const -16)
+  ;; CHECK-NEXT:   (i64.const 123)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $store_overflow (result)
+    (i64.store offset=32
+      (i64.const 0xfffffffffffffff0)
+      (i64.const 123)
+    )
+  )
+)
diff --git a/test/lit/passes/optimize-casts.wast b/test/lit/passes/optimize-casts.wast
new file mode 100644
index 0000000..449416e
--- /dev/null
+++ b/test/lit/passes/optimize-casts.wast
@@ -0,0 +1,398 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --optimize-casts -all --nominal -S -o - \
+;; RUN:   | filecheck %s
+
+(module
+  ;; CHECK:      (type $A (struct_subtype  data))
+  (type $A (struct_subtype data))
+
+  ;; CHECK:      (type $B (struct_subtype  $A))
+  (type $B (struct_subtype $A))
+
+  ;; CHECK:      (func $ref.as (type $ref?|$A|_=>_none) (param $x (ref null $A))
+  ;; CHECK-NEXT:  (local $1 (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.as (param $x (ref null $A))
+    ;; After the first ref.as, we can use the cast value in later gets, which is
+    ;; more refined.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (ref.as_non_null
+        (local.get $x)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+    ;; In this case we don't really need the last ref.as here, but we leave that
+    ;; for later opts.
+    (drop
+      (ref.as_non_null
+        (local.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.as-no (type $ref|$A|_=>_none) (param $x (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.as-no (param $x (ref $A))
+    ;; As above, but the param is now non-nullable anyhow, so we should do
+    ;; nothing.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (ref.as_non_null
+        (local.get $x)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (ref.as_non_null
+        (local.get $x)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref.cast (type $ref|data|_=>_none) (param $x (ref data))
+  ;; CHECK-NEXT:  (local $1 (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (ref.cast_static $A
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ref.cast (param $x (ref data))
+    ;; As $ref.as but with ref.casts: we should use the cast value after it has
+    ;; been computed, in both gets.
+    (drop
+      (ref.cast_static $A
+        (local.get $x)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $not-past-set (type $ref|data|_=>_none) (param $x (ref data))
+  ;; CHECK-NEXT:  (local $1 (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (ref.cast_static $A
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (call $get)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $not-past-set (param $x (ref data))
+    (drop
+      (ref.cast_static $A
+        (local.get $x)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+    ;; The local.set in the middle stops us from helping the last get.
+    (local.set $x
+      (call $get)
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $best (type $ref|data|_=>_none) (param $x (ref data))
+  ;; CHECK-NEXT:  (local $1 (ref $A))
+  ;; CHECK-NEXT:  (local $2 (ref $B))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (ref.cast_static $A
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $2
+  ;; CHECK-NEXT:    (ref.cast_static $B
+  ;; CHECK-NEXT:     (local.get $1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $best (param $x (ref data))
+    (drop
+      (ref.cast_static $A
+        (local.get $x)
+      )
+    )
+    ;; Here we should use $A.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (ref.cast_static $B
+        (local.get $x)
+      )
+    )
+    ;; Here we should use $B, which is even better.
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $best-2 (type $ref|data|_=>_none) (param $x (ref data))
+  ;; CHECK-NEXT:  (local $1 (ref $B))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (ref.cast_static $B
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $A
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $best-2 (param $x (ref data))
+    ;; As above, but with the casts reversed. Now we should use $B in both
+    ;; gets.
+    (drop
+      (ref.cast_static $B
+        (local.get $x)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (ref.cast_static $A
+        (local.get $x)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $fallthrough (type $ref|data|_=>_none) (param $x (ref data))
+  ;; CHECK-NEXT:  (local $1 (ref $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $1
+  ;; CHECK-NEXT:    (ref.cast_static $A
+  ;; CHECK-NEXT:     (block (result (ref data))
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $fallthrough (param $x (ref data))
+    (drop
+      (ref.cast_static $A
+        ;; We look through the block, and optimize.
+        (block (result (ref data))
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $past-basic-block (type $ref|data|_=>_none) (param $x (ref data))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.cast_static $A
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (return)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $past-basic-block (param $x (ref data))
+    (drop
+      (ref.cast_static $A
+        (local.get $x)
+      )
+    )
+    ;; The if means the later get is in another basic block. We do not handle
+    ;; this atm.
+    (if
+      (i32.const 0)
+      (return)
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $multiple (type $ref|data|_ref|data|_=>_none) (param $x (ref data)) (param $y (ref data))
+  ;; CHECK-NEXT:  (local $a (ref data))
+  ;; CHECK-NEXT:  (local $b (ref data))
+  ;; CHECK-NEXT:  (local $4 (ref $A))
+  ;; CHECK-NEXT:  (local $5 (ref $A))
+  ;; CHECK-NEXT:  (local.set $a
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $b
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $4
+  ;; CHECK-NEXT:    (ref.cast_static $A
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.tee $5
+  ;; CHECK-NEXT:    (ref.cast_static $A
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $5)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $b
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $multiple (param $x (ref data)) (param $y (ref data))
+    (local $a (ref data))
+    (local $b (ref data))
+    ;; Two different locals, with overlapping lives.
+    (local.set $a
+      (local.get $x)
+    )
+    (local.set $b
+      (local.get $y)
+    )
+    (drop
+      (ref.cast_static $A
+        (local.get $a)
+      )
+    )
+    (drop
+      (ref.cast_static $A
+        (local.get $b)
+      )
+    )
+    ;; These two can be optimized.
+    (drop
+      (local.get $a)
+    )
+    (drop
+      (local.get $b)
+    )
+    (local.set $b
+      (local.get $x)
+    )
+    ;; Now only the first can be, since $b changed.
+    (drop
+      (local.get $a)
+    )
+    (drop
+      (local.get $b)
+    )
+  )
+
+  ;; CHECK:      (func $get (type $none_=>_ref|data|) (result (ref data))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $get (result (ref data))
+    ;; Helper for the above.
+    (unreachable)
+  )
+)
diff --git a/test/lit/passes/optimize-instructions-atomics.wast b/test/lit/passes/optimize-instructions-atomics.wast
index 979afde..2903718 100644
--- a/test/lit/passes/optimize-instructions-atomics.wast
+++ b/test/lit/passes/optimize-instructions-atomics.wast
@@ -7,14 +7,10 @@
 
  ;; CHECK:      (func $x
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (i32.shr_s
- ;; CHECK-NEXT:    (i32.shl
- ;; CHECK-NEXT:     (i32.atomic.load8_u
- ;; CHECK-NEXT:      (i32.const 100)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (i32.const 24)
+ ;; CHECK-NEXT:   (i32.extend8_s
+ ;; CHECK-NEXT:    (i32.atomic.load8_u
+ ;; CHECK-NEXT:     (i32.const 100)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (i32.const 24)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
diff --git a/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast b/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast
index 1d36278..e59c093 100644
--- a/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast
+++ b/test/lit/passes/optimize-instructions-call_ref-roundtrip.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: wasm-opt %s --optimize-instructions --nominal --roundtrip --all-features -S -o - | filecheck %s
 ;; roundtrip to see the effects on heap types in the binary format, specifically
@@ -69,17 +69,17 @@
  (func $call-table-get (param $x i32)
   ;; The heap type of the call_indirects that we emit here should be the
   ;; identical one as on the table that they correspond to.
-  (call_ref
+  (call_ref $v1
    (table.get $table-1
     (local.get $x)
    )
   )
-  (call_ref
+  (call_ref $v2
    (table.get $table-2
     (local.get $x)
    )
   )
-  (call_ref
+  (call_ref $v3
    (table.get $table-3
     (local.get $x)
    )
diff --git a/test/lit/passes/optimize-instructions-call_ref.wast b/test/lit/passes/optimize-instructions-call_ref.wast
index 553f8d0..66e0ee7 100644
--- a/test/lit/passes/optimize-instructions-call_ref.wast
+++ b/test/lit/passes/optimize-instructions-call_ref.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions --all-features -S -o - | filecheck %s
 ;; remove-unused-names is to allow fallthrough computations to work on blocks
@@ -16,16 +16,18 @@
 
  ;; CHECK:      (type $i32_=>_none (func (param i32)))
 
- ;; CHECK:      (type $data_=>_none (func (param dataref)))
+ ;; CHECK:      (type $data_=>_none (func (param (ref data))))
  (type $data_=>_none (func (param (ref data))))
 
+ ;; CHECK:      (type $i32_i32_i32_ref|$i32_i32_=>_none|_=>_none (func (param i32 i32 i32 (ref $i32_i32_=>_none))))
+
  ;; CHECK:      (table $table-1 10 (ref null $i32_i32_=>_none))
  (table $table-1 10 (ref null $i32_i32_=>_none))
  ;; CHECK:      (elem $elem-1 (table $table-1) (i32.const 0) (ref null $i32_i32_=>_none) (ref.func $foo))
  (elem $elem-1 (table $table-1) (i32.const 0) (ref null $i32_i32_=>_none)
   (ref.func $foo))
 
- ;; CHECK:      (elem declare func $fallthrough-no-params $fallthrough-non-nullable $return-nothing)
+ ;; CHECK:      (elem declare func $bar $fallthrough-no-params $fallthrough-non-nullable $return-nothing)
 
  ;; CHECK:      (func $foo (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (unreachable)
@@ -33,6 +35,14 @@
  (func $foo (param i32) (param i32)
   (unreachable)
  )
+
+ ;; CHECK:      (func $bar (param $0 i32) (param $1 i32)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $bar (param i32) (param i32)
+  (unreachable)
+ )
+
  ;; CHECK:      (func $call_ref-to-direct (param $x i32) (param $y i32)
  ;; CHECK-NEXT:  (call $foo
  ;; CHECK-NEXT:   (local.get $x)
@@ -41,7 +51,7 @@
  ;; CHECK-NEXT: )
  (func $call_ref-to-direct (param $x i32) (param $y i32)
   ;; This call_ref should become a direct call.
-  (call_ref
+  (call_ref $i32_i32_=>_none
    (local.get $x)
    (local.get $y)
    (ref.func $foo)
@@ -74,7 +84,7 @@
   ;; This call_ref should become a direct call, even though it doesn't have a
   ;; simple ref.func as the target - we need to look into the fallthrough, and
   ;; handle things with locals.
-  (call_ref
+  (call_ref $i32_i32_=>_none
    ;; Write to $x before the block, and write to it in the block; we should not
    ;; reorder these things as the side effects could alter what value appears
    ;; in the get of $x. (There is a risk of reordering here if we naively moved
@@ -106,7 +116,7 @@
  (func $fallthrough-no-params (result i32)
   ;; A fallthrough appears here, but there are no operands so this is easier to
   ;; optimize: we can just drop the call_ref's target before the call.
-  (call_ref
+  (call_ref $none_=>_i32
    (block (result (ref $none_=>_i32))
     (nop)
     (ref.func $fallthrough-no-params)
@@ -114,10 +124,10 @@
   )
  )
 
- ;; CHECK:      (func $fallthrough-non-nullable (param $x dataref)
- ;; CHECK-NEXT:  (local $1 (ref null data))
+ ;; CHECK:      (func $fallthrough-non-nullable (param $x (ref data))
+ ;; CHECK-NEXT:  (local $1 dataref)
  ;; CHECK-NEXT:  (call $fallthrough-non-nullable
- ;; CHECK-NEXT:   (block (result dataref)
+ ;; CHECK-NEXT:   (block (result (ref data))
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
@@ -138,7 +148,7 @@
   ;; nullable, which means we must be careful when we create a temp local for
   ;; it: the local should be nullable, and gets of it should use a
   ;; ref.as_non_null so that we validate.
-  (call_ref
+  (call_ref $data_=>_none
    (local.get $x)
    (block (result (ref $data_=>_none))
     (nop)
@@ -148,14 +158,11 @@
  )
 
  ;; CHECK:      (func $fallthrough-bad-type (result i32)
- ;; CHECK-NEXT:  (call_ref
+ ;; CHECK-NEXT:  (call_ref $none_=>_i32
  ;; CHECK-NEXT:   (block (result (ref $none_=>_i32))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.func $return-nothing)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (drop
- ;; CHECK-NEXT:     (rtt.canon $none_=>_i32)
- ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -167,10 +174,9 @@
   ;; emit non-validating code here, which would happen if we replace the
   ;; call_ref that returns nothing with a call that returns an i32. In fact, we
   ;; end up optimizing the cast into an unreachable.
-  (call_ref
-   (ref.cast
+  (call_ref $none_=>_i32
+   (ref.cast_static $none_=>_i32
     (ref.func $return-nothing)
-    (rtt.canon $none_=>_i32)
    )
   )
  )
@@ -182,7 +188,7 @@
  (func $return-nothing)
 
  ;; CHECK:      (func $fallthrough-unreachable
- ;; CHECK-NEXT:  (call_ref
+ ;; CHECK-NEXT:  (call_ref $i32_i32_=>_none
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (block (result (ref $i32_i32_=>_none))
@@ -193,7 +199,7 @@
  ;; CHECK-NEXT: )
  (func $fallthrough-unreachable
   ;; If the call is not reached, do not optimize it.
-  (call_ref
+  (call_ref $i32_i32_=>_none
    (unreachable)
    (unreachable)
    (block (result (ref $i32_i32_=>_none))
@@ -204,11 +210,16 @@
  )
 
  ;; CHECK:      (func $ignore-unreachable
- ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
+ ;; CHECK-NEXT:   (drop
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ignore-unreachable
   ;; Ignore an unreachable call_ref target entirely.
-  (call_ref
+  (call_ref $i32_i32_=>_none
    (unreachable)
   )
  )
@@ -221,7 +232,7 @@
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $call-table-get (param $x i32)
-  (call_ref
+  (call_ref $i32_i32_=>_none
    (i32.const 1)
    (i32.const 2)
    (table.get $table-1
@@ -229,4 +240,103 @@
    )
   )
  )
+
+ ;; CHECK:      (func $call_ref-to-select (param $x i32) (param $y i32) (param $z i32) (param $f (ref $i32_i32_=>_none))
+ ;; CHECK-NEXT:  (local $4 i32)
+ ;; CHECK-NEXT:  (local $5 i32)
+ ;; CHECK-NEXT:  (block
+ ;; CHECK-NEXT:   (local.set $4
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.set $5
+ ;; CHECK-NEXT:    (local.get $y)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (if
+ ;; CHECK-NEXT:    (local.get $z)
+ ;; CHECK-NEXT:    (call $foo
+ ;; CHECK-NEXT:     (local.get $4)
+ ;; CHECK-NEXT:     (local.get $5)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (call $bar
+ ;; CHECK-NEXT:     (local.get $4)
+ ;; CHECK-NEXT:     (local.get $5)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (call_ref $i32_i32_=>_none
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:   (select (result (ref $i32_i32_=>_none))
+ ;; CHECK-NEXT:    (local.get $f)
+ ;; CHECK-NEXT:    (ref.func $bar)
+ ;; CHECK-NEXT:    (local.get $z)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $call_ref-to-select (param $x i32) (param $y i32) (param $z i32) (param $f (ref $i32_i32_=>_none))
+  ;; This call_ref should become an if over two direct calls.
+  (call_ref $i32_i32_=>_none
+   (local.get $x)
+   (local.get $y)
+   (select
+    (ref.func $foo)
+    (ref.func $bar)
+    (local.get $z)
+   )
+  )
+
+  ;; But here one arm is not constant, so we do not optimize.
+  (call_ref $i32_i32_=>_none
+   (local.get $x)
+   (local.get $y)
+   (select
+    (local.get $f)
+    (ref.func $bar)
+    (local.get $z)
+   )
+  )
+ )
+
+ ;; CHECK:      (func $return_call_ref-to-select (param $x i32) (param $y i32)
+ ;; CHECK-NEXT:  (local $2 i32)
+ ;; CHECK-NEXT:  (local $3 i32)
+ ;; CHECK-NEXT:  (local.set $2
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (local.set $3
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (if
+ ;; CHECK-NEXT:   (call $get-i32)
+ ;; CHECK-NEXT:   (return_call $foo
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $3)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (return_call $bar
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $3)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $return_call_ref-to-select (param $x i32) (param $y i32)
+  ;; As above, but with a return call. We optimize this too, and turn a
+  ;; return_call_ref over a select into an if over return_calls.
+  (return_call_ref $i32_i32_=>_none
+   (local.get $x)
+   (local.get $y)
+   (select
+    (ref.func $foo)
+    (ref.func $bar)
+    (call $get-i32)
+   )
+  )
+ )
+
+ ;; CHECK:      (func $get-i32 (result i32)
+ ;; CHECK-NEXT:  (i32.const 42)
+ ;; CHECK-NEXT: )
+ (func $get-i32 (result i32)
+  ;; Helper for the above.
+  (i32.const 42)
+ )
 )
diff --git a/test/lit/passes/optimize-instructions-default.wast b/test/lit/passes/optimize-instructions-default.wast
new file mode 100644
index 0000000..ab40b4d
--- /dev/null
+++ b/test/lit/passes/optimize-instructions-default.wast
@@ -0,0 +1,130 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --optimize-instructions -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (func $duplicate-elimination (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.extend8_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.extend16_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $duplicate-elimination (param $x i32)
+    (drop (i32.extend8_s (i32.extend8_s (local.get $x))))
+    (drop (i32.extend16_s (i32.extend16_s (local.get $x))))
+  )
+
+  ;; i64(x) << 56 >> 56   ==>   i64.extend8_s(x)
+  ;; i64(x) << 48 >> 48   ==>   i64.extend16_s(x)
+  ;; i64(x) << 32 >> 32   ==>   i64.extend32_s(x)
+  ;; i64.extend_i32_s(i32.wrap_i64(x))   ==>   i64.extend32_s(x)
+
+  ;; CHECK:      (func $i64-sign-extentions (param $x i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend8_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend16_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend32_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shr_s
+  ;; CHECK-NEXT:    (i64.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i64.const 16)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 16)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend32_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $i64-sign-extentions (param $x i64)
+    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 56)) (i64.const 56)))
+    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 48)) (i64.const 48)))
+    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32)))
+    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 16)) (i64.const 16))) ;; skip
+    (drop (i64.extend_i32_s (i32.wrap_i64 (local.get $x))))
+  )
+
+  ;; i32(x) << 24 >> 24   ==>   i32.extend8_s(x)
+  ;; i32(x) << 16 >> 16   ==>   i32.extend16_s(x)
+
+  ;; CHECK:      (func $i32-sign-extentions (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.extend8_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.extend16_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:    (i32.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 8)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 255)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 65535)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:    (i32.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 16)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 24)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:    (i32.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 24)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 16)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $i32-sign-extentions (param $x i32)
+    (drop (i32.shr_s (i32.shl (local.get $x) (i32.const 24)) (i32.const 24)))
+    (drop (i32.shr_s (i32.shl (local.get $x) (i32.const 16)) (i32.const 16)))
+
+    (drop (i32.shr_s (i32.shl (local.get $x)  (i32.const 8))  (i32.const 8))) ;; skip
+    (drop (i32.shr_u (i32.shl (local.get $x) (i32.const 24)) (i32.const 24))) ;; skip
+    (drop (i32.shr_u (i32.shl (local.get $x) (i32.const 16)) (i32.const 16))) ;; skip
+    (drop (i32.shr_s (i32.shl (local.get $x) (i32.const 16)) (i32.const 24))) ;; skip
+    (drop (i32.shr_s (i32.shl (local.get $x) (i32.const 24)) (i32.const 16))) ;; skip
+  )
+)
diff --git a/test/lit/passes/optimize-instructions-gc-extern.wast b/test/lit/passes/optimize-instructions-gc-extern.wast
new file mode 100644
index 0000000..18c91f1
--- /dev/null
+++ b/test/lit/passes/optimize-instructions-gc-extern.wast
@@ -0,0 +1,59 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --optimize-instructions -all -S -o - \
+;; RUN:   | filecheck %s
+
+(module
+  ;; CHECK:      (func $extern.externalize (param $x anyref) (param $y externref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (extern.externalize
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (extern.externalize
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (extern.internalize
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (extern.internalize
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $extern.externalize (export "ext") (param $x (ref null any)) (param $y (ref null extern))
+    ;; We should not change anything here, and also not hit an internal error.
+    (drop
+      (extern.externalize
+        (local.get $x)
+      )
+    )
+    (drop
+      (extern.externalize
+        (ref.as_non_null
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (extern.internalize
+        (local.get $y)
+      )
+    )
+    (drop
+      (extern.internalize
+        (ref.as_non_null
+          (local.get $y)
+        )
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/optimize-instructions-gc-heap.wast b/test/lit/passes/optimize-instructions-gc-heap.wast
index fc44e11..e448e57 100644
--- a/test/lit/passes/optimize-instructions-gc-heap.wast
+++ b/test/lit/passes/optimize-instructions-gc-heap.wast
@@ -308,7 +308,7 @@
   ;; CHECK-NEXT:   (local.get $ref)
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (local.set $ref
-  ;; CHECK-NEXT:     (ref.null $struct)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:   )
@@ -343,7 +343,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (local.set $ref
-  ;; CHECK-NEXT:     (ref.null $struct)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:   )
@@ -374,7 +374,7 @@
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (local.set $other
-  ;; CHECK-NEXT:      (ref.null $struct)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (i32.const 20)
   ;; CHECK-NEXT:    )
@@ -717,6 +717,7 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
diff --git a/test/lit/passes/optimize-instructions-gc-iit.wast b/test/lit/passes/optimize-instructions-gc-iit.wast
index 4117dce..24c5c68 100644
--- a/test/lit/passes/optimize-instructions-gc-iit.wast
+++ b/test/lit/passes/optimize-instructions-gc-iit.wast
@@ -33,107 +33,65 @@
   (func $foo)
 
 
-  ;; CHECK:      (func $ref-cast-iit (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other)) (param $parent-rtt (rtt $parent)) (param $child-rtt (rtt $child)) (param $other-rtt (rtt $other))
+  ;; CHECK:      (func $ref-cast-iit (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref $parent))
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $parent-rtt)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $parent)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $parent)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref $child))
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $parent-rtt)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $child)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $child)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $child
   ;; CHECK-NEXT:    (local.get $parent)
-  ;; CHECK-NEXT:    (local.get $child-rtt)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref $other))
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $child)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $other-rtt)
-  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $ref-cast-iit (type $ref|$parent|_ref|$child|_ref|$other|_rtt_$parent_rtt_$child_rtt_$other_=>_none) (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other)) (param $parent-rtt (rtt $parent)) (param $child-rtt (rtt $child)) (param $other-rtt (rtt $other))
+  ;; NOMNL:      (func $ref-cast-iit (type $ref|$parent|_ref|$child|_ref|$other|_=>_none) (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref $parent))
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (local.get $parent-rtt)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (local.get $parent)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (local.get $parent)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref $child))
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (local.get $parent-rtt)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (local.get $child)
-  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (local.get $child)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $child
   ;; NOMNL-NEXT:    (local.get $parent)
-  ;; NOMNL-NEXT:    (local.get $child-rtt)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref $other))
+  ;; NOMNL-NEXT:   (block
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $child)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (local.get $other-rtt)
-  ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (unreachable)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
-  ;; NOMNL-TNH:      (func $ref-cast-iit (type $ref|$parent|_ref|$child|_ref|$other|_rtt_$parent_rtt_$child_rtt_$other_=>_none) (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other)) (param $parent-rtt (rtt $parent)) (param $child-rtt (rtt $child)) (param $other-rtt (rtt $other))
+  ;; NOMNL-TNH:      (func $ref-cast-iit (type $ref|$parent|_ref|$child|_ref|$other|_=>_none) (param $parent (ref $parent)) (param $child (ref $child)) (param $other (ref $other))
   ;; NOMNL-TNH-NEXT:  (drop
-  ;; NOMNL-TNH-NEXT:   (block (result (ref $parent))
-  ;; NOMNL-TNH-NEXT:    (drop
-  ;; NOMNL-TNH-NEXT:     (local.get $parent-rtt)
-  ;; NOMNL-TNH-NEXT:    )
-  ;; NOMNL-TNH-NEXT:    (local.get $parent)
-  ;; NOMNL-TNH-NEXT:   )
+  ;; NOMNL-TNH-NEXT:   (local.get $parent)
   ;; NOMNL-TNH-NEXT:  )
   ;; NOMNL-TNH-NEXT:  (drop
-  ;; NOMNL-TNH-NEXT:   (block (result (ref $child))
-  ;; NOMNL-TNH-NEXT:    (drop
-  ;; NOMNL-TNH-NEXT:     (local.get $parent-rtt)
-  ;; NOMNL-TNH-NEXT:    )
-  ;; NOMNL-TNH-NEXT:    (local.get $child)
-  ;; NOMNL-TNH-NEXT:   )
+  ;; NOMNL-TNH-NEXT:   (local.get $child)
   ;; NOMNL-TNH-NEXT:  )
   ;; NOMNL-TNH-NEXT:  (drop
-  ;; NOMNL-TNH-NEXT:   (ref.cast
+  ;; NOMNL-TNH-NEXT:   (ref.cast_static $child
   ;; NOMNL-TNH-NEXT:    (local.get $parent)
-  ;; NOMNL-TNH-NEXT:    (local.get $child-rtt)
   ;; NOMNL-TNH-NEXT:   )
   ;; NOMNL-TNH-NEXT:  )
   ;; NOMNL-TNH-NEXT:  (drop
-  ;; NOMNL-TNH-NEXT:   (block (result (ref $other))
+  ;; NOMNL-TNH-NEXT:   (block
   ;; NOMNL-TNH-NEXT:    (drop
   ;; NOMNL-TNH-NEXT:     (local.get $child)
   ;; NOMNL-TNH-NEXT:    )
-  ;; NOMNL-TNH-NEXT:    (drop
-  ;; NOMNL-TNH-NEXT:     (local.get $other-rtt)
-  ;; NOMNL-TNH-NEXT:    )
   ;; NOMNL-TNH-NEXT:    (unreachable)
   ;; NOMNL-TNH-NEXT:   )
   ;; NOMNL-TNH-NEXT:  )
@@ -143,174 +101,91 @@
     (param $child (ref $child))
     (param $other (ref $other))
 
-    (param $parent-rtt (rtt $parent))
-    (param $child-rtt (rtt $child))
-    (param $other-rtt (rtt $other))
-
-    ;; a cast of parent to an rtt of parent: assuming no traps as we do, we can
-    ;; optimize this as the new type will be valid.
+    ;; a cast of parent to parent. We can optimize this as the new type will be
+    ;; valid.
     (drop
-      (ref.cast
+      (ref.cast_static $parent
         (local.get $parent)
-        (local.get $parent-rtt)
       )
     )
     ;; a cast of child to a supertype: again, we replace with a valid type.
     (drop
-      (ref.cast
+      (ref.cast_static $parent
         (local.get $child)
-        (local.get $parent-rtt)
       )
     )
     ;; a cast of parent to a subtype: we cannot replace the original heap type
     ;; $child with one that is not equal or more specific, like $parent, so we
     ;; cannot optimize here.
     (drop
-      (ref.cast
+      (ref.cast_static $child
         (local.get $parent)
-        (local.get $child-rtt)
       )
     )
     ;; a cast of child to an unrelated type: it will trap anyhow
     (drop
-      (ref.cast
+      (ref.cast_static $other
         (local.get $child)
-        (local.get $other-rtt)
       )
     )
   )
 
-  ;; CHECK:      (func $ref-cast-iit-bad (param $parent (ref $parent)) (param $parent-rtt (rtt $parent))
-  ;; CHECK-NEXT:  (local $2 (ref null $parent))
+  ;; CHECK:      (func $ref-cast-iit-bad (param $parent (ref $parent))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref $parent))
-  ;; CHECK-NEXT:    (local.set $2
-  ;; CHECK-NEXT:     (block $block (result (ref $parent))
-  ;; CHECK-NEXT:      (call $foo)
-  ;; CHECK-NEXT:      (local.get $parent)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (block $block0 (result (rtt $parent))
-  ;; CHECK-NEXT:      (call $foo)
-  ;; CHECK-NEXT:      (local.get $parent-rtt)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.as_non_null
-  ;; CHECK-NEXT:     (local.get $2)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:    (call $foo)
   ;; CHECK-NEXT:    (local.get $parent)
-  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $parent
   ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:    (local.get $parent-rtt)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $ref-cast-iit-bad (type $ref|$parent|_rtt_$parent_=>_none) (param $parent (ref $parent)) (param $parent-rtt (rtt $parent))
-  ;; NOMNL-NEXT:  (local $2 (ref null $parent))
+  ;; NOMNL:      (func $ref-cast-iit-bad (type $ref|$parent|_=>_none) (param $parent (ref $parent))
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result (ref $parent))
-  ;; NOMNL-NEXT:    (local.set $2
-  ;; NOMNL-NEXT:     (block $block (result (ref $parent))
-  ;; NOMNL-NEXT:      (call $foo)
-  ;; NOMNL-NEXT:      (local.get $parent)
-  ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (block $block0 (result (rtt $parent))
-  ;; NOMNL-NEXT:      (call $foo)
-  ;; NOMNL-NEXT:      (local.get $parent-rtt)
-  ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.as_non_null
-  ;; NOMNL-NEXT:     (local.get $2)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:   )
-  ;; NOMNL-NEXT:  )
-  ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:    (call $foo)
   ;; NOMNL-NEXT:    (local.get $parent)
-  ;; NOMNL-NEXT:    (unreachable)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $parent
   ;; NOMNL-NEXT:    (unreachable)
-  ;; NOMNL-NEXT:    (local.get $parent-rtt)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
-  ;; NOMNL-TNH:      (func $ref-cast-iit-bad (type $ref|$parent|_rtt_$parent_=>_none) (param $parent (ref $parent)) (param $parent-rtt (rtt $parent))
-  ;; NOMNL-TNH-NEXT:  (local $2 (ref null $parent))
+  ;; NOMNL-TNH:      (func $ref-cast-iit-bad (type $ref|$parent|_=>_none) (param $parent (ref $parent))
   ;; NOMNL-TNH-NEXT:  (drop
   ;; NOMNL-TNH-NEXT:   (block (result (ref $parent))
-  ;; NOMNL-TNH-NEXT:    (local.set $2
-  ;; NOMNL-TNH-NEXT:     (block $block (result (ref $parent))
-  ;; NOMNL-TNH-NEXT:      (call $foo)
-  ;; NOMNL-TNH-NEXT:      (local.get $parent)
-  ;; NOMNL-TNH-NEXT:     )
-  ;; NOMNL-TNH-NEXT:    )
-  ;; NOMNL-TNH-NEXT:    (drop
-  ;; NOMNL-TNH-NEXT:     (block $block0 (result (rtt $parent))
-  ;; NOMNL-TNH-NEXT:      (call $foo)
-  ;; NOMNL-TNH-NEXT:      (local.get $parent-rtt)
-  ;; NOMNL-TNH-NEXT:     )
-  ;; NOMNL-TNH-NEXT:    )
-  ;; NOMNL-TNH-NEXT:    (ref.as_non_null
-  ;; NOMNL-TNH-NEXT:     (local.get $2)
-  ;; NOMNL-TNH-NEXT:    )
-  ;; NOMNL-TNH-NEXT:   )
-  ;; NOMNL-TNH-NEXT:  )
-  ;; NOMNL-TNH-NEXT:  (drop
-  ;; NOMNL-TNH-NEXT:   (ref.cast
+  ;; NOMNL-TNH-NEXT:    (call $foo)
   ;; NOMNL-TNH-NEXT:    (local.get $parent)
-  ;; NOMNL-TNH-NEXT:    (unreachable)
   ;; NOMNL-TNH-NEXT:   )
   ;; NOMNL-TNH-NEXT:  )
   ;; NOMNL-TNH-NEXT:  (drop
-  ;; NOMNL-TNH-NEXT:   (ref.cast
+  ;; NOMNL-TNH-NEXT:   (ref.cast_static $parent
   ;; NOMNL-TNH-NEXT:    (unreachable)
-  ;; NOMNL-TNH-NEXT:    (local.get $parent-rtt)
   ;; NOMNL-TNH-NEXT:   )
   ;; NOMNL-TNH-NEXT:  )
   ;; NOMNL-TNH-NEXT: )
   (func $ref-cast-iit-bad
     (param $parent (ref $parent))
-    (param $parent-rtt (rtt $parent))
 
     ;; optimizing this cast away requires reordering.
     (drop
-      (ref.cast
+      (ref.cast_static $parent
         (block (result (ref $parent))
           (call $foo)
           (local.get $parent)
         )
-        (block (result (rtt $parent))
-          (call $foo)
-          (local.get $parent-rtt)
-        )
       )
     )
 
     ;; ignore unreachability
     (drop
-      (ref.cast
-        (local.get $parent)
-        (unreachable)
-      )
-    )
-    (drop
-      (ref.cast
+      (ref.cast_static $parent
         (unreachable)
-        (local.get $parent-rtt)
       )
     )
   )
@@ -335,9 +210,8 @@
     (drop
       (ref.eq
         (local.get $x)
-        (ref.cast
+        (ref.cast_static $parent
           (local.get $x)
-          (rtt.canon $parent)
         )
       )
     )
diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast
new file mode 100644
index 0000000..d8fe7a6
--- /dev/null
+++ b/test/lit/passes/optimize-instructions-gc-tnh.wast
@@ -0,0 +1,203 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --optimize-instructions --traps-never-happen -all --nominal -S -o - | filecheck %s --check-prefix TNH
+;; RUN: wasm-opt %s --optimize-instructions                      -all --nominal -S -o - | filecheck %s --check-prefix NO_TNH
+
+(module
+  ;; TNH:      (type $struct (struct_subtype  data))
+  ;; NO_TNH:      (type $struct (struct_subtype  data))
+  (type $struct (struct_subtype data))
+
+  ;; TNH:      (func $ref.eq (type $eqref_eqref_=>_i32) (param $a eqref) (param $b eqref) (result i32)
+  ;; TNH-NEXT:  (ref.eq
+  ;; TNH-NEXT:   (local.get $a)
+  ;; TNH-NEXT:   (local.get $b)
+  ;; TNH-NEXT:  )
+  ;; TNH-NEXT: )
+  ;; NO_TNH:      (func $ref.eq (type $eqref_eqref_=>_i32) (param $a eqref) (param $b eqref) (result i32)
+  ;; NO_TNH-NEXT:  (ref.eq
+  ;; NO_TNH-NEXT:   (ref.as_non_null
+  ;; NO_TNH-NEXT:    (ref.cast_static $struct
+  ;; NO_TNH-NEXT:     (local.get $a)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:   (ref.as_data
+  ;; NO_TNH-NEXT:    (local.get $b)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT: )
+  (func $ref.eq (param $a (ref null eq)) (param $b (ref null eq)) (result i32)
+    ;; When traps never happen we can remove all the casts here, since they do
+    ;; not affect the comparison of the references.
+    (ref.eq
+      (ref.as_data
+        (ref.as_non_null
+          (ref.cast_static $struct
+            (local.get $a)
+          )
+        )
+      )
+      ;; Note that we can remove the non-null casts here in both modes, as the
+      ;; ref.as_data also checks for null.
+      (ref.as_data
+        (ref.as_non_null
+          (ref.as_non_null
+            (local.get $b)
+          )
+        )
+      )
+    )
+  )
+
+  ;; TNH:      (func $ref.eq-no (type $eqref_eqref_=>_none) (param $a eqref) (param $b eqref)
+  ;; TNH-NEXT:  (drop
+  ;; TNH-NEXT:   (i32.const 1)
+  ;; TNH-NEXT:  )
+  ;; TNH-NEXT: )
+  ;; NO_TNH:      (func $ref.eq-no (type $eqref_eqref_=>_none) (param $a eqref) (param $b eqref)
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.eq
+  ;; NO_TNH-NEXT:    (block (result (ref $struct))
+  ;; NO_TNH-NEXT:     (drop
+  ;; NO_TNH-NEXT:      (ref.func $ref.eq-no)
+  ;; NO_TNH-NEXT:     )
+  ;; NO_TNH-NEXT:     (unreachable)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:    (block (result (ref data))
+  ;; NO_TNH-NEXT:     (drop
+  ;; NO_TNH-NEXT:      (ref.func $ref.eq-no)
+  ;; NO_TNH-NEXT:     )
+  ;; NO_TNH-NEXT:     (unreachable)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT: )
+  (func $ref.eq-no (param $a (ref null eq)) (param $b (ref null eq))
+    ;; We must leave the inputs to ref.eq of type eqref or a subtype. Note that
+    ;; these casts will trap, so other opts might get in the way before we can
+    ;; do anything. The crucial thing we test here is that we do not emit
+    ;; something that does not validate (as ref.eq inputs must be eqrefs).
+    (drop
+      (ref.eq
+        (ref.cast_static $struct
+          (ref.func $ref.eq-no) ;; *Not* an eqref!
+        )
+        (ref.as_non_null
+          (ref.as_data
+            (ref.as_non_null
+              (ref.func $ref.eq-no) ;; *Not* an eqref!
+            )
+          )
+        )
+      )
+    )
+  )
+
+  ;; TNH:      (func $ref.is (type $eqref_=>_i32) (param $a eqref) (result i32)
+  ;; TNH-NEXT:  (drop
+  ;; TNH-NEXT:   (ref.cast_static $struct
+  ;; TNH-NEXT:    (ref.as_data
+  ;; TNH-NEXT:     (local.get $a)
+  ;; TNH-NEXT:    )
+  ;; TNH-NEXT:   )
+  ;; TNH-NEXT:  )
+  ;; TNH-NEXT:  (i32.const 0)
+  ;; TNH-NEXT: )
+  ;; NO_TNH:      (func $ref.is (type $eqref_=>_i32) (param $a eqref) (result i32)
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.cast_static $struct
+  ;; NO_TNH-NEXT:    (ref.as_data
+  ;; NO_TNH-NEXT:     (local.get $a)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (i32.const 0)
+  ;; NO_TNH-NEXT: )
+  (func $ref.is (param $a (ref null eq)) (result i32)
+    ;; In this case non-nullability is enough to tell that the ref.is will
+    ;; return 0. TNH does not help here.
+    (ref.is_null
+      (ref.cast_static $struct
+        (ref.as_non_null
+          (ref.as_data
+            (local.get $a)
+          )
+        )
+      )
+    )
+  )
+
+  ;; TNH:      (func $ref.is_b (type $eqref_=>_i32) (param $a eqref) (result i32)
+  ;; TNH-NEXT:  (ref.is_null
+  ;; TNH-NEXT:   (local.get $a)
+  ;; TNH-NEXT:  )
+  ;; TNH-NEXT: )
+  ;; NO_TNH:      (func $ref.is_b (type $eqref_=>_i32) (param $a eqref) (result i32)
+  ;; NO_TNH-NEXT:  (ref.is_null
+  ;; NO_TNH-NEXT:   (ref.cast_static $struct
+  ;; NO_TNH-NEXT:    (local.get $a)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT: )
+  (func $ref.is_b(param $a (ref null eq)) (result i32)
+    ;; Here we only have a cast, and no ref.as operations that force the value
+    ;; to be non-nullable. That means we cannot remove the ref.is, but we can
+    ;; remove the cast in TNH.
+    (ref.is_null
+      (ref.cast_static $struct
+        (local.get $a)
+      )
+    )
+  )
+
+  ;; TNH:      (func $ref.is_func_a (type $anyref_=>_i32) (param $a anyref) (result i32)
+  ;; TNH-NEXT:  (drop
+  ;; TNH-NEXT:   (ref.as_func
+  ;; TNH-NEXT:    (local.get $a)
+  ;; TNH-NEXT:   )
+  ;; TNH-NEXT:  )
+  ;; TNH-NEXT:  (i32.const 1)
+  ;; TNH-NEXT: )
+  ;; NO_TNH:      (func $ref.is_func_a (type $anyref_=>_i32) (param $a anyref) (result i32)
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.as_func
+  ;; NO_TNH-NEXT:    (local.get $a)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (i32.const 1)
+  ;; NO_TNH-NEXT: )
+  (func $ref.is_func_a (param $a (ref null any)) (result i32)
+    ;; The check must succeed. We can return 1 here, and drop the rest, with or
+    ;; without TNH (in particular, TNH should not just remove the cast but not
+    ;; return a 1).
+    (ref.is_func
+      (ref.as_func
+        (local.get $a)
+      )
+    )
+  )
+
+  ;; TNH:      (func $ref.is_func_b (type $anyref_=>_i32) (param $a anyref) (result i32)
+  ;; TNH-NEXT:  (drop
+  ;; TNH-NEXT:   (ref.as_data
+  ;; TNH-NEXT:    (local.get $a)
+  ;; TNH-NEXT:   )
+  ;; TNH-NEXT:  )
+  ;; TNH-NEXT:  (i32.const 0)
+  ;; TNH-NEXT: )
+  ;; NO_TNH:      (func $ref.is_func_b (type $anyref_=>_i32) (param $a anyref) (result i32)
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.as_data
+  ;; NO_TNH-NEXT:    (local.get $a)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (i32.const 0)
+  ;; NO_TNH-NEXT: )
+  (func $ref.is_func_b (param $a (ref null any)) (result i32)
+    ;; A case where the type cannot match, and we return 0.
+    (ref.is_func
+      (ref.as_data
+        (local.get $a)
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast
index 64213d9..fb50d10 100644
--- a/test/lit/passes/optimize-instructions-gc.wast
+++ b/test/lit/passes/optimize-instructions-gc.wast
@@ -14,13 +14,14 @@
     (field $i64 (mut i64))
   ))
 
+  ;; CHECK:      (type $B (struct (field i32) (field i32) (field f32)))
+
+  ;; CHECK:      (type $array (array (mut i8)))
+
   ;; CHECK:      (type $A (struct (field i32)))
   ;; NOMNL:      (type $A (struct_subtype (field i32) data))
   (type $A (struct (field i32)))
 
-  ;; CHECK:      (type $B (struct (field i32) (field i32) (field f32)))
-
-  ;; CHECK:      (type $array (array (mut i8)))
   ;; NOMNL:      (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
 
   ;; NOMNL:      (type $array (array_subtype (mut i8) data))
@@ -32,14 +33,20 @@
   ;; NOMNL:      (type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B))
   (type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B))
 
-  ;; CHECK:      (type $empty (struct ))
+  ;; NOMNL:      (type $void (func_subtype func))
+
+  ;; NOMNL:      (type $C (struct_subtype (field i32) (field i32) (field f64) $A))
+
   ;; NOMNL:      (type $empty (struct_subtype  data))
   (type $empty (struct))
 
+  ;; CHECK:      (type $void (func))
+
   ;; CHECK:      (type $C (struct (field i32) (field i32) (field f64)))
-  ;; NOMNL:      (type $C (struct_subtype (field i32) (field i32) (field f64) $A))
   (type $C (struct_subtype (field i32) (field i32) (field f64) $A))
 
+  (type $void (func))
+
   ;; CHECK:      (import "env" "get-i32" (func $get-i32 (result i32)))
   ;; NOMNL:      (import "env" "get-i32" (func $get-i32 (result i32)))
   (import "env" "get-i32" (func $get-i32 (result i32)))
@@ -47,38 +54,38 @@
   ;; These functions test if an `if` with subtyped arms is correctly folded
   ;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold)
   ;; CHECK:      (func $if-arms-subtype-fold (result anyref)
-  ;; CHECK-NEXT:  (ref.null any)
+  ;; CHECK-NEXT:  (ref.null none)
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $if-arms-subtype-fold (type $none_=>_anyref) (result anyref)
-  ;; NOMNL-NEXT:  (ref.null any)
+  ;; NOMNL-NEXT:  (ref.null none)
   ;; NOMNL-NEXT: )
   (func $if-arms-subtype-fold (result anyref)
     (if (result anyref)
       (i32.const 0)
-      (ref.null extern)
-      (ref.null extern)
+      (ref.null eq)
+      (ref.null eq)
     )
   )
   ;; 2. if its `ifTrue` and `ifFalse` arms are not identical (cannot fold)
-  ;; CHECK:      (func $if-arms-subtype-nofold (result anyref)
+  ;; CHECK:      (func $if-arms-subtype-nofold (param $i31ref i31ref) (result anyref)
   ;; CHECK-NEXT:  (if (result anyref)
   ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (ref.null any)
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:   (local.get $i31ref)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $if-arms-subtype-nofold (type $none_=>_anyref) (result anyref)
+  ;; NOMNL:      (func $if-arms-subtype-nofold (type $i31ref_=>_anyref) (param $i31ref i31ref) (result anyref)
   ;; NOMNL-NEXT:  (if (result anyref)
   ;; NOMNL-NEXT:   (i32.const 0)
-  ;; NOMNL-NEXT:   (ref.null any)
-  ;; NOMNL-NEXT:   (ref.null func)
+  ;; NOMNL-NEXT:   (ref.null none)
+  ;; NOMNL-NEXT:   (local.get $i31ref)
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
-  (func $if-arms-subtype-nofold (result anyref)
+  (func $if-arms-subtype-nofold (param $i31ref i31ref) (result anyref)
     (if (result anyref)
       (i32.const 0)
-      (ref.null extern)
-      (ref.null func)
+      (ref.null data)
+      (local.get $i31ref)
     )
   )
 
@@ -154,7 +161,7 @@
 
   ;; ref.is_null is not needed on a non-nullable value, and if something is
   ;; a func we don't need that either etc. if we know the result
-  ;; CHECK:      (func $unneeded_is (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref)
+  ;; CHECK:      (func $unneeded_is (param $struct (ref $struct)) (param $func (ref func)) (param $data (ref data)) (param $i31 (ref i31))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
@@ -188,7 +195,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_is (type $ref|$struct|_ref|func|_dataref_i31ref_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref)
+  ;; NOMNL:      (func $unneeded_is (type $ref|$struct|_ref|func|_ref|data|_ref|i31|_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $data (ref data)) (param $i31 (ref i31))
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
@@ -243,7 +250,7 @@
 
   ;; similar to $unneeded_is, but the values are nullable. we can at least
   ;; leave just the null check.
-  ;; CHECK:      (func $unneeded_is_null (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; CHECK:      (func $unneeded_is_null (param $struct (ref null $struct)) (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.is_null
   ;; CHECK-NEXT:    (local.get $struct)
@@ -271,7 +278,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_is_null (type $ref?|$struct|_funcref_ref?|data|_ref?|i31|_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; NOMNL:      (func $unneeded_is_null (type $ref?|$struct|_funcref_dataref_i31ref_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.is_null
   ;; NOMNL-NEXT:    (local.get $struct)
@@ -320,7 +327,7 @@
 
   ;; similar to $unneeded_is, but the values are of mixed kind (is_func of
   ;; data, etc.). regardless of nullability the result here is always 0.
-  ;; CHECK:      (func $unneeded_is_bad_kinds (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; CHECK:      (func $unneeded_is_bad_kinds (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
@@ -376,7 +383,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_is_bad_kinds (type $funcref_ref?|data|_ref?|i31|_=>_none) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; NOMNL:      (func $unneeded_is_bad_kinds (type $funcref_dataref_i31ref_=>_none) (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result i32)
   ;; NOMNL-NEXT:    (drop
@@ -459,7 +466,7 @@
 
   ;; ref.as_non_null is not needed on a non-nullable value, and if something is
   ;; a func we don't need that either etc., and can just return the value.
-  ;; CHECK:      (func $unneeded_as (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref)
+  ;; CHECK:      (func $unneeded_as (param $struct (ref $struct)) (param $func (ref func)) (param $data (ref data)) (param $i31 (ref i31))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:  )
@@ -473,7 +480,7 @@
   ;; CHECK-NEXT:   (local.get $i31)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_as (type $ref|$struct|_ref|func|_dataref_i31ref_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref)
+  ;; NOMNL:      (func $unneeded_as (type $ref|$struct|_ref|func|_ref|data|_ref|i31|_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $data (ref data)) (param $i31 (ref i31))
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (local.get $struct)
   ;; NOMNL-NEXT:  )
@@ -508,7 +515,7 @@
 
   ;; similar to $unneeded_as, but the values are nullable. we can turn the
   ;; more specific things into ref.as_non_null.
-  ;; CHECK:      (func $unneeded_as_null (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; CHECK:      (func $unneeded_as_null (param $struct (ref null $struct)) (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.as_non_null
   ;; CHECK-NEXT:    (local.get $struct)
@@ -530,7 +537,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_as_null (type $ref?|$struct|_funcref_ref?|data|_ref?|i31|_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; NOMNL:      (func $unneeded_as_null (type $ref?|$struct|_funcref_dataref_i31ref_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.as_non_null
   ;; NOMNL-NEXT:    (local.get $struct)
@@ -573,7 +580,7 @@
 
   ;; similar to $unneeded_as, but the values are of mixed kind (as_func of
   ;; data, etc.), so we know we will trap
-  ;; CHECK:      (func $unneeded_as_bad_kinds (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; CHECK:      (func $unneeded_as_bad_kinds (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block (result (ref func))
   ;; CHECK-NEXT:    (drop
@@ -583,7 +590,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result dataref)
+  ;; CHECK-NEXT:   (block (result (ref data))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $i31)
   ;; CHECK-NEXT:    )
@@ -591,7 +598,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i31ref)
+  ;; CHECK-NEXT:   (block (result (ref i31))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $func)
   ;; CHECK-NEXT:    )
@@ -607,7 +614,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result dataref)
+  ;; CHECK-NEXT:   (block (result (ref data))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $i31)
   ;; CHECK-NEXT:    )
@@ -615,7 +622,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i31ref)
+  ;; CHECK-NEXT:   (block (result (ref i31))
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $func)
   ;; CHECK-NEXT:    )
@@ -623,7 +630,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_as_bad_kinds (type $funcref_ref?|data|_ref?|i31|_=>_none) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31))
+  ;; NOMNL:      (func $unneeded_as_bad_kinds (type $funcref_dataref_i31ref_=>_none) (param $func funcref) (param $data dataref) (param $i31 i31ref)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (block (result (ref func))
   ;; NOMNL-NEXT:    (drop
@@ -633,7 +640,7 @@
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result dataref)
+  ;; NOMNL-NEXT:   (block (result (ref data))
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $i31)
   ;; NOMNL-NEXT:    )
@@ -641,7 +648,7 @@
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result i31ref)
+  ;; NOMNL-NEXT:   (block (result (ref i31))
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $func)
   ;; NOMNL-NEXT:    )
@@ -657,7 +664,7 @@
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result dataref)
+  ;; NOMNL-NEXT:   (block (result (ref data))
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $i31)
   ;; NOMNL-NEXT:    )
@@ -665,7 +672,7 @@
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result i31ref)
+  ;; NOMNL-NEXT:   (block (result (ref i31))
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $func)
   ;; NOMNL-NEXT:    )
@@ -710,7 +717,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $unneeded_unreachability (type $none_=>_none)
+  ;; NOMNL:      (func $unneeded_unreachability (type $void)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.is_func
   ;; NOMNL-NEXT:    (unreachable)
@@ -732,7 +739,7 @@
     )
   )
 
-  ;; CHECK:      (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array))
+  ;; CHECK:      (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.as_non_null
   ;; CHECK-NEXT:    (local.get $x)
@@ -759,12 +766,15 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (array.len $array
+  ;; CHECK-NEXT:   (array.len
   ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call_ref $void
+  ;; CHECK-NEXT:   (local.get $f)
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $redundant-non-null-casts (type $ref?|$struct|_ref?|$array|_=>_none) (param $x (ref null $struct)) (param $y (ref null $array))
+  ;; NOMNL:      (func $redundant-non-null-casts (type $ref?|$struct|_ref?|$array|_ref?|$void|_=>_none) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void))
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.as_non_null
   ;; NOMNL-NEXT:    (local.get $x)
@@ -791,12 +801,15 @@
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (array.len $array
+  ;; NOMNL-NEXT:   (array.len
   ;; NOMNL-NEXT:    (local.get $y)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call_ref $void
+  ;; NOMNL-NEXT:   (local.get $f)
+  ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
-  (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array))
+  (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void))
     (drop
       (ref.as_non_null
         (ref.as_non_null
@@ -841,6 +854,11 @@
         )
       )
     )
+    (call_ref $void
+      (ref.as_non_null
+        (local.get $f)
+      )
+    )
   )
 
   ;; CHECK:      (func $get-eqref (result eqref)
@@ -947,7 +965,7 @@
   ;; CHECK:      (func $nothing
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $nothing (type $none_=>_none)
+  ;; NOMNL:      (func $nothing (type $void)
   ;; NOMNL-NEXT:  (nop)
   ;; NOMNL-NEXT: )
   (func $nothing)
@@ -968,17 +986,23 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.eq
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct
-  ;; CHECK-NEXT:     (rtt.canon $struct)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (struct.new_default_with_rtt $struct
-  ;; CHECK-NEXT:     (rtt.canon $struct)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (struct.new_default $struct)
+  ;; CHECK-NEXT:    (struct.new_default $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.tee $x
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $ref-eq-corner-cases (type $eqref_=>_none) (param $x eqref)
   ;; NOMNL-NEXT:  (drop
@@ -995,17 +1019,23 @@
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.eq
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct
-  ;; NOMNL-NEXT:     (rtt.canon $struct)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (struct.new_default_with_rtt $struct
-  ;; NOMNL-NEXT:     (rtt.canon $struct)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (struct.new_default $struct)
+  ;; NOMNL-NEXT:    (struct.new_default $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (i32.const 1)
   ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (block (result i32)
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (local.tee $x
+  ;; NOMNL-NEXT:      (local.get $x)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (i32.const 1)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $ref-eq-corner-cases (param $x eqref)
     ;; side effects prevent optimization
@@ -1024,12 +1054,8 @@
     ;; allocation prevents optimization
     (drop
       (ref.eq
-        (struct.new_default_with_rtt $struct
-          (rtt.canon $struct)
-        )
-        (struct.new_default_with_rtt $struct
-          (rtt.canon $struct)
-        )
+        (struct.new_default $struct)
+        (struct.new_default $struct)
       )
     )
     ;; but irrelevant allocations do not prevent optimization
@@ -1038,17 +1064,13 @@
         (block (result eqref)
           ;; an allocation that does not trouble us
           (drop
-            (struct.new_default_with_rtt $struct
-              (rtt.canon $struct)
-            )
+            (struct.new_default $struct)
           )
           (local.get $x)
         )
         (block (result eqref)
           (drop
-            (struct.new_default_with_rtt $struct
-              (rtt.canon $struct)
-            )
+            (struct.new_default $struct)
           )
           ;; add a nop to make the two inputs to ref.eq not structurally equal,
           ;; but in a way that does not matter (since only the value falling
@@ -1058,15 +1080,23 @@
         )
       )
     )
+    ;; a tee does not prevent optimization, as we can fold the tee and the get.
+    (drop
+      (ref.eq
+        (local.tee $x
+          (local.get $x)
+        )
+        (local.get $x)
+      )
+    )
   )
 
   ;; CHECK:      (func $ref-eq-ref-cast (param $x eqref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.eq
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (ref.cast
+  ;; CHECK-NEXT:    (ref.cast_static $struct
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -1075,9 +1105,8 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.eq
   ;; NOMNL-NEXT:    (local.get $x)
-  ;; NOMNL-NEXT:    (ref.cast
+  ;; NOMNL-NEXT:    (ref.cast_static $struct
   ;; NOMNL-NEXT:     (local.get $x)
-  ;; NOMNL-NEXT:     (rtt.canon $struct)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
@@ -1088,9 +1117,8 @@
     (drop
       (ref.eq
         (local.get $x)
-        (ref.cast
+        (ref.cast_static $struct
           (local.get $x)
-          (rtt.canon $struct)
         )
       )
     )
@@ -1099,132 +1127,117 @@
   ;; CHECK:      (func $flip-cast-of-as-non-null (param $x anyref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (ref.cast
+  ;; CHECK-NEXT:    (ref.cast_static $struct
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get_u $struct $i8
-  ;; CHECK-NEXT:    (ref.cast
+  ;; CHECK-NEXT:    (ref.cast_static $struct
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $struct
   ;; CHECK-NEXT:    (ref.as_func
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $struct
   ;; CHECK-NEXT:    (ref.as_data
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $struct
   ;; CHECK-NEXT:    (ref.as_i31
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $flip-cast-of-as-non-null (type $anyref_=>_none) (param $x anyref)
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.as_non_null
-  ;; NOMNL-NEXT:    (ref.cast
+  ;; NOMNL-NEXT:    (ref.cast_static $struct
   ;; NOMNL-NEXT:     (local.get $x)
-  ;; NOMNL-NEXT:     (rtt.canon $struct)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (struct.get_u $struct $i8
-  ;; NOMNL-NEXT:    (ref.cast
+  ;; NOMNL-NEXT:    (ref.cast_static $struct
   ;; NOMNL-NEXT:     (local.get $x)
-  ;; NOMNL-NEXT:     (rtt.canon $struct)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
   ;; NOMNL-NEXT:    (ref.as_func
   ;; NOMNL-NEXT:     (local.get $x)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
   ;; NOMNL-NEXT:    (ref.as_data
   ;; NOMNL-NEXT:     (local.get $x)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
   ;; NOMNL-NEXT:    (ref.as_i31
   ;; NOMNL-NEXT:     (local.get $x)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $flip-cast-of-as-non-null (param $x anyref)
     (drop
-      (ref.cast
+      (ref.cast_static $struct
         ;; this can be moved through the ref.cast outward.
         (ref.as_non_null
           (local.get $x)
         )
-        (rtt.canon $struct)
       )
     )
     (drop
       ;; an example of how this helps: the struct.get will trap on null anyhow
       (struct.get_u $struct 0
-        (ref.cast
+        (ref.cast_static $struct
           ;; this can be moved through the ref.cast outward.
           (ref.as_non_null
             (local.get $x)
           )
-          (rtt.canon $struct)
         )
       )
     )
     ;; other ref.as* operations are ignored for now
     (drop
-      (ref.cast
+      (ref.cast_static $struct
         (ref.as_func
           (local.get $x)
         )
-        (rtt.canon $struct)
       )
     )
     (drop
-      (ref.cast
+      (ref.cast_static $struct
         (ref.as_data
           (local.get $x)
         )
-        (rtt.canon $struct)
       )
     )
     (drop
-      (ref.cast
+      (ref.cast_static $struct
         (ref.as_i31
           (local.get $x)
         )
-        (rtt.canon $struct)
       )
     )
   )
@@ -1261,7 +1274,7 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (local.tee $x
   ;; CHECK-NEXT:    (ref.as_non_null
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -1270,7 +1283,7 @@
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (local.tee $x
   ;; NOMNL-NEXT:    (ref.as_non_null
-  ;; NOMNL-NEXT:     (ref.null any)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
@@ -1434,48 +1447,46 @@
 
   ;; CHECK:      (func $ref-cast-squared (param $x eqref)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $struct
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $ref-cast-squared (type $eqref_=>_none) (param $x eqref)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
   ;; NOMNL-NEXT:    (local.get $x)
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $ref-cast-squared (param $x eqref)
     ;; Identical ref.casts can be folded together.
     (drop
-      (ref.cast
-        (ref.cast
+      (ref.cast_static $struct
+        (ref.cast_static $struct
           (local.get $x)
-          (rtt.canon $struct)
         )
-        (rtt.canon $struct)
       )
     )
   )
   ;; CHECK:      (func $ref-cast-squared-fallthrough (param $x eqref)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.tee $x
-  ;; CHECK-NEXT:    (ref.cast
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (rtt.canon $struct)
+  ;; CHECK-NEXT:   (ref.cast_static $struct
+  ;; CHECK-NEXT:    (local.tee $x
+  ;; CHECK-NEXT:     (ref.cast_static $struct
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $ref-cast-squared-fallthrough (type $eqref_=>_none) (param $x eqref)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (local.tee $x
-  ;; NOMNL-NEXT:    (ref.cast
-  ;; NOMNL-NEXT:     (local.get $x)
-  ;; NOMNL-NEXT:     (rtt.canon $struct)
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
+  ;; NOMNL-NEXT:    (local.tee $x
+  ;; NOMNL-NEXT:     (ref.cast_static $struct
+  ;; NOMNL-NEXT:      (local.get $x)
+  ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
@@ -1483,128 +1494,68 @@
   (func $ref-cast-squared-fallthrough (param $x eqref)
     ;; A fallthrough in the middle does not prevent this optimization.
     (drop
-      (ref.cast
+      (ref.cast_static $struct
         (local.tee $x
-          (ref.cast
+          (ref.cast_static $struct
             (local.get $x)
-            (rtt.canon $struct)
           )
         )
-        (rtt.canon $struct)
       )
     )
   )
   ;; CHECK:      (func $ref-cast-cubed (param $x eqref)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $struct
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $ref-cast-cubed (type $eqref_=>_none) (param $x eqref)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
   ;; NOMNL-NEXT:    (local.get $x)
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $ref-cast-cubed (param $x eqref)
     ;; Three and more also work.
     (drop
-      (ref.cast
-        (ref.cast
-          (ref.cast
+      (ref.cast_static $struct
+        (ref.cast_static $struct
+          (ref.cast_static $struct
             (local.get $x)
-            (rtt.canon $struct)
           )
-          (rtt.canon $struct)
         )
-        (rtt.canon $struct)
       )
     )
   )
   ;; CHECK:      (func $ref-cast-squared-different (param $x eqref)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
-  ;; CHECK-NEXT:    (ref.cast
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (rtt.canon $empty)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
+  ;; CHECK-NEXT:   (ref.cast_static $struct
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $ref-cast-squared-different (type $eqref_=>_none) (param $x eqref)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
-  ;; NOMNL-NEXT:    (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
+  ;; NOMNL-NEXT:    (ref.cast_static $empty
   ;; NOMNL-NEXT:     (local.get $x)
-  ;; NOMNL-NEXT:     (rtt.canon $empty)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $ref-cast-squared-different (param $x eqref)
     ;; Different casts cannot be folded.
     (drop
-      (ref.cast
-        (ref.cast
-          (local.get $x)
-          (rtt.canon $empty)
-        )
-        (rtt.canon $struct)
-      )
-    )
-  )
-  ;; CHECK:      (func $ref-cast-squared-effects (param $x eqref)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
-  ;; CHECK-NEXT:    (ref.cast
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (call $get-rtt)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (call $get-rtt)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $ref-cast-squared-effects (type $eqref_=>_none) (param $x eqref)
-  ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
-  ;; NOMNL-NEXT:    (ref.cast
-  ;; NOMNL-NEXT:     (local.get $x)
-  ;; NOMNL-NEXT:     (call $get-rtt)
-  ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (call $get-rtt)
-  ;; NOMNL-NEXT:   )
-  ;; NOMNL-NEXT:  )
-  ;; NOMNL-NEXT: )
-  (func $ref-cast-squared-effects (param $x eqref)
-    ;; The rtts are equal but have side effects, preventing optimization.
-    (drop
-      (ref.cast
-        (ref.cast
+      (ref.cast_static $struct
+        (ref.cast_static $empty
           (local.get $x)
-          (call $get-rtt)
         )
-        (call $get-rtt)
       )
     )
   )
 
-  ;; Helper function for above.
-  ;; CHECK:      (func $get-rtt (result (rtt $empty))
-  ;; CHECK-NEXT:  (unreachable)
-  ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $get-rtt (type $none_=>_rtt_$empty) (result (rtt $empty))
-  ;; NOMNL-NEXT:  (unreachable)
-  ;; NOMNL-NEXT: )
-  (func $get-rtt (result (rtt $empty))
-    (unreachable)
-  )
-
   ;; CHECK:      (func $ref-eq-null (param $x eqref)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.is_null
@@ -1661,29 +1612,317 @@
     )
   )
 
-  ;; CHECK:      (func $hoist-LUB-danger (param $x i32) (result i32)
+  ;; CHECK:      (func $ref-eq-possible (param $x eqref) (param $y eqref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (ref.cast_static $struct
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.cast_static $array
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $ref-eq-possible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (ref.eq
+  ;; NOMNL-NEXT:    (ref.cast_static $struct
+  ;; NOMNL-NEXT:     (local.get $x)
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (ref.cast_static $array
+  ;; NOMNL-NEXT:     (local.get $y)
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $ref-eq-possible (param $x eqref) (param $y eqref)
+    ;; These casts are to types that are incompatible. However, it is possible
+    ;; they are both null, so we cannot optimize here.
+    (drop
+      (ref.eq
+        (ref.cast_static $struct
+          (local.get $x)
+        )
+        (ref.cast_static $array
+          (local.get $y)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref-eq-impossible (param $x eqref) (param $y eqref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (ref.cast_static $struct
+  ;; CHECK-NEXT:       (local.get $x)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.cast_static $array
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.cast_static $struct
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (ref.cast_static $array
+  ;; CHECK-NEXT:       (local.get $y)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (ref.cast_static $struct
+  ;; CHECK-NEXT:       (local.get $x)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (ref.cast_static $array
+  ;; CHECK-NEXT:       (local.get $y)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $ref-eq-impossible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (block (result i32)
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (ref.as_non_null
+  ;; NOMNL-NEXT:      (ref.cast_static $struct
+  ;; NOMNL-NEXT:       (local.get $x)
+  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (ref.cast_static $array
+  ;; NOMNL-NEXT:      (local.get $y)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (i32.const 0)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (block (result i32)
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (ref.cast_static $struct
+  ;; NOMNL-NEXT:      (local.get $x)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (ref.as_non_null
+  ;; NOMNL-NEXT:      (ref.cast_static $array
+  ;; NOMNL-NEXT:       (local.get $y)
+  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (i32.const 0)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (block (result i32)
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (ref.as_non_null
+  ;; NOMNL-NEXT:      (ref.cast_static $struct
+  ;; NOMNL-NEXT:       (local.get $x)
+  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (drop
+  ;; NOMNL-NEXT:     (ref.as_non_null
+  ;; NOMNL-NEXT:      (ref.cast_static $array
+  ;; NOMNL-NEXT:       (local.get $y)
+  ;; NOMNL-NEXT:      )
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (i32.const 0)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $ref-eq-impossible (param $x eqref) (param $y eqref)
+    ;; As above but cast one of them to non-null, which proves they cannot be
+    ;; equal, and the result must be 0.
+    (drop
+      (ref.eq
+        (ref.cast_static $struct
+          (ref.as_non_null
+            (local.get $x)
+          )
+        )
+        (ref.cast_static $array
+          (local.get $y)
+        )
+      )
+    )
+    ;; As above but the cast is on the other one.
+    (drop
+      (ref.eq
+        (ref.cast_static $struct
+          (local.get $x)
+        )
+        (ref.cast_static $array
+          (ref.as_non_null
+            (local.get $y)
+          )
+        )
+      )
+    )
+    ;; As above but the cast is both.
+    (drop
+      (ref.eq
+        (ref.cast_static $struct
+          (ref.as_non_null
+            (local.get $x)
+          )
+        )
+        (ref.cast_static $array
+          (ref.as_non_null
+            (local.get $y)
+          )
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $ref-eq-possible-b (param $x eqref) (param $y eqref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (ref.cast_static $A
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (ref.cast_static $B
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (ref.cast_static $B
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (ref.cast_static $A
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $ref-eq-possible-b (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (ref.eq
+  ;; NOMNL-NEXT:    (ref.as_non_null
+  ;; NOMNL-NEXT:     (ref.cast_static $A
+  ;; NOMNL-NEXT:      (local.get $x)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (ref.as_non_null
+  ;; NOMNL-NEXT:     (ref.cast_static $B
+  ;; NOMNL-NEXT:      (local.get $y)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (ref.eq
+  ;; NOMNL-NEXT:    (ref.as_non_null
+  ;; NOMNL-NEXT:     (ref.cast_static $B
+  ;; NOMNL-NEXT:      (local.get $x)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (ref.as_non_null
+  ;; NOMNL-NEXT:     (ref.cast_static $A
+  ;; NOMNL-NEXT:      (local.get $y)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $ref-eq-possible-b (param $x eqref) (param $y eqref)
+    ;; As above but the casts are to things that are compatible, since B is a
+    ;; subtype of A, so we cannot optimize.
+    (drop
+      (ref.eq
+        (ref.cast_static $A
+          (ref.as_non_null
+            (local.get $x)
+          )
+        )
+        (ref.cast_static $B
+          (ref.as_non_null
+            (local.get $y)
+          )
+        )
+      )
+    )
+    ;; As above but flipped.
+    (drop
+      (ref.eq
+        (ref.cast_static $B
+          (ref.as_non_null
+            (local.get $x)
+          )
+        )
+        (ref.cast_static $A
+          (ref.as_non_null
+            (local.get $y)
+          )
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $hoist-LUB-danger (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32)
   ;; CHECK-NEXT:  (if (result i32)
   ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:   (struct.get $B 1
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (local.get $b)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (struct.get $C 1
-  ;; CHECK-NEXT:    (ref.null $C)
+  ;; CHECK-NEXT:    (local.get $c)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $hoist-LUB-danger (type $i32_=>_i32) (param $x i32) (result i32)
+  ;; NOMNL:      (func $hoist-LUB-danger (type $i32_ref|$B|_ref|$C|_=>_i32) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32)
   ;; NOMNL-NEXT:  (if (result i32)
   ;; NOMNL-NEXT:   (local.get $x)
   ;; NOMNL-NEXT:   (struct.get $B 1
-  ;; NOMNL-NEXT:    (ref.null $B)
+  ;; NOMNL-NEXT:    (local.get $b)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (struct.get $C 1
-  ;; NOMNL-NEXT:    (ref.null $C)
+  ;; NOMNL-NEXT:    (local.get $c)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
-  (func $hoist-LUB-danger (param $x i32) (result i32)
+  (func $hoist-LUB-danger (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32)
     ;; In nominal typing, if we hoist the struct.get out of the if, then the if
     ;; will have a new type, $A, but $A does not have field "1" which would be an
     ;; error. We disallow subtyping for this reason.
@@ -1695,10 +1934,10 @@
     (if (result i32)
       (local.get $x)
       (struct.get $B 1
-        (ref.null $B)
+        (local.get $b)
       )
       (struct.get $C 1
-        (ref.null $C)
+        (local.get $c)
       )
     )
   )
@@ -1709,9 +1948,6 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $struct)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $array)
-  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -1722,119 +1958,98 @@
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $struct)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $array)
-  ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (unreachable)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $incompatible-cast-of-non-null (param $struct (ref $struct))
     (drop
-      (ref.cast
+      (ref.cast_static $array
         (local.get $struct)
-        (rtt.canon $array)
       )
     )
   )
 
   ;; CHECK:      (func $incompatible-cast-of-null
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $array))
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $struct)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $array)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $array)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.as_non_null
-  ;; CHECK-NEXT:    (block (result (ref null $array))
+  ;; CHECK-NEXT:    (block (result nullref)
   ;; CHECK-NEXT:     (drop
   ;; CHECK-NEXT:      (ref.as_non_null
-  ;; CHECK-NEXT:       (ref.null $struct)
+  ;; CHECK-NEXT:       (ref.null none)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (drop
-  ;; CHECK-NEXT:      (rtt.canon $array)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (ref.null $array)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $incompatible-cast-of-null (type $none_=>_none)
+  ;; NOMNL:      (func $incompatible-cast-of-null (type $void)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $array))
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $struct)
-  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $array)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $array)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
   ;; NOMNL-NEXT:   (ref.as_non_null
-  ;; NOMNL-NEXT:    (block (result (ref null $array))
+  ;; NOMNL-NEXT:    (block (result nullref)
   ;; NOMNL-NEXT:     (drop
   ;; NOMNL-NEXT:      (ref.as_non_null
-  ;; NOMNL-NEXT:       (ref.null $struct)
+  ;; NOMNL-NEXT:       (ref.null none)
   ;; NOMNL-NEXT:      )
   ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (drop
-  ;; NOMNL-NEXT:      (rtt.canon $array)
-  ;; NOMNL-NEXT:     )
-  ;; NOMNL-NEXT:     (ref.null $array)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $incompatible-cast-of-null
     (drop
-      (ref.cast
+      (ref.cast_static $array
         (ref.null $struct)
-        (rtt.canon $array)
       )
     )
     (drop
-      (ref.cast
+      (ref.cast_static $array
         ;; The fallthrough is null, but the node's child's type is non-nullable,
         ;; so we must add a ref.as_non_null on the outside to keep the type
         ;; identical.
         (ref.as_non_null
           (ref.null $struct)
         )
-        (rtt.canon $array)
       )
     )
   )
 
   ;; CHECK:      (func $incompatible-cast-of-unknown (param $struct (ref null $struct))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
+  ;; CHECK-NEXT:   (ref.cast_static $array
   ;; CHECK-NEXT:    (local.get $struct)
-  ;; CHECK-NEXT:    (rtt.canon $array)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $incompatible-cast-of-unknown (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
+  ;; NOMNL-NEXT:   (ref.cast_static $array
   ;; NOMNL-NEXT:    (local.get $struct)
-  ;; NOMNL-NEXT:    (rtt.canon $array)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $incompatible-cast-of-unknown (param $struct (ref null $struct))
     (drop
-      (ref.cast
+      (ref.cast_static $array
         (local.get $struct)
-        (rtt.canon $array)
       )
     )
   )
@@ -1845,9 +2060,6 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $struct)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (rtt.canon $array)
-  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -1858,9 +2070,6 @@
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.get $struct)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (rtt.canon $array)
-  ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:    (i32.const 0)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
@@ -1868,70 +2077,61 @@
   (func $incompatible-test (param $struct (ref null $struct))
     (drop
       ;; This test will definitely fail, so we can turn it into 0.
-      (ref.test
+      (ref.test_static $array
         (local.get $struct)
-        (rtt.canon $array)
       )
     )
   )
 
   ;; CHECK:      (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.test
+  ;; CHECK-NEXT:   (ref.test_static $B
   ;; CHECK-NEXT:    (local.get $A)
-  ;; CHECK-NEXT:    (rtt.canon $B)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.test
+  ;; CHECK-NEXT:   (ref.test_static $A
   ;; CHECK-NEXT:    (local.get $B)
-  ;; CHECK-NEXT:    (rtt.canon $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $subtype-compatible (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.test
+  ;; NOMNL-NEXT:   (ref.test_static $B
   ;; NOMNL-NEXT:    (local.get $A)
-  ;; NOMNL-NEXT:    (rtt.canon $B)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.test
+  ;; NOMNL-NEXT:   (ref.test_static $A
   ;; NOMNL-NEXT:    (local.get $B)
-  ;; NOMNL-NEXT:    (rtt.canon $A)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B))
     (drop
       ;; B is a subtype of A, so this can work.
-      (ref.test
+      (ref.test_static $B
         (local.get $A)
-        (rtt.canon $B)
       )
     )
     (drop
       ;; The other direction works too.
-      (ref.test
+      (ref.test_static $A
         (local.get $B)
-        (rtt.canon $A)
       )
     )
   )
   ;; CHECK:      (func $ref.test-unreachable (param $A (ref null $A))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.test
+  ;; CHECK-NEXT:   (ref.test_static $A
   ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:    (rtt.canon $A)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $ref.test-unreachable (type $ref?|$A|_=>_none) (param $A (ref null $A))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.test
+  ;; NOMNL-NEXT:   (ref.test_static $A
   ;; NOMNL-NEXT:    (unreachable)
-  ;; NOMNL-NEXT:    (rtt.canon $A)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -1939,42 +2139,39 @@
     (drop
       ;; We should ignore unreachable ref.tests and not try to compare their
       ;; HeapTypes.
-      (ref.test
+      (ref.test_static $A
         (unreachable)
-        (rtt.canon $A)
       )
     )
   )
 
   ;; CHECK:      (func $consecutive-opts-with-unreachable (param $func funcref)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (ref.cast
-  ;; CHECK-NEXT:    (block (result dataref)
+  ;; CHECK-NEXT:   (ref.cast_static $struct
+  ;; CHECK-NEXT:    (block (result (ref data))
   ;; CHECK-NEXT:     (drop
   ;; CHECK-NEXT:      (local.get $func)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (rtt.canon $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; NOMNL:      (func $consecutive-opts-with-unreachable (type $funcref_=>_none) (param $func funcref)
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (ref.cast
-  ;; NOMNL-NEXT:    (block (result dataref)
+  ;; NOMNL-NEXT:   (ref.cast_static $struct
+  ;; NOMNL-NEXT:    (block (result (ref data))
   ;; NOMNL-NEXT:     (drop
   ;; NOMNL-NEXT:      (local.get $func)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:     (unreachable)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (rtt.canon $struct)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
   (func $consecutive-opts-with-unreachable (param $func funcref)
    (drop
-     (ref.cast
+     (ref.cast_static $struct
        ;; Casting a funcref to data will definitely fail, so this will be
        ;; replaced with an unreachable. But it should be enclosed in a block of
        ;; the previous type, so that the outside ref.cast is not confused. This
@@ -1987,7 +2184,6 @@
        (ref.as_data
          (local.get $func)
        )
-       (rtt.canon $struct)
      )
    )
   )
@@ -1995,74 +2191,74 @@
   ;; CHECK:      (func $ref-cast-static-null
   ;; CHECK-NEXT:  (local $a (ref null $A))
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $B)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $B))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null $A)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $B)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result (ref null $A))
+  ;; CHECK-NEXT:   (block (result nullref)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.tee $a
-  ;; CHECK-NEXT:      (ref.null $A)
+  ;; CHECK-NEXT:      (ref.null none)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (ref.null $A)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  ;; NOMNL:      (func $ref-cast-static-null (type $none_=>_none)
+  ;; NOMNL:      (func $ref-cast-static-null (type $void)
   ;; NOMNL-NEXT:  (local $a (ref null $A))
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $B)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $B))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (drop
-  ;; NOMNL-NEXT:     (ref.null $A)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $B)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
-  ;; NOMNL-NEXT:   (block (result (ref null $A))
+  ;; NOMNL-NEXT:   (block (result nullref)
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (local.tee $a
-  ;; NOMNL-NEXT:      (ref.null $A)
+  ;; NOMNL-NEXT:      (ref.null none)
   ;; NOMNL-NEXT:     )
   ;; NOMNL-NEXT:    )
-  ;; NOMNL-NEXT:    (ref.null $A)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT: )
@@ -2847,4 +3043,138 @@
       )
     )
   )
+
+  ;; CHECK:      (func $ref-boolean (param $x eqref) (param $y eqref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.is_func
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (ref.test_static $A
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $ref-boolean (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (ref.eq
+  ;; NOMNL-NEXT:    (local.get $x)
+  ;; NOMNL-NEXT:    (local.get $y)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (ref.is_func
+  ;; NOMNL-NEXT:    (local.get $x)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (ref.test_static $A
+  ;; NOMNL-NEXT:    (local.get $x)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $ref-boolean (param $x eqref) (param $y eqref)
+    ;; ref.eq returns a boolean, so &1 on it is not needed.
+    (drop
+      (i32.and
+        (ref.eq
+          (local.get $x)
+          (local.get $y)
+        )
+        (i32.const 1)
+      )
+    )
+    ;; likewise ref.is and ref.test
+    (drop
+      (i32.and
+        (ref.is_func
+          (local.get $x)
+        )
+        (i32.const 1)
+      )
+    )
+    (drop
+      (i32.and
+        (ref.test_static $A
+          (local.get $x)
+        )
+        (i32.const 1)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $impossible (result (ref none))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $impossible (type $none_=>_ref|none|) (result (ref none))
+  ;; NOMNL-NEXT:  (unreachable)
+  ;; NOMNL-NEXT: )
+  (func $impossible (result (ref none))
+    (unreachable)
+  )
+
+  ;; CHECK:      (func $bottom-type-accessors (param $bot (ref none)) (param $null nullref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (call $impossible)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $bottom-type-accessors (type $ref|none|_nullref_=>_none) (param $bot (ref none)) (param $null nullref)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (unreachable)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (unreachable)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (unreachable)
+  ;; NOMNL-NEXT:  (block
+  ;; NOMNL-NEXT:   (drop
+  ;; NOMNL-NEXT:    (call $impossible)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:   (unreachable)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (unreachable)
+  ;; NOMNL-NEXT: )
+  (func $bottom-type-accessors (param $bot (ref none)) (param $null nullref)
+    (drop
+      (struct.get $A 0
+        (local.get $bot)
+      )
+    )
+    (drop
+      (array.get $array
+        (local.get $null)
+        (i32.const 0)
+      )
+    )
+    (struct.set $A 0
+      (ref.null none)
+      (i32.const 42)
+    )
+    (array.set $array
+      (call $impossible)
+      (i32.const 1)
+      (i32.const 2)
+    )
+    (call_ref $void
+      (ref.null nofunc)
+    )
+  )
 )
diff --git a/test/lit/passes/optimize-instructions-ignore-traps.wast b/test/lit/passes/optimize-instructions-ignore-traps.wast
index f76410a..a199f1b 100644
--- a/test/lit/passes/optimize-instructions-ignore-traps.wast
+++ b/test/lit/passes/optimize-instructions-ignore-traps.wast
@@ -5,8 +5,13 @@
 (module
   ;; CHECK:      (type $0 (func (param i32 i32) (result i32)))
   (type $0 (func (param i32 i32) (result i32)))
+  ;; CHECK:      (import "a" "b" (func $get-i32 (result i32)))
+
   ;; CHECK:      (memory $0 0)
   (memory $0 0)
+
+  (import "a" "b" (func $get-i32 (result i32)))
+
   ;; CHECK:      (func $conditionals (param $0 i32) (param $1 i32) (result i32)
   ;; CHECK-NEXT:  (local $2 i32)
   ;; CHECK-NEXT:  (local $3 i32)
@@ -517,15 +522,11 @@
   ;; CHECK-NEXT:    (i32.and
   ;; CHECK-NEXT:     (i32.lt_u
   ;; CHECK-NEXT:      (i32.and
-  ;; CHECK-NEXT:       (i32.shr_s
-  ;; CHECK-NEXT:        (i32.shl
-  ;; CHECK-NEXT:         (i32.sub
-  ;; CHECK-NEXT:          (local.get $1)
-  ;; CHECK-NEXT:          (i32.const 1)
-  ;; CHECK-NEXT:         )
-  ;; CHECK-NEXT:         (i32.const 24)
+  ;; CHECK-NEXT:       (i32.extend8_s
+  ;; CHECK-NEXT:        (i32.sub
+  ;; CHECK-NEXT:         (local.get $1)
+  ;; CHECK-NEXT:         (i32.const 1)
   ;; CHECK-NEXT:        )
-  ;; CHECK-NEXT:        (i32.const 24)
   ;; CHECK-NEXT:       )
   ;; CHECK-NEXT:       (i32.const 255)
   ;; CHECK-NEXT:      )
@@ -588,15 +589,11 @@
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:     (i32.lt_u
   ;; CHECK-NEXT:      (i32.and
-  ;; CHECK-NEXT:       (i32.shr_s
-  ;; CHECK-NEXT:        (i32.shl
-  ;; CHECK-NEXT:         (i32.sub
-  ;; CHECK-NEXT:          (local.get $0)
-  ;; CHECK-NEXT:          (i32.const 1)
-  ;; CHECK-NEXT:         )
-  ;; CHECK-NEXT:         (i32.const 24)
+  ;; CHECK-NEXT:       (i32.extend8_s
+  ;; CHECK-NEXT:        (i32.sub
+  ;; CHECK-NEXT:         (local.get $0)
+  ;; CHECK-NEXT:         (i32.const 1)
   ;; CHECK-NEXT:        )
-  ;; CHECK-NEXT:        (i32.const 24)
   ;; CHECK-NEXT:       )
   ;; CHECK-NEXT:       (i32.const 255)
   ;; CHECK-NEXT:      )
@@ -740,6 +737,19 @@
  ;; CHECK-NEXT:    (local.get $src)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (memory.copy
+ ;; CHECK-NEXT:   (call $get-i32)
+ ;; CHECK-NEXT:   (call $get-i32)
+ ;; CHECK-NEXT:   (local.get $sz)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (block
+ ;; CHECK-NEXT:   (drop
+ ;; CHECK-NEXT:    (call $get-i32)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (drop
+ ;; CHECK-NEXT:    (call $get-i32)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $optimize-bulk-memory-copy (param $dst i32) (param $src i32) (param $sz i32)
   (memory.copy  ;; nop
@@ -753,6 +763,16 @@
     (local.get $src)
     (i32.const 0)
   )
+  (memory.copy  ;; not a nop as the runtime dst/src may differ
+    (call $get-i32)
+    (call $get-i32)
+    (local.get $sz)
+  )
+  (memory.copy  ;; as above, but with a size of 0. the calls must remain.
+    (call $get-i32)
+    (call $get-i32)
+    (i32.const 0)
+  )
  )
 
  ;; CHECK:      (func $optimize-bulk-memory-fill (param $dst i32) (param $val i32) (param $sz i32)
diff --git a/test/lit/passes/optimize-instructions-iit-eh.wast b/test/lit/passes/optimize-instructions-iit-eh.wast
index b297cb7..ee6b787 100644
--- a/test/lit/passes/optimize-instructions-iit-eh.wast
+++ b/test/lit/passes/optimize-instructions-iit-eh.wast
@@ -5,33 +5,17 @@
 (module
   ;; CHECK:      (type $struct.A (struct (field i32)))
   (type $struct.A (struct i32))
-  ;; CHECK:      (global $g-struct.A (rtt $struct.A) (rtt.canon $struct.A))
-
   ;; CHECK:      (tag $e (param (ref null $struct.A)))
   (tag $e (param (ref null $struct.A)))
-  (global $g-struct.A (rtt $struct.A) (rtt.canon $struct.A))
 
   ;; CHECK:      (func $ref-cast-statically-removed
-  ;; CHECK-NEXT:  (local $0 (ref null $struct.A))
-  ;; CHECK-NEXT:  (local $1 (ref null $struct.A))
   ;; CHECK-NEXT:  (try $try
   ;; CHECK-NEXT:   (do
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (catch $e
-  ;; CHECK-NEXT:    (local.set $1
-  ;; CHECK-NEXT:     (pop (ref null $struct.A))
-  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (throw $e
-  ;; CHECK-NEXT:     (block (result (ref null $struct.A))
-  ;; CHECK-NEXT:      (local.set $0
-  ;; CHECK-NEXT:       (local.get $1)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (global.get $g-struct.A)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:      (local.get $0)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (pop (ref null $struct.A))
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -41,15 +25,14 @@
       (do)
       (catch $e
         (throw $e
-          ;; Because --ignore-implicit-traps is given, this ref.cast is assumed
-          ;; not to throw so this ref.cast can be statically removed. But that
-          ;; creates a block around this, making 'pop' nested into it, which is
-          ;; invalid. We fix this up at the end up OptimizeInstruction,
+          ;; Because --ignore-implicit-traps is given, this ref.cast_static is
+          ;; assumed not to throw so this ref.cast can be statically removed.
+          ;; But that creates a block around this, making 'pop' nested into it,
+          ;; which is invalid. We fix this up at the end up OptimizeInstruction,
           ;; assigning the 'pop' to a local at the start of this 'catch' body
           ;; and later using 'local.get' to get it.
-          (ref.cast
+          (ref.cast_static $struct.A
             (pop (ref null $struct.A))
-            (global.get $g-struct.A)
           )
         )
       )
diff --git a/test/lit/passes/optimize-instructions.wast b/test/lit/passes/optimize-instructions-mvp.wast
similarity index 81%
rename from test/lit/passes/optimize-instructions.wast
rename to test/lit/passes/optimize-instructions-mvp.wast
index 70e28cf..8a86e25 100644
--- a/test/lit/passes/optimize-instructions.wast
+++ b/test/lit/passes/optimize-instructions-mvp.wast
@@ -1,10 +1,14 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --optimize-instructions -S -o - | filecheck %s
+;; RUN: wasm-opt %s --optimize-instructions --mvp-features -S -o - | filecheck %s
 
 (module
   (memory 0)
   ;; CHECK:      (type $0 (func (param i32 i64)))
   (type $0 (func (param i32 i64)))
+
+  ;; CHECK:      (import "a" "b" (func $get-f64 (result f64)))
+  (import "a" "b" (func $get-f64 (result f64)))
+
   ;; CHECK:      (func $and-and (param $i1 i32) (result i32)
   ;; CHECK-NEXT:  (i32.and
   ;; CHECK-NEXT:   (local.get $i1)
@@ -148,9 +152,8 @@
     )
   )
   ;; CHECK:      (func $eqz-gt_s (result i32)
-  ;; CHECK-NEXT:  (i32.le_u
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  (i32.eqz
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-gt_s (result i32)
@@ -162,9 +165,8 @@
     )
   )
   ;; CHECK:      (func $eqz-ge_s (result i32)
-  ;; CHECK-NEXT:  (i32.lt_u
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  (i32.eqz
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-ge_s (result i32)
@@ -176,9 +178,8 @@
     )
   )
   ;; CHECK:      (func $eqz-lt_s (result i32)
-  ;; CHECK-NEXT:  (i32.ge_u
+  ;; CHECK-NEXT:  (i32.eqz
   ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-lt_s (result i32)
@@ -190,9 +191,8 @@
     )
   )
   ;; CHECK:      (func $eqz-le_s (result i32)
-  ;; CHECK-NEXT:  (i32.gt_u
+  ;; CHECK-NEXT:  (i32.eqz
   ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-le_s (result i32)
@@ -204,9 +204,8 @@
     )
   )
   ;; CHECK:      (func $eqz-gt_u (result i32)
-  ;; CHECK-NEXT:  (i32.le_u
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  (i32.eqz
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-gt_u (result i32)
@@ -218,9 +217,8 @@
     )
   )
   ;; CHECK:      (func $eqz-ge_u (result i32)
-  ;; CHECK-NEXT:  (i32.lt_u
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  (i32.eqz
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-ge_u (result i32)
@@ -232,9 +230,8 @@
     )
   )
   ;; CHECK:      (func $eqz-lt_u (result i32)
-  ;; CHECK-NEXT:  (i32.ge_u
+  ;; CHECK-NEXT:  (i32.eqz
   ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-lt_u (result i32)
@@ -246,9 +243,8 @@
     )
   )
   ;; CHECK:      (func $eqz-le_u (result i32)
-  ;; CHECK-NEXT:  (i32.gt_u
+  ;; CHECK-NEXT:  (i32.eqz
   ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $eqz-le_u (result i32)
@@ -459,10 +455,7 @@
     )
   )
   ;; CHECK:      (func $eq-zero-lhs (result i32)
-  ;; CHECK-NEXT:  (i32.eq
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (i32.const 100)
-  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
   ;; CHECK-NEXT: )
   (func $eq-zero-lhs (result i32)
     (i32.eq
@@ -493,10 +486,7 @@
     )
   )
   ;; CHECK:      (func $eq-zero-lhs-i64 (result i32)
-  ;; CHECK-NEXT:  (i64.eq
-  ;; CHECK-NEXT:   (i64.const 0)
-  ;; CHECK-NEXT:   (i64.const 100)
-  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
   ;; CHECK-NEXT: )
   (func $eq-zero-lhs-i64 (result i32)
     (i64.eq
@@ -728,6 +718,25 @@
       )
     )
   )
+  ;; CHECK:      (func $select-and-eqz (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (i32.eqz
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-and-eqz (param $x i32) (param $y i32) (result i32)
+    (select
+      (i32.eqz
+        (local.get $x)
+      )
+      (i32.const 0)
+      (i32.eqz
+        (local.get $y)
+      )
+    )
+  )
   ;; CHECK:      (func $select-or-side-effects (param $x i32) (param $y i32) (result i32)
   ;; CHECK-NEXT:  (i32.or
   ;; CHECK-NEXT:   (i32.eq
@@ -870,9 +879,35 @@
       )
     )
   )
+  ;; CHECK:      (func $select-or-negation (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (i32.and
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-or-negation (param $x i32) (param $y i32) (result i32)
+    (select
+      ;; We can turn this select into an and by negating the condition.
+      (i32.const 0)
+      (i32.eq
+        (local.get $y)
+        (i32.const 1337)
+      )
+      (i32.lt_u
+        (local.get $x)
+        (i32.const 20)
+      )
+    )
+  )
   ;; CHECK:      (func $select-or-no-const (param $x i32) (param $y i32) (result i32)
   ;; CHECK-NEXT:  (select
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:   (i32.eq
   ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:    (i32.const 1337)
@@ -885,8 +920,8 @@
   ;; CHECK-NEXT: )
   (func $select-or-no-const (param $x i32) (param $y i32) (result i32)
     (select
-      ;; The wrong const (should be 1).
-      (i32.const 0)
+      ;; The wrong const (should be 0 or 1).
+      (i32.const 2)
       (i32.eq
         (local.get $y)
         (i32.const 1337)
@@ -922,14 +957,97 @@
       )
     )
   )
-  ;; CHECK:      (func $select-and-no-const (param $x i32) (param $y i32) (result i32)
+  ;; CHECK:      (func $select-and-negation (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (i32.or
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 42)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-and-negation (param $x i32) (param $y i32) (result i32)
+    (select
+      (i32.eq
+        (local.get $y)
+        (i32.const 1337)
+      )
+      ;; With a 1 here, we negate the condition.
+      (i32.const 1)
+      (i32.eq
+        (local.get $x)
+        (i32.const 42)
+      )
+    )
+  )
+  ;; CHECK:      (func $select-and-negation-impossible (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (select
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.shr_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 31)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-and-negation-impossible (param $x i32) (param $y i32) (result i32)
+    (select
+      (i32.eq
+        (local.get $y)
+        (i32.const 1337)
+      )
+      ;; With a 1 here, we must negate the condition, but the condition here
+      ;; cannot be negated in a simple way, so skip.
+      (i32.const 1)
+      (i32.shr_u
+        (local.get $x)
+        (i32.const 31)
+      )
+    )
+  )
+  ;; CHECK:      (func $select-and-negation-impossible-float (param $x f64) (param $y i32) (result i32)
   ;; CHECK-NEXT:  (select
   ;; CHECK-NEXT:   (i32.eq
   ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:    (i32.const 1337)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f64.le
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 3.14159)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-and-negation-impossible-float (param $x f64) (param $y i32) (result i32)
+    (select
+      (i32.eq
+        (local.get $y)
+        (i32.const 1337)
+      )
+      ;; With a 1 here, we must negate the condition, but the condition here
+      ;; cannot be negated due to it operating on floats (where NaNs cause
+      ;; difficulties), so we skip.
+      (i32.const 1)
+      (f64.le
+        (local.get $x)
+        (f64.const 3.14159)
+      )
+    )
+  )
+  ;; CHECK:      (func $select-and-no-const (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (select
+  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.const 1337)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (i32.const 42)
   ;; CHECK-NEXT:   )
@@ -941,8 +1059,8 @@
         (local.get $y)
         (i32.const 1337)
       )
-      ;; The wrong constant (should be 0).
-      (i32.const 1)
+      ;; The wrong constant (should be 0 or 1).
+      (i32.const 2)
       (i32.eq
         (local.get $x)
         (i32.const 42)
@@ -1067,32 +1185,32 @@
   (func $store16-and-65534
     (i32.store16 (i32.const 11) (i32.and (i32.const -4) (i32.const 65534)))
   )
-  ;; CHECK:      (func $store8-wrap
+  ;; CHECK:      (func $store8-wrap (param $x i64)
   ;; CHECK-NEXT:  (i64.store8
   ;; CHECK-NEXT:   (i32.const 11)
-  ;; CHECK-NEXT:   (i64.const 1)
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $store8-wrap
-    (i32.store8 (i32.const 11) (i32.wrap_i64 (i64.const 1)))
+  (func $store8-wrap (param $x i64)
+    (i32.store8 (i32.const 11) (i32.wrap_i64 (local.get $x)))
   )
-  ;; CHECK:      (func $store16-wrap
+  ;; CHECK:      (func $store16-wrap (param $x i64)
   ;; CHECK-NEXT:  (i64.store16
   ;; CHECK-NEXT:   (i32.const 11)
-  ;; CHECK-NEXT:   (i64.const 2)
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $store16-wrap
-    (i32.store16 (i32.const 11) (i32.wrap_i64 (i64.const 2)))
+  (func $store16-wrap (param $x i64)
+    (i32.store16 (i32.const 11) (i32.wrap_i64 (local.get $x)))
   )
-  ;; CHECK:      (func $store-wrap
+  ;; CHECK:      (func $store-wrap (param $x i64)
   ;; CHECK-NEXT:  (i64.store32
   ;; CHECK-NEXT:   (i32.const 11)
-  ;; CHECK-NEXT:   (i64.const 3)
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $store-wrap
-    (i32.store (i32.const 11) (i32.wrap_i64 (i64.const 3)))
+  (func $store-wrap (param $x i64)
+    (i32.store (i32.const 11) (i32.wrap_i64 (local.get $x)))
   )
   ;; CHECK:      (func $store8-neg1
   ;; CHECK-NEXT:  (i32.store8
@@ -1253,10 +1371,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_u
-  ;; CHECK-NEXT:    (i32.const 2000)
-  ;; CHECK-NEXT:    (i32.const 3000)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $and-pos1
@@ -1348,7 +1463,7 @@
   ;; CHECK:      (func $canonicalize-block-var (param $x i32)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (i32.const -5)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $x)
@@ -1356,7 +1471,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block0 (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (i32.const -6)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (local.get $x)
@@ -1380,7 +1495,7 @@
   ;; CHECK:      (func $canonicalize-block-loop
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (loop $loop-in (result i32)
@@ -1390,7 +1505,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block2 (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (i32.const 8)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (loop $loop-in1 (result i32)
@@ -1400,7 +1515,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block4 (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (i32.const 10)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (loop $loop-in3 (result i32)
@@ -1411,7 +1526,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block6 (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (call $and-pos1)
   ;; CHECK-NEXT:     (i32.const 12)
   ;; CHECK-NEXT:    )
@@ -1426,7 +1541,7 @@
   ;; CHECK-NEXT:     (call $and-pos1)
   ;; CHECK-NEXT:     (i32.const 13)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (block $block8 (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (call $and-pos1)
   ;; CHECK-NEXT:     (i32.const 14)
   ;; CHECK-NEXT:    )
@@ -1434,7 +1549,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (block $block9 (result i32)
+  ;; CHECK-NEXT:    (block (result i32)
   ;; CHECK-NEXT:     (call $and-pos1)
   ;; CHECK-NEXT:     (i32.const 14)
   ;; CHECK-NEXT:    )
@@ -1640,81 +1755,208 @@
       (i32.const 1)
     ))
   )
-  ;; CHECK:      (func $canonicalize-cmp-const (param $x i32) (param $fx f64)
+  ;; CHECK:      (func $canonicalize-cmp-near-min-max (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.le_s
+  ;; CHECK-NEXT:   (i32.eq
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.ne
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f64.const -1)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.gt
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f64.const -2)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 2147483647)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.le
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f64.const inf)
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 9223372036854775807)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.ge
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f64.const nan:0x8000000000000)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 2147483647)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.ge
-  ;; CHECK-NEXT:    (f64.const 1)
-  ;; CHECK-NEXT:    (f64.const 2)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $canonicalize-cmp-const (param $x i32) (param $fx f64)
-    (drop (i32.gt_s
-      (i32.const 1)
+  (func $canonicalize-cmp-near-min-max (param $x i32) (param $y i64)
+    ;; (signed)x < s_min + 1   ==>   x == s_min
+    (drop (i32.lt_s
       (local.get $x)
+      (i32.const -2147483647)
     ))
-    (drop (i32.gt_u
-      (i32.const 0)
-      (local.get $x)
+    (drop (i64.lt_s
+      (local.get $y)
+      (i64.const -9223372036854775807)
     ))
-    (drop (i32.ne
-      (i32.const -1)
+    ;; (signed)x >= s_min + 1   ==>   x != s_min
+    (drop (i32.ge_s
       (local.get $x)
+      (i32.const -2147483647)
     ))
-    (drop (f64.ne
-      (f64.const -1)
-      (local.get $fx)
+    (drop (i64.ge_s
+      (local.get $y)
+      (i64.const -9223372036854775807)
     ))
-    (drop (f64.lt
-      (f64.const -2)
-      (local.get $fx)
+    ;; (signed)x > s_max - 1   ==>   x == s_max
+    (drop (i32.gt_s
+      (local.get $x)
+      (i32.const 2147483646)
     ))
-    (drop (f64.ge
-      (f64.const inf)
-      (local.get $fx)
+    (drop (i64.gt_s
+      (local.get $y)
+      (i64.const 9223372036854775806)
     ))
-    (drop (f64.le
-      (f64.const nan)
-      (local.get $fx)
+    ;; (signed)x <= s_max - 1   ==>   x != s_max
+    (drop (i32.le_s
+      (local.get $x)
+      (i32.const 2147483646)
+    ))
+    (drop (i64.le_s
+      (local.get $y)
+      (i64.const 9223372036854775806)
+    ))
+    ;; (unsigned)x <= u_max - 1   ==>   x != u_max
+    (drop (i32.le_u
+      (local.get $x)
+      (i32.const -2)
+    ))
+    (drop (i64.le_u
+      (local.get $y)
+      (i64.const -2)
+    ))
+    ;; (unsigned)x > u_max - 1   ==>   x == u_max
+    (drop (i32.gt_u
+      (local.get $x)
+      (i32.const -2)
+    ))
+    (drop (i64.gt_u
+      (local.get $y)
+      (i64.const -2)
+    ))
+  )
+  ;; CHECK:      (func $canonicalize-cmp-const (param $x i32) (param $fx f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.le_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.ne
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f64.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.gt
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f64.const -2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.le
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f64.const inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.ge
+  ;; CHECK-NEXT:    (f64.const 1)
+  ;; CHECK-NEXT:    (f64.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $canonicalize-cmp-const (param $x i32) (param $fx f64)
+    (drop (i32.gt_s
+      (i32.const 1)
+      (local.get $x)
+    ))
+    (drop (i32.gt_u
+      (i32.const 0)
+      (local.get $x)
+    ))
+    (drop (i32.ne
+      (i32.const -1)
+      (local.get $x)
+    ))
+    (drop (f64.ne
+      (f64.const -1)
+      (local.get $fx)
+    ))
+    (drop (f64.lt
+      (f64.const -2)
+      (local.get $fx)
+    ))
+    (drop (f64.ge
+      (f64.const inf)
+      (local.get $fx)
+    ))
+    (drop (f64.le
+      (f64.const nan)
+      (local.get $fx)
     ))
     ;; skip
     (drop (f64.ge
@@ -1774,6 +2016,103 @@
     (drop (i32.add (i32.ctz (local.get $x)) (i32.eqz (local.get $y))))
     (drop (i32.add (i32.eqz (local.get $x)) (i32.ctz (local.get $y))))
   )
+  ;; CHECK:      (func $canonicalize-consts-floats (param $x f32) (param $y f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.add
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (f64.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 3.4000000953674316)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (f64.const 3.4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.min
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 3.4000000953674316)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.min
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (f64.const 3.4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.max
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 3.4000000953674316)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.max
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (f64.const 3.4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.copysign
+  ;; CHECK-NEXT:    (f32.const 1)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.copysign
+  ;; CHECK-NEXT:    (f64.const 1)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $canonicalize-consts-floats (param $x f32) (param $y f64)
+    (drop (f32.sub (local.get $x) (f32.const 1.0)))
+    (drop (f64.sub (local.get $y) (f64.const 1.0)))
+
+    (drop (f32.mul (f32.const 3.4) (local.get $x)))
+    (drop (f64.mul (f64.const 3.4) (local.get $y)))
+
+    (drop (f32.min (f32.const 3.4) (local.get $x)))
+    (drop (f64.min (f64.const 3.4) (local.get $y)))
+
+    (drop (f32.max (f32.const 3.4) (local.get $x)))
+    (drop (f64.max (f64.const 3.4) (local.get $y)))
+
+    (drop (f32.min (f32.const nan) (local.get $x)))
+    (drop (f64.min (f64.const nan) (local.get $y)))
+
+    (drop (f32.max (f32.const nan) (local.get $x)))
+    (drop (f64.max (f64.const nan) (local.get $y)))
+
+    ;; skips
+    (drop (f32.copysign (f32.const 1.0) (local.get $x)))
+    (drop (f64.copysign (f64.const 1.0) (local.get $y)))
+  )
   ;; CHECK:      (func $ne0 (result i32)
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (call $ne0)
@@ -1840,7 +2179,7 @@
   ;; CHECK-NEXT:   (nop)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:    (call $ne0)
   ;; CHECK-NEXT:   )
@@ -2279,12 +2618,9 @@
   )
   ;; CHECK:      (func $eq-sext-unsigned-shr (param $0 i32) (result i32)
   ;; CHECK-NEXT:  (i32.eqz
-  ;; CHECK-NEXT:   (i32.shr_u
-  ;; CHECK-NEXT:    (i32.shl
-  ;; CHECK-NEXT:     (local.get $0)
-  ;; CHECK-NEXT:     (i32.const 24)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 24)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $0)
+  ;; CHECK-NEXT:    (i32.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -2992,14 +3328,9 @@
   )
   ;; CHECK:      (func $sext-24-shr_u-wrap-too-big (result i32)
   ;; CHECK-NEXT:  (i32.shr_s
-  ;; CHECK-NEXT:   (i32.shl
-  ;; CHECK-NEXT:    (i32.shr_u
-  ;; CHECK-NEXT:     (i32.wrap_i64
-  ;; CHECK-NEXT:      (i64.const -1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (i32.const 24)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 24)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:    (i32.const -16777216)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i32.const 24)
   ;; CHECK-NEXT:  )
@@ -3020,9 +3351,7 @@
   )
   ;; CHECK:      (func $sext-24-shr_u-wrap (result i32)
   ;; CHECK-NEXT:  (i32.shr_u
-  ;; CHECK-NEXT:   (i32.wrap_i64
-  ;; CHECK-NEXT:    (i64.const -1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:   (i32.const 25)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -3042,12 +3371,9 @@
   )
   ;; CHECK:      (func $sext-24-shr_u-wrap-extend-too-big (result i32)
   ;; CHECK-NEXT:  (i32.shr_s
-  ;; CHECK-NEXT:   (i32.shl
-  ;; CHECK-NEXT:    (i32.shr_u
-  ;; CHECK-NEXT:     (i32.const -1)
-  ;; CHECK-NEXT:     (i32.const 24)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 24)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:    (i32.const -16777216)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i32.const 24)
   ;; CHECK-NEXT:  )
@@ -5713,16 +6039,37 @@
     (i32.const 255)
    )
   )
-  ;; CHECK:      (func $shifts-square-overflow (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (i32.shr_u
-  ;; CHECK-NEXT:   (i32.shr_u
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 31)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 31)
+  ;; CHECK:      (func $left-shifts-square-overflow (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $left-shifts-square-overflow (param $x i32) (result i32)
+   (i32.shl
+    (i32.shl
+     (local.get $x)
+     (i32.const 31)
+    )
+    (i32.const 1)
+   )
+  )
+  ;; CHECK:      (func $left-shifts-square-overflow-with-side-effect (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (call $ne0)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $left-shifts-square-overflow-with-side-effect (param $x i32) (result i32)
+   (i32.shl
+    (i32.shl
+     (call $ne0) ;; side effect
+     (i32.const 31)
+    )
+    (i32.const 5)
+   )
+  )
+  ;; CHECK:      (func $right-shifts-square-overflow-unsigned (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.const 0)
   ;; CHECK-NEXT: )
-  (func $shifts-square-overflow (param $x i32) (result i32)
+  (func $right-shifts-square-overflow-unsigned (param $x i32) (result i32)
    (i32.shr_u
     (i32.shr_u
      (local.get $x)
@@ -5731,6 +6078,21 @@
     (i32.const 32767) ;; also 31 bits, so two shifts that force the value into nothing for sure
    )
   )
+  ;; CHECK:      (func $shifts-square-overflow-signed (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.shr_s
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 31)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $shifts-square-overflow-signed (param $x i32) (result i32)
+   (i32.shr_s
+    (i32.shr_s
+     (local.get $x)
+     (i32.const 65535) ;; 31 bits effectively
+    )
+    (i32.const 32767) ;; also 31 bits, so two shifts that force the value into nothing for sure
+   )
+  )
   ;; CHECK:      (func $shifts-square-no-overflow-small (param $x i32) (result i32)
   ;; CHECK-NEXT:  (i32.shr_u
   ;; CHECK-NEXT:   (local.get $x)
@@ -5746,16 +6108,22 @@
     (i32.const 4098) ;; 2 bits effectively
    )
   )
-  ;; CHECK:      (func $shifts-square-overflow-64 (param $x i64) (result i64)
-  ;; CHECK-NEXT:  (i64.shr_u
-  ;; CHECK-NEXT:   (i64.shr_u
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i64.const 63)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i64.const 63)
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $left-shifts-square-overflow-unsigned-64 (param $x i64) (result i64)
+  ;; CHECK-NEXT:  (i64.const 0)
+  ;; CHECK-NEXT: )
+  (func $left-shifts-square-overflow-unsigned-64 (param $x i64) (result i64)
+   (i64.shl
+    (i64.shl
+     (local.get $x)
+     (i64.const 2)
+    )
+    (i64.const 63)
+   )
+  )
+  ;; CHECK:      (func $right-shifts-square-overflow-unsigned-64 (param $x i64) (result i64)
+  ;; CHECK-NEXT:  (i64.const 0)
   ;; CHECK-NEXT: )
-  (func $shifts-square-overflow-64 (param $x i64) (result i64)
+  (func $right-shifts-square-overflow-unsigned-64 (param $x i64) (result i64)
    (i64.shr_u
     (i64.shr_u
      (local.get $x)
@@ -5764,6 +6132,21 @@
     (i64.const 64767) ;; also 63 bits, so two shifts that force the value into nothing for sure
    )
   )
+  ;; CHECK:      (func $right-shifts-square-overflow-signed-64 (param $x i64) (result i64)
+  ;; CHECK-NEXT:  (i64.shr_s
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i64.const 63)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $right-shifts-square-overflow-signed-64 (param $x i64) (result i64)
+   (i64.shr_s
+    (i64.shr_s
+     (local.get $x)
+     (i64.const 65535) ;; 63 bits effectively
+    )
+    (i64.const 64767) ;; also 63 bits, so two shifts that force the value into nothing for sure
+   )
+  )
   ;; CHECK:      (func $shifts-square-no-overflow-small-64 (param $x i64) (result i64)
   ;; CHECK-NEXT:  (i64.shr_u
   ;; CHECK-NEXT:   (local.get $x)
@@ -5808,8067 +6191,10407 @@
   ;; CHECK-NEXT: )
   (func $mix-shifts (result i32)
     (i32.shr_s
-     (i32.shl
-      (i32.const 23)
-      (i32.const -61)
-     )
-     (i32.const 168)
-    )
-  )
-  ;; CHECK:      (func $actually-no-shifts (result i32)
-  ;; CHECK-NEXT:  (i32.const 33)
-  ;; CHECK-NEXT: )
-  (func $actually-no-shifts (result i32)
-    (i32.add
       (i32.shl
         (i32.const 23)
-        (i32.const 32) ;; really 0
-      )
-      (i32.const 10)
-    )
-  )
-  ;; CHECK:      (func $less-shifts-than-it-seems (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (i32.const 4800)
-  ;; CHECK-NEXT: )
-  (func $less-shifts-than-it-seems (param $x i32) (result i32)
-    (i32.add
-      (i32.shl
-        (i32.const 200)
-        (i32.const 36) ;; really 4
-      )
-      (i32.shl
-        (i32.const 100)
-        (i32.const 4)
+        (i32.const -61)
       )
+      (i32.const 168)
     )
   )
-  ;; CHECK:      (func $and-popcount32 (result i32)
-  ;; CHECK-NEXT:  (i32.and
-  ;; CHECK-NEXT:   (i32.popcnt
-  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK:      (func $mix-shifts-with-same-const-signed (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -16)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 31)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $and-popcount32 (result i32)
-    (i32.and
-      (i32.popcnt
-        (i32.const -1)
-      )
-      (i32.const 31)
-    )
-  )
-  ;; CHECK:      (func $and-popcount32-big (result i32)
-  ;; CHECK-NEXT:  (i32.popcnt
-  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $and-popcount32-big (result i32)
-    (i32.and
-      (i32.popcnt
-        (i32.const -1)
-      )
-      (i32.const 63)
-    )
-  )
-  ;; CHECK:      (func $and-popcount64 (result i64)
-  ;; CHECK-NEXT:  (i64.and
-  ;; CHECK-NEXT:   (i64.popcnt
-  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i64.const 63)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $and-popcount64 (result i64) ;; these are TODOs
-    (i64.and
-      (i64.popcnt
-        (i64.const -1)
-      )
-      (i64.const 63)
-    )
-  )
-  ;; CHECK:      (func $and-popcount64-big (result i64)
-  ;; CHECK-NEXT:  (i64.and
-  ;; CHECK-NEXT:   (i64.popcnt
-  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shl
+  ;; CHECK-NEXT:    (i32.shr_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 4)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 5)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i64.const 127)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $and-popcount64-big (result i64)
-    (i64.and
-      (i64.popcnt
-        (i64.const -1)
-      )
-      (i64.const 127)
-    )
-  )
-  ;; CHECK:      (func $and-popcount64-bigger (result i64)
-  ;; CHECK-NEXT:  (i64.and
-  ;; CHECK-NEXT:   (i64.popcnt
-  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shl
+  ;; CHECK-NEXT:    (i64.shr_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 63)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 1)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i64.const 255)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $and-popcount64-bigger (result i64)
-    (i64.and
-      (i64.popcnt
-        (i64.const -1)
+  (func $mix-shifts-with-same-const-signed (param $x i32) (param $y i64)
+    (drop
+      (i32.shl
+        (i32.shr_s
+          (local.get $x)
+          (i32.const 4)
+        )
+        (i32.const 4)
       )
-      (i64.const 255)
     )
-  )
-  ;; CHECK:      (func $optimizeAddedConstants-filters-through-nonzero (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.shl
-  ;; CHECK-NEXT:    (i32.const -536870912)
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 31744)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimizeAddedConstants-filters-through-nonzero (result i32)
-   (i32.sub
-    (i32.add
-     (i32.shl
-      (i32.const -536870912)
-      (i32.wrap_i64
-       (i64.const 0)
+    (drop
+      (i64.shl
+        (i64.shr_s
+          (local.get $y)
+          (i64.const 63)
+        )
+        (i64.const 127) ;; effective shift is 63
       )
-     )
-     (i32.const -32768)
     )
-    (i32.const -1024)
-   )
-  )
-  ;; CHECK:      (func $optimizeAddedConstants-filters-through-nonzero-b (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.shl
-  ;; CHECK-NEXT:    (i32.const -536870912)
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (i64.const -1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 31744)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimizeAddedConstants-filters-through-nonzero-b (result i32)
-   (i32.sub
-    (i32.add
-     (i32.shl
-      (i32.const -536870912)
-      (i32.wrap_i64
-       (i64.const -1)
+
+    ;; skips
+    (drop
+      (i32.shl
+        (i32.shr_s
+          (local.get $x)
+          (i32.const 4)
+        )
+        (i32.const 5)
       )
-     )
-     (i32.const -32768)
     )
-    (i32.const -1024)
-   )
-  )
-  ;; CHECK:      (func $return-proper-value-from-shift-left-by-zero (result i32)
-  ;; CHECK-NEXT:  (if (result i32)
-  ;; CHECK-NEXT:   (i32.add
-  ;; CHECK-NEXT:    (loop $label$0 (result i32)
-  ;; CHECK-NEXT:     (block $label$1
-  ;; CHECK-NEXT:      (br_if $label$1
-  ;; CHECK-NEXT:       (i32.load
-  ;; CHECK-NEXT:        (i32.const 0)
-  ;; CHECK-NEXT:       )
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (i32.const -62)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 40)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $return-proper-value-from-shift-left-by-zero (result i32)
-   (if (result i32)
-    (i32.sub
-     (i32.add
-      (loop $label$0 (result i32)
-       (block $label$1
-        (br_if $label$1
-         (i32.shl
-          (i32.load
-           (i32.const 0)
-          )
-          (i32.const -31904) ;; really 0 shifts
-         )
+    (drop
+      (i64.shl
+        (i64.shr_s
+          (local.get $y)
+          (i64.const 63)
         )
-       )
-       (i32.const -62)
+        (i64.const 1)
       )
-      (i32.const 38)
-     )
-     (i32.const -2)
     )
-    (i32.const 1)
-    (i32.const 0)
-   )
   )
-  ;; CHECK:      (func $de-morgan-2 (param $x i32) (param $y i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $mix-shifts-with-same-const-unsigned (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -16)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.xor
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:   (i32.shl
+  ;; CHECK-NEXT:    (i32.shr_u
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 4)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 5)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:   (i64.shl
+  ;; CHECK-NEXT:    (i64.shr_u
   ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 63)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i64.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT: )
+  (func $mix-shifts-with-same-const-unsigned (param $x i32) (param $y i64)
+    (drop
+      (i32.shl
+        (i32.shr_u
+          (local.get $x)
+          (i32.const 4)
+        )
+        (i32.const 4)
+      )
+    )
+    (drop
+      (i64.shl
+        (i64.shr_u
+          (local.get $y)
+          (i64.const 63)
+        )
+        (i64.const 127) ;; effective shift is 63
+      )
+    )
+
+    ;; skips
+    (drop
+      (i32.shl
+        (i32.shr_u
+          (local.get $x)
+          (i32.const 4)
+        )
+        (i32.const 5)
+      )
+    )
+    (drop
+      (i64.shl
+        (i64.shr_u
+          (local.get $y)
+          (i64.const 63)
+        )
+        (i64.const 1)
+      )
+    )
+  )
+  ;; CHECK:      (func $reversed-mix-shifts-with-same-const (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 268435455)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_u
+  ;; CHECK-NEXT:    (i32.shl
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (i64.const 2)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 4)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:   (i64.shr_u
+  ;; CHECK-NEXT:    (i64.shl
   ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 4)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:    (i64.const 8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:    (i32.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 6)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $de-morgan-2 (param $x i32) (param $y i32)
-    (drop
-      (i32.and (i32.eqz (local.get $x)) (i32.eqz (local.get $y)))
-    )
+  (func $reversed-mix-shifts-with-same-const (param $x i32) (param $y i64)
     (drop
-      (i32.or (i32.eqz (local.get $x)) (i32.eqz (local.get $y)))
+      (i32.shr_u
+        (i32.shl
+          (local.get $x)
+          (i32.const 4)
+        )
+        (i32.const 4)
+      )
     )
     (drop
-      (i32.xor (i32.eqz (local.get $x)) (i32.eqz (local.get $y)))
+      (i32.shr_u
+        (i32.shl
+          (local.get $x)
+          (i32.const 31)
+        )
+        (i32.const 31)
+      )
     )
     (drop
-      (i32.and (i32.eqz (local.get $x)) (local.get $y))
+      (i64.shr_u
+        (i64.shl
+          (local.get $y)
+          (i64.const 127) ;; effective shift is 63
+        )
+        (i64.const 63) ;; effective shift is 63
+      )
     )
+
+    ;; skips
     (drop
-      (i32.and (local.get $x) (i32.eqz (local.get $y)))
+      (i32.shr_u
+        (i32.shl
+          (local.get $x)
+          (i32.const 5)
+        )
+        (i32.const 4)
+      )
     )
     (drop
-      (i32.and (i32.eqz (local.get $x)) (i32.wrap_i64 (i64.const 2)))
+      (i64.shr_u
+        (i64.shl
+          (local.get $y)
+          (i64.const 4)
+        )
+        (i64.const 8)
+      )
     )
     (drop
-      (i32.and (i32.wrap_i64 (i64.const 1)) (i32.eqz (local.get $y)))
+      (i32.shr_s
+        (i32.shl
+          (local.get $x)
+          (i32.const 6)
+        )
+        (i32.const 6)
+      )
     )
   )
-  ;; CHECK:      (func $subzero1 (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.const 32)
-  ;; CHECK-NEXT:   (i32.clz
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $actually-no-shifts (result i32)
+  ;; CHECK-NEXT:  (i32.const 33)
   ;; CHECK-NEXT: )
-  (func $subzero1 (param $0 i32) (result i32)
+  (func $actually-no-shifts (result i32)
     (i32.add
-     (i32.sub
-      (i32.const 1)
-      (i32.clz
-       (local.get $0)
+      (i32.shl
+        (i32.const 23)
+        (i32.const 32) ;; really 0
       )
-     )
-     (i32.const 31)
+      (i32.const 10)
     )
   )
-  ;; CHECK:      (func $subzero2 (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.const 32)
-  ;; CHECK-NEXT:   (i32.clz
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $less-shifts-than-it-seems (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.const 4800)
   ;; CHECK-NEXT: )
-  (func $subzero2 (param $0 i32) (result i32)
+  (func $less-shifts-than-it-seems (param $x i32) (result i32)
     (i32.add
-     (i32.const 31)
-     (i32.sub
-      (i32.const 1)
-      (i32.clz
-       (local.get $0)
+      (i32.shl
+        (i32.const 200)
+        (i32.const 36) ;; really 4
+      )
+      (i32.shl
+        (i32.const 100)
+        (i32.const 4)
       )
-     )
     )
   )
-  ;; CHECK:      (func $subzero3 (param $0 i32) (param $1 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (local.get $1)
-  ;; CHECK-NEXT:   (i32.clz
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-left-square-no-overflow-small (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotl
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 7)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $subzero3 (param $0 i32) (param $1 i32) (result i32)
-    (i32.add
-     (i32.sub
-      (i32.const 0)
-      (i32.clz
-       (local.get $0)
+  (func $rotate-left-square-no-overflow-small (param $x i32) (result i32)
+    (i32.rotl
+      (i32.rotl
+        (local.get $x)
+        (i32.const 3)
       )
-     )
-     (local.get $1)
+      (i32.const 4)
     )
   )
-  ;; CHECK:      (func $subzero4 (param $0 i32) (param $1 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (local.get $0)
-  ;; CHECK-NEXT:   (i32.clz
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-right-square-no-overflow-small (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotr
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 7)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $subzero4 (param $0 i32) (param $1 i32) (result i32)
-    (i32.add
-     (local.get $0)
-     (i32.sub
-      (i32.const 0)
-      (i32.clz
-       (local.get $1)
+  (func $rotate-right-square-no-overflow-small (param $x i32) (result i32)
+    (i32.rotr
+      (i32.rotr
+        (local.get $x)
+        (i32.const 3)
       )
-     )
+      (i32.const 4)
     )
   )
-  ;; CHECK:      (func $remove-signs-for-mul-i32 (param $0 i32) (param $1 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.mul
-  ;; CHECK-NEXT:   (local.get $0)
-  ;; CHECK-NEXT:   (local.get $1)
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $rotate-left-square-no-shifts (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (local.get $x)
   ;; CHECK-NEXT: )
-  (func $remove-signs-for-mul-i32 (param $0 i32) (param $1 i32) (result i32)
-    (i32.mul
-      (i32.sub
-        (i32.const 0)
-        (local.get $0)
-      )
-      (i32.sub
-        (i32.const 0)
-        (local.get $1)
+  (func $rotate-left-square-no-shifts (param $x i32) (result i32)
+    (i32.rotl
+      (i32.rotl
+        (local.get $x)
+        (i32.const 12)
       )
+      (i32.const 20)
     )
   )
-  ;; CHECK:      (func $remove-signs-for-mul-i64 (param $0 i64) (param $1 i64) (result i64)
-  ;; CHECK-NEXT:  (i64.mul
-  ;; CHECK-NEXT:   (local.get $0)
-  ;; CHECK-NEXT:   (local.get $1)
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $rotate-right-square-no-shifts (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (local.get $x)
   ;; CHECK-NEXT: )
-  (func $remove-signs-for-mul-i64 (param $0 i64) (param $1 i64) (result i64)
-    (i64.mul
-      (i64.sub
-        (i64.const 0)
-        (local.get $0)
-      )
-      (i64.sub
-        (i64.const 0)
-        (local.get $1)
+  (func $rotate-right-square-no-shifts (param $x i32) (result i32)
+    (i32.rotr
+      (i32.rotr
+        (local.get $x)
+        (i32.const 30)
       )
+      (i32.const 2)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-i32-lhs (param $0 i32) (param $1 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (i32.mul
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-right-left-pos (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotl
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 23)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-i32-lhs (param $0 i32) (param $1 i32) (result i32)
-    (i32.mul
-      (i32.sub
-        (i32.const 0)
-        (local.get $0)
+  (func $rotate-right-left-pos (param $x i32) (result i32)
+    (i32.rotr
+      (i32.rotl
+        (local.get $x)
+        (i32.const 27)
       )
-      (local.get $1)
+      (i32.const 4)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-i32-rhs (param $0 i32) (param $1 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (i32.mul
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-left-right-pos (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotr
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 7)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-i32-rhs (param $0 i32) (param $1 i32) (result i32)
-    (i32.mul
-      (local.get $0)
-      (i32.sub
-        (i32.const 0)
-        (local.get $1)
+  (func $rotate-left-right-pos (param $x i32) (result i32)
+    (i32.rotl
+      (i32.rotr
+        (local.get $x)
+        (i32.const 12)
       )
+      (i32.const 5)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-i64-lhs (param $0 i64) (param $1 i64) (result i64)
-  ;; CHECK-NEXT:  (i64.sub
-  ;; CHECK-NEXT:   (i64.const 0)
-  ;; CHECK-NEXT:   (i64.mul
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-right-left-neg (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotl
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 27)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-i64-lhs (param $0 i64) (param $1 i64) (result i64)
-    (i64.mul
-      (i64.sub
-        (i64.const 0)
-        (local.get $0)
+  (func $rotate-right-left-neg (param $x i32) (result i32)
+    (i32.rotr
+      (i32.rotl
+        (local.get $x)
+        (i32.const 5)
       )
-      (local.get $1)
+      (i32.const 10)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-i64-rhs (param $0 i64) (param $1 i64) (result i64)
-  ;; CHECK-NEXT:  (i64.sub
-  ;; CHECK-NEXT:   (i64.const 0)
-  ;; CHECK-NEXT:   (i64.mul
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-left-right-neg (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotr
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 27)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-i64-rhs (param $0 i64) (param $1 i64) (result i64)
-    (i64.mul
-      (local.get $0)
-      (i64.sub
-        (i64.const 0)
-        (local.get $1)
+  (func $rotate-left-right-neg (param $x i32) (result i32)
+    (i32.rotl
+      (i32.rotr
+        (local.get $x)
+        (i32.const 5)
       )
+      (i32.const 10)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-i32-rhs-side (param $0 i32) (param $1 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (i32.mul
-  ;; CHECK-NEXT:    (call $subzero4
-  ;; CHECK-NEXT:     (local.get $0)
-  ;; CHECK-NEXT:     (local.get $1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $rotate-right-left-none (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (local.get $x)
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-i32-rhs-side (param $0 i32) (param $1 i32) (result i32)
-    (i32.mul
-      (call $subzero4
-        (local.get $0)
-        (local.get $1)
-      )
-      (i32.sub
-        (i32.const 0)
-        (local.get $1)
+  (func $rotate-right-left-none (param $x i32) (result i32)
+    (i32.rotr
+      (i32.rotl
+        (local.get $x)
+        (i32.const 16)
       )
+      (i32.const 16)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-mul-i32-lhs-const (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.mul
-  ;; CHECK-NEXT:   (local.get $0)
-  ;; CHECK-NEXT:   (i32.const -3)
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $rotate-left-right-none (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (local.get $x)
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-mul-i32-lhs-const (param $0 i32) (result i32)
-    (i32.mul
-      (i32.const 3)
-      (i32.sub
-        (i32.const 0)
-        (local.get $0)
+  (func $rotate-left-right-none (param $x i32) (result i32)
+    (i32.rotl
+      (i32.rotr
+        (local.get $x)
+        (i32.const 16)
       )
+      (i32.const 16)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-mul-i32-lhs-const-pot (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.sub
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (i32.mul
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:    (i32.const 2)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $rotate-right-left-overflow (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotl
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 27)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-mul-i32-lhs-const-pot (param $0 i32) (result i32)
-    (i32.mul
-      (i32.const 2)
-      (i32.sub
-        (i32.const 0)
-        (local.get $0)
+  (func $rotate-right-left-overflow (param $x i32) (result i32)
+    (i32.rotr
+      (i32.rotl
+        (local.get $x)
+        (i32.const 18)
       )
+      (i32.const 23)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-mul-i32-rhs-const (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.mul
-  ;; CHECK-NEXT:   (local.get $0)
-  ;; CHECK-NEXT:   (i32.const 5)
+  ;; CHECK:      (func $rotate-left-right-overflow (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.rotr
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 27)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-mul-i32-rhs-const (param $0 i32) (result i32)
-    (i32.mul
-      (i32.sub
-        (i32.const 0)
-        (local.get $0)
+  (func $rotate-left-right-overflow (param $x i32) (result i32)
+    (i32.rotl
+      (i32.rotr
+        (local.get $x)
+        (i32.const 18)
       )
-      (i32.const -5)
+      (i32.const 23)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-i32-both-sign-consts (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.mul
-  ;; CHECK-NEXT:   (i32.const 2)
-  ;; CHECK-NEXT:   (i32.const 5)
+  ;; CHECK:      (func $and-popcount32 (result i32)
+  ;; CHECK-NEXT:  (i32.and
+  ;; CHECK-NEXT:   (i32.popcnt
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 31)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-i32-both-sign-consts (param $0 i32) (result i32)
-    (i32.mul
-      (i32.const -5)
-      (i32.sub
-        (i32.const 0)
-        (i32.const 2)
+  (func $and-popcount32 (result i32)
+    (i32.and
+      (i32.popcnt
+        (i32.const -1)
       )
+      (i32.const 31)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-mul-i32-smin (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.shl
-  ;; CHECK-NEXT:   (local.get $0)
-  ;; CHECK-NEXT:   (i32.const 31)
+  ;; CHECK:      (func $and-popcount32-big (result i32)
+  ;; CHECK-NEXT:  (i32.popcnt
+  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-mul-i32-smin (param $0 i32) (result i32)
-    (i32.mul
-      (i32.sub
-        (i32.const 0)
-        (local.get $0)
+  (func $and-popcount32-big (result i32)
+    (i32.and
+      (i32.popcnt
+        (i32.const -1)
       )
-      (i32.const 0x80000000)
+      (i32.const 63)
     )
   )
-  ;; CHECK:      (func $propagate-sign-for-mul-i32-skip-2 (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (i32.mul
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK:      (func $and-popcount64 (result i64)
+  ;; CHECK-NEXT:  (i64.and
+  ;; CHECK-NEXT:   (i64.popcnt
+  ;; CHECK-NEXT:    (i64.const -1)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (i64.const 63)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $propagate-sign-for-mul-i32-skip-2 (param $0 i32) (result i32)
-    (i32.mul
-      (local.get $0)
-      (i32.sub
-        (i32.const 0)
-        (i32.const 3)
+  (func $and-popcount64 (result i64) ;; these are TODOs
+    (i64.and
+      (i64.popcnt
+        (i64.const -1)
       )
+      (i64.const 63)
     )
   )
-  ;; CHECK:      (func $mul-32-power-2 (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (i32.shl
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (i32.mul
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 5)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (i32.mul
-  ;; CHECK-NEXT:     (call $mul-32-power-2
-  ;; CHECK-NEXT:      (i32.const 123)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $mul-32-power-2
-  ;; CHECK-NEXT:    (i32.shl
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 31)
-  ;; CHECK-NEXT:    )
+  ;; CHECK:      (func $and-popcount64-big (result i64)
+  ;; CHECK-NEXT:  (i64.and
+  ;; CHECK-NEXT:   (i64.popcnt
+  ;; CHECK-NEXT:    (i64.const -1)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const 127)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
-  (func $mul-32-power-2 (param $x i32) (result i32)
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (local.get $x)
-          (i32.const 4)
-        )
-      )
-    )
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (local.get $x)
-          (i32.const 5)
-        )
+  (func $and-popcount64-big (result i64)
+    (i64.and
+      (i64.popcnt
+        (i64.const -1)
       )
+      (i64.const 127)
     )
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (local.get $x)
-          (i32.const 1)
-        )
-      )
-    )
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (local.get $x)
-          (i32.const 0)
-        )
-      )
-    )
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (call $mul-32-power-2 (i32.const 123)) ;; side effects
-          (i32.const 0)
-        )
-      )
-    )
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (local.get $x)
-          (i32.const 0xffffffff)
-        )
-      )
-    )
-    (drop
-      (call $mul-32-power-2
-        (i32.mul
-          (local.get $x)
-          (i32.const 0x80000000)
-        )
-      )
-    )
-    (unreachable)
   )
-    ;; CHECK:      (func $mul-64-power-2 (param $x i64) (result i64)
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (i64.shl
-    ;; CHECK-NEXT:     (local.get $x)
-    ;; CHECK-NEXT:     (i64.const 2)
-    ;; CHECK-NEXT:    )
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (i64.mul
-    ;; CHECK-NEXT:     (local.get $x)
-    ;; CHECK-NEXT:     (i64.const 5)
-    ;; CHECK-NEXT:    )
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (local.get $x)
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (i64.const 0)
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (i64.mul
-    ;; CHECK-NEXT:     (call $mul-64-power-2
-    ;; CHECK-NEXT:      (i64.const 123)
-    ;; CHECK-NEXT:     )
-    ;; CHECK-NEXT:     (i64.const 0)
-    ;; CHECK-NEXT:    )
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (i64.sub
-    ;; CHECK-NEXT:     (i64.const 0)
-    ;; CHECK-NEXT:     (local.get $x)
-    ;; CHECK-NEXT:    )
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (drop
-    ;; CHECK-NEXT:   (call $mul-64-power-2
-    ;; CHECK-NEXT:    (i64.shl
-    ;; CHECK-NEXT:     (local.get $x)
-    ;; CHECK-NEXT:     (i64.const 63)
-    ;; CHECK-NEXT:    )
-    ;; CHECK-NEXT:   )
-    ;; CHECK-NEXT:  )
-    ;; CHECK-NEXT:  (unreachable)
-    ;; CHECK-NEXT: )
-    (func $mul-64-power-2 (param $x i64) (result i64)
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (local.get $x)
-          (i64.const 4)
-        )
-      )
-    )
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (local.get $x)
-          (i64.const 5)
-        )
+  ;; CHECK:      (func $and-popcount64-bigger (result i64)
+  ;; CHECK-NEXT:  (i64.and
+  ;; CHECK-NEXT:   (i64.popcnt
+  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const 255)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $and-popcount64-bigger (result i64)
+    (i64.and
+      (i64.popcnt
+        (i64.const -1)
       )
+      (i64.const 255)
     )
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (local.get $x)
-          (i64.const 1)
-        )
+  )
+  ;; CHECK:      (func $optimizeAddedConstants-filters-through-nonzero (result i32)
+  ;; CHECK-NEXT:  (i32.const -536902656)
+  ;; CHECK-NEXT: )
+  (func $optimizeAddedConstants-filters-through-nonzero (result i32)
+   (i32.sub
+    (i32.add
+     (i32.shl
+      (i32.const -536870912)
+      (i32.wrap_i64
+       (i64.const 0)
       )
+     )
+     (i32.const -32768)
     )
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (local.get $x)
-          (i64.const 0)
-        )
+    (i32.const -1024)
+   )
+  )
+  ;; CHECK:      (func $optimizeAddedConstants-filters-through-nonzero-b (result i32)
+  ;; CHECK-NEXT:  (i32.const -31744)
+  ;; CHECK-NEXT: )
+  (func $optimizeAddedConstants-filters-through-nonzero-b (result i32)
+   (i32.sub
+    (i32.add
+     (i32.shl
+      (i32.const -536870912)
+      (i32.wrap_i64
+       (i64.const -1)
       )
+     )
+     (i32.const -32768)
     )
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (call $mul-64-power-2 (i64.const 123)) ;; side effects
-          (i64.const 0)
-        )
-      )
+    (i32.const -1024)
+   )
+  )
+  ;; CHECK:      (func $optimizeAddedConstants-mul-lshift-32 (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimizeAddedConstants-mul-lshift-32 (param $x i32) (result i32)
+   (i32.shl
+    (i32.mul
+     (local.get $x)
+     (i32.const 3)
     )
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (local.get $x)
-          (i64.const 0xffffffffffffffff)
-        )
-      )
+    (i32.const 2)
+   )
+  )
+  ;; CHECK:      (func $optimizeAddedConstants-mul-lshift-64 (param $x i64) (result i64)
+  ;; CHECK-NEXT:  (i64.mul
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i64.const 12)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimizeAddedConstants-mul-lshift-64 (param $x i64) (result i64)
+   (i64.shl
+    (i64.mul
+     (local.get $x)
+     (i64.const 3)
     )
-    (drop
-      (call $mul-64-power-2
-        (i64.mul
-          (local.get $x)
-          (i64.const 0x8000000000000000)
-        )
-      )
+    (i64.const 2)
+   )
+  )
+  ;; CHECK:      (func $optimizeAddedConstants-lshift-mul-32 (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 12)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimizeAddedConstants-lshift-mul-32 (param $x i32) (result i32)
+   (i32.mul
+    (i32.shl
+     (local.get $x)
+     (i32.const 2)
     )
-    (unreachable)
+    (i32.const 3)
+   )
   )
-  ;; CHECK:      (func $div-32-power-2 (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (i32.shr_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $optimizeAddedConstants-lshift-mul-64 (param $x i64) (result i64)
+  ;; CHECK-NEXT:  (i64.mul
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i64.const 12)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (i32.div_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 5)
+  ;; CHECK-NEXT: )
+  (func $optimizeAddedConstants-lshift-mul-64 (param $x i64) (result i64)
+   (i64.mul
+    (i64.shl
+     (local.get $x)
+     (i64.const 2)
+    )
+    (i64.const 3)
+   )
+  )
+  ;; CHECK:      (func $return-proper-value-from-shift-left-by-zero (result i32)
+  ;; CHECK-NEXT:  (if (result i32)
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (loop $label$0 (result i32)
+  ;; CHECK-NEXT:     (block $label$1
+  ;; CHECK-NEXT:      (br_if $label$1
+  ;; CHECK-NEXT:       (i32.load
+  ;; CHECK-NEXT:        (i32.const 0)
+  ;; CHECK-NEXT:       )
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const -62)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 40)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (i32.div_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (i32.div_u
-  ;; CHECK-NEXT:     (call $div-32-power-2
-  ;; CHECK-NEXT:      (i32.const 123)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (i32.eq
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const -1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $div-32-power-2
-  ;; CHECK-NEXT:    (i32.shr_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 31)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
-  (func $div-32-power-2 (param $x i32) (result i32)
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (local.get $x)
-          (i32.const 4)
-        )
-      )
-    )
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (local.get $x)
-          (i32.const 5)
-        )
-      )
-    )
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (local.get $x)
-          (i32.const 1)
-        )
-      )
-    )
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (local.get $x)
-          (i32.const 0)
-        )
-      )
-    )
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (call $div-32-power-2 (i32.const 123)) ;; side effects
-          (i32.const 0)
-        )
-      )
-    )
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (local.get $x)
-          (i32.const 0xffffffff)
-        )
-      )
-    )
-    (drop
-      (call $div-32-power-2
-        (i32.div_u
-          (local.get $x)
-          (i32.const 0x80000000)
+  (func $return-proper-value-from-shift-left-by-zero (result i32)
+   (if (result i32)
+    (i32.sub
+     (i32.add
+      (loop $label$0 (result i32)
+       (block $label$1
+        (br_if $label$1
+         (i32.shl
+          (i32.load
+           (i32.const 0)
+          )
+          (i32.const -31904) ;; really 0 shifts
+         )
         )
+       )
+       (i32.const -62)
       )
+      (i32.const 38)
+     )
+     (i32.const -2)
     )
-    (unreachable)
+    (i32.const 1)
+    (i32.const 0)
+   )
   )
-  ;; CHECK:      (func $urem-32-power-2 (param $x i32) (result i32)
+  ;; CHECK:      (func $de-morgan-2 (param $x i32) (param $y i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.or
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.rem_u
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.eqz
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 5)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i32.xor
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.rem_u
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.eqz
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.rem_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const -1)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (i32.eqz
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $urem-32-power-2
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
-  (func $urem-32-power-2 (param $x i32) (result i32)
+  (func $de-morgan-2 (param $x i32) (param $y i32)
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 4)
-        )
-      )
+      (i32.and (i32.eqz (local.get $x)) (i32.eqz (local.get $y)))
     )
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 5)
-        )
-      )
+      (i32.or (i32.eqz (local.get $x)) (i32.eqz (local.get $y)))
     )
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 1)
-        )
-      )
+      (i32.xor (i32.eqz (local.get $x)) (i32.eqz (local.get $y)))
     )
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 0)
-        )
-      )
+      (i32.and (i32.eqz (local.get $x)) (local.get $y))
     )
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 0xffffffff)
-        )
-      )
+      (i32.and (local.get $x) (i32.eqz (local.get $y)))
     )
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 0x80000000)
-        )
-      )
+      (i32.and (i32.eqz (local.get $x)) (i32.wrap_i64 (i64.const 2)))
     )
-    ;; (unsigned)x % 1
     (drop
-      (call $urem-32-power-2
-        (i32.rem_u
-          (local.get $x)
-          (i32.const 1)
-        )
-      )
+      (i32.and (i32.wrap_i64 (i64.const 1)) (i32.eqz (local.get $y)))
     )
-    (unreachable)
   )
-  ;; CHECK:      (func $fdiv-32-power-2 (param $x f32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 0.5)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const -0.5)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 2.3283064365386963e-10)
+  ;; CHECK:      (func $subzero1 (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 32)
+  ;; CHECK-NEXT:   (i32.clz
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 5.421010862427522e-20)
+  ;; CHECK-NEXT: )
+  (func $subzero1 (param $0 i32) (result i32)
+    (i32.add
+     (i32.sub
+      (i32.const 1)
+      (i32.clz
+       (local.get $0)
+      )
+     )
+     (i32.const 31)
+    )
+  )
+  ;; CHECK:      (func $subzero2 (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 32)
+  ;; CHECK-NEXT:   (i32.clz
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 8507059173023461586584365e13)
+  ;; CHECK-NEXT: )
+  (func $subzero2 (param $0 i32) (result i32)
+    (i32.add
+     (i32.const 31)
+     (i32.sub
+      (i32.const 1)
+      (i32.clz
+       (local.get $0)
+      )
+     )
+    )
+  )
+  ;; CHECK:      (func $subzero3 (param $0 i32) (param $1 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (local.get $1)
+  ;; CHECK-NEXT:   (i32.clz
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 1.1754943508222875e-38)
+  ;; CHECK-NEXT: )
+  (func $subzero3 (param $0 i32) (param $1 i32) (result i32)
+    (i32.add
+     (i32.sub
+      (i32.const 0)
+      (i32.clz
+       (local.get $0)
+      )
+     )
+     (local.get $1)
+    )
+  )
+  ;; CHECK:      (func $subzero4 (param $0 i32) (param $1 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (i32.clz
+  ;; CHECK-NEXT:    (local.get $1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const -8507059173023461586584365e13)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT: )
+  (func $subzero4 (param $0 i32) (param $1 i32) (result i32)
+    (i32.add
+     (local.get $0)
+     (i32.sub
+      (i32.const 0)
+      (i32.clz
+       (local.get $1)
+      )
+     )
+    )
+  )
+  ;; CHECK:      (func $remove-signs-for-mul-i32 (param $0 i32) (param $1 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (local.get $1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const -1.1754943508222875e-38)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT: )
+  (func $remove-signs-for-mul-i32 (param $0 i32) (param $1 i32) (result i32)
+    (i32.mul
+      (i32.sub
+        (i32.const 0)
+        (local.get $0)
+      )
+      (i32.sub
+        (i32.const 0)
+        (local.get $1)
+      )
+    )
+  )
+  ;; CHECK:      (func $remove-signs-for-mul-i64 (param $0 i64) (param $1 i64) (result i64)
+  ;; CHECK-NEXT:  (i64.mul
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (local.get $1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 5.877471754111438e-39)
+  ;; CHECK-NEXT: )
+  (func $remove-signs-for-mul-i64 (param $0 i64) (param $1 i64) (result i64)
+    (i64.mul
+      (i64.sub
+        (i64.const 0)
+        (local.get $0)
+      )
+      (i64.sub
+        (i64.const 0)
+        (local.get $1)
+      )
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-i32-lhs (param $0 i32) (param $1 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.mul
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 5.877471754111438e-39)
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-i32-lhs (param $0 i32) (param $1 i32) (result i32)
+    (i32.mul
+      (i32.sub
+        (i32.const 0)
+        (local.get $0)
+      )
+      (local.get $1)
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-i32-rhs (param $0 i32) (param $1 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.mul
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-i32-rhs (param $0 i32) (param $1 i32) (result i32)
+    (i32.mul
+      (local.get $0)
+      (i32.sub
+        (i32.const 0)
+        (local.get $1)
+      )
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-i64-lhs (param $0 i64) (param $1 i64) (result i64)
+  ;; CHECK-NEXT:  (i64.sub
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (i64.mul
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const nan:0x400000)
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-i64-lhs (param $0 i64) (param $1 i64) (result i64)
+    (i64.mul
+      (i64.sub
+        (i64.const 0)
+        (local.get $0)
+      )
+      (local.get $1)
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-i64-rhs (param $0 i64) (param $1 i64) (result i64)
+  ;; CHECK-NEXT:  (i64.sub
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (i64.mul
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:    (local.get $0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-i64-rhs (param $0 i64) (param $1 i64) (result i64)
+    (i64.mul
+      (local.get $0)
+      (i64.sub
+        (i64.const 0)
+        (local.get $1)
+      )
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-i32-rhs-side (param $0 i32) (param $1 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.mul
+  ;; CHECK-NEXT:    (call $subzero4
+  ;; CHECK-NEXT:     (local.get $0)
+  ;; CHECK-NEXT:     (local.get $1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-i32-rhs-side (param $0 i32) (param $1 i32) (result i32)
+    (i32.mul
+      (call $subzero4
+        (local.get $0)
+        (local.get $1)
+      )
+      (i32.sub
+        (i32.const 0)
+        (local.get $1)
+      )
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-mul-i32-lhs-const (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (i32.const -3)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-mul-i32-lhs-const (param $0 i32) (result i32)
+    (i32.mul
+      (i32.const 3)
+      (i32.sub
+        (i32.const 0)
+        (local.get $0)
+      )
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-mul-i32-lhs-const-pot (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.mul
+  ;; CHECK-NEXT:    (local.get $0)
+  ;; CHECK-NEXT:    (i32.const 2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $fdiv-32-power-2 (param $x f32)
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 2)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const -2)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 4294967296)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 18446744073709551616)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 0x1p-126)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 0x1p+126)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const -0x1p-126)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const -0x1p+126)
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 0x1p-127) ;; skip
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 0x1p-127) ;; skip
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const 0) ;; skip
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const nan) ;; skip
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const inf) ;; skip
-    ))
-    (drop (f32.div
-      (local.get $x)
-      (f32.const -inf) ;; skip
-    ))
+  (func $propagate-sign-for-mul-i32-lhs-const-pot (param $0 i32) (result i32)
+    (i32.mul
+      (i32.const 2)
+      (i32.sub
+        (i32.const 0)
+        (local.get $0)
+      )
+    )
   )
-  ;; CHECK:      (func $fdiv-64-power-2 (param $x f64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 0.5)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const -0.5)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 2.3283064365386963e-10)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 5.421010862427522e-20)
-  ;; CHECK-NEXT:   )
+  ;; CHECK:      (func $propagate-sign-for-mul-i32-rhs-const (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (i32.const 5)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 4494232837155789769323262e283)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-mul-i32-rhs-const (param $0 i32) (result i32)
+    (i32.mul
+      (i32.sub
+        (i32.const 0)
+        (local.get $0)
+      )
+      (i32.const -5)
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-i32-both-sign-consts (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:   (i32.const 5)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 2.2250738585072014e-308)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-i32-both-sign-consts (param $0 i32) (result i32)
+    (i32.mul
+      (i32.const -5)
+      (i32.sub
+        (i32.const 0)
+        (i32.const 2)
+      )
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-mul-i32-smin (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.shl
+  ;; CHECK-NEXT:   (local.get $0)
+  ;; CHECK-NEXT:   (i32.const 31)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const -4494232837155789769323262e283)
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-mul-i32-smin (param $0 i32) (result i32)
+    (i32.mul
+      (i32.sub
+        (i32.const 0)
+        (local.get $0)
+      )
+      (i32.const 0x80000000)
+    )
+  )
+  ;; CHECK:      (func $propagate-sign-for-mul-i32-skip-2 (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const 3)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $0)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $propagate-sign-for-mul-i32-skip-2 (param $0 i32) (result i32)
+    (i32.mul
+      (local.get $0)
+      (i32.sub
+        (i32.const 0)
+        (i32.const 3)
+      )
+    )
+  )
+  ;; CHECK:      (func $mul-32-power-2 (param $x i32) (result i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const -2.2250738585072014e-308)
+  ;; CHECK-NEXT:   (call $mul-32-power-2
+  ;; CHECK-NEXT:    (i32.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 1.1125369292536007e-308)
+  ;; CHECK-NEXT:   (call $mul-32-power-2
+  ;; CHECK-NEXT:    (i32.mul
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 5)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:   (call $mul-32-power-2
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 8988465674311579538646525e283)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const 0)
+  ;; CHECK-NEXT:   (call $mul-32-power-2
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const nan:0x8000000000000)
+  ;; CHECK-NEXT:   (call $mul-32-power-2
+  ;; CHECK-NEXT:    (i32.mul
+  ;; CHECK-NEXT:     (call $mul-32-power-2
+  ;; CHECK-NEXT:      (i32.const 123)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const inf)
+  ;; CHECK-NEXT:   (call $mul-32-power-2
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (f64.const -inf)
+  ;; CHECK-NEXT:   (call $mul-32-power-2
+  ;; CHECK-NEXT:    (i32.shl
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 31)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
-  (func $fdiv-64-power-2 (param $x f64)
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 2)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const -2)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 4294967296)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 18446744073709551616)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 0x1p-1022)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 0x1p+1022)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const -0x1p-1022)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const -0x1p+1022)
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 0x1p-1023) ;; skip
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 0x1p+1023) ;; skip
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const 0) ;; skip
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const nan) ;; skip
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const inf) ;; skip
-    ))
-    (drop (f64.div
-      (local.get $x)
-      (f64.const -inf) ;; skip
-    ))
-  )
-  ;; CHECK:      (func $srem-by-const (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rem_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.rem_s
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $srem-by-const (param $x i32) (param $y i64)
-    ;; (signed)x % 1
-    (drop (i32.rem_s
-      (local.get $x)
-      (i32.const 1)
-    ))
-    (drop (i64.rem_s
-      (local.get $y)
-      (i64.const 1)
-    ))
-    ;; (signed)x % 0x80000000 -> x & 0x7FFFFFFF
-    (drop (i32.rem_s
-      (local.get $x)
-      (i32.const 0x80000000)
-    ))
-    ;; (signed)x % 0x8000000000000000 -> x & 0x7FFFFFFFFFFFFFFF
-    (drop (i64.rem_s
-      (local.get $y)
-      (i64.const 0x8000000000000000)
-    ))
+  (func $mul-32-power-2 (param $x i32) (result i32)
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (local.get $x)
+          (i32.const 4)
+        )
+      )
+    )
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (local.get $x)
+          (i32.const 5)
+        )
+      )
+    )
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (local.get $x)
+          (i32.const 1)
+        )
+      )
+    )
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (local.get $x)
+          (i32.const 0)
+        )
+      )
+    )
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (call $mul-32-power-2 (i32.const 123)) ;; side effects
+          (i32.const 0)
+        )
+      )
+    )
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (local.get $x)
+          (i32.const 0xffffffff)
+        )
+      )
+    )
+    (drop
+      (call $mul-32-power-2
+        (i32.mul
+          (local.get $x)
+          (i32.const 0x80000000)
+        )
+      )
+    )
+    (unreachable)
   )
-  ;; CHECK:      (func $srem-by-pot-eq-ne-zero (param $x i32) (param $y i64)
+    ;; CHECK:      (func $mul-64-power-2 (param $x i64) (result i64)
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (i64.shl
+    ;; CHECK-NEXT:     (local.get $x)
+    ;; CHECK-NEXT:     (i64.const 2)
+    ;; CHECK-NEXT:    )
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (i64.mul
+    ;; CHECK-NEXT:     (local.get $x)
+    ;; CHECK-NEXT:     (i64.const 5)
+    ;; CHECK-NEXT:    )
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (local.get $x)
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (i64.const 0)
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (i64.mul
+    ;; CHECK-NEXT:     (call $mul-64-power-2
+    ;; CHECK-NEXT:      (i64.const 123)
+    ;; CHECK-NEXT:     )
+    ;; CHECK-NEXT:     (i64.const 0)
+    ;; CHECK-NEXT:    )
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (i64.sub
+    ;; CHECK-NEXT:     (i64.const 0)
+    ;; CHECK-NEXT:     (local.get $x)
+    ;; CHECK-NEXT:    )
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (drop
+    ;; CHECK-NEXT:   (call $mul-64-power-2
+    ;; CHECK-NEXT:    (i64.shl
+    ;; CHECK-NEXT:     (local.get $x)
+    ;; CHECK-NEXT:     (i64.const 63)
+    ;; CHECK-NEXT:    )
+    ;; CHECK-NEXT:   )
+    ;; CHECK-NEXT:  )
+    ;; CHECK-NEXT:  (unreachable)
+    ;; CHECK-NEXT: )
+    (func $mul-64-power-2 (param $x i64) (result i64)
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (local.get $x)
+          (i64.const 4)
+        )
+      )
+    )
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (local.get $x)
+          (i64.const 5)
+        )
+      )
+    )
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (local.get $x)
+          (i64.const 1)
+        )
+      )
+    )
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (local.get $x)
+          (i64.const 0)
+        )
+      )
+    )
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (call $mul-64-power-2 (i64.const 123)) ;; side effects
+          (i64.const 0)
+        )
+      )
+    )
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (local.get $x)
+          (i64.const 0xffffffffffffffff)
+        )
+      )
+    )
+    (drop
+      (call $mul-64-power-2
+        (i64.mul
+          (local.get $x)
+          (i64.const 0x8000000000000000)
+        )
+      )
+    )
+    (unreachable)
+  )
+  ;; CHECK:      (func $div-32-power-2 (param $x i32) (result i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (i32.shr_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 3)
+  ;; CHECK-NEXT:     (i32.const 2)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (i32.div_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 3)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (i32.div_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (i32.div_u
+  ;; CHECK-NEXT:     (call $div-32-power-2
+  ;; CHECK-NEXT:      (i32.const 123)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (i32.eq
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:     (i32.const -1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 3)
+  ;; CHECK-NEXT:   (call $div-32-power-2
+  ;; CHECK-NEXT:    (i32.shr_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 31)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $div-32-power-2 (param $x i32) (result i32)
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (local.get $x)
+          (i32.const 4)
+        )
+      )
+    )
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (local.get $x)
+          (i32.const 5)
+        )
+      )
+    )
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (local.get $x)
+          (i32.const 1)
+        )
+      )
+    )
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (local.get $x)
+          (i32.const 0)
+        )
+      )
+    )
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (call $div-32-power-2 (i32.const 123)) ;; side effects
+          (i32.const 0)
+        )
+      )
+    )
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (local.get $x)
+          (i32.const 0xffffffff)
+        )
+      )
+    )
+    (drop
+      (call $div-32-power-2
+        (i32.div_u
+          (local.get $x)
+          (i32.const 0x80000000)
+        )
+      )
+    )
+    (unreachable)
+  )
+  ;; CHECK:      (func $urem-32-power-2 (param $x i32) (result i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.wrap_i64
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:   (call $urem-32-power-2
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (call $urem-32-power-2
+  ;; CHECK-NEXT:    (i32.rem_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (call $urem-32-power-2
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:   (call $urem-32-power-2
+  ;; CHECK-NEXT:    (i32.rem_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:   (call $urem-32-power-2
+  ;; CHECK-NEXT:    (i32.rem_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const -1)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.rem_s
+  ;; CHECK-NEXT:   (call $urem-32-power-2
+  ;; CHECK-NEXT:    (i32.and
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.rem_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 3)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (call $urem-32-power-2
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
-  (func $srem-by-pot-eq-ne-zero (param $x i32) (param $y i64)
-    ;; eqz((signed)x % 4)
-    (drop (i32.eqz
-      (i32.rem_s
-        (local.get $x)
-        (i32.const 4)
-      )
-    ))
-    (drop (i64.eqz
-      (i64.rem_s
-        (local.get $y)
-        (i64.const 4)
-      )
-    ))
-    ;; eqz((signed)x % -4)
-    (drop (i32.eqz
-      (i32.rem_s
-        (local.get $x)
-        (i32.const -4)
-      )
-    ))
-    (drop (i64.eqz
-      (i64.rem_s
-        (local.get $y)
-        (i64.const -4)
-      )
-    ))
-    ;; (signed)x % 4 == 0
-    (drop (i32.eq
-      (i32.rem_s
-        (local.get $x)
-        (i32.const 4)
-      )
-      (i32.const 0)
-    ))
-    (drop (i64.eq
-      (i64.rem_s
-        (local.get $y)
-        (i64.const 2)
+  (func $urem-32-power-2 (param $x i32) (result i32)
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 4)
+        )
       )
-      (i64.const 0)
-    ))
-    ;; (signed)x % -4 == 0
-    (drop (i32.eq
-      (i32.rem_s
-        (local.get $x)
-        (i32.const -4)
+    )
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 5)
+        )
       )
-      (i32.const 0)
-    ))
-    (drop (i64.eq
-      (i64.rem_s
-        (local.get $y)
-        (i64.const -4)
+    )
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 1)
+        )
       )
-      (i64.const 0)
-    ))
-    ;; (signed)x % 2 != 0
-    (drop (i32.ne
-      (i32.rem_s
-        (local.get $x)
-        (i32.const 2)
+    )
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 0)
+        )
       )
-      (i32.const 0)
-    ))
-    (drop (i64.ne
-      (i64.rem_s
-        (local.get $y)
-        (i64.const 2)
+    )
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 0xffffffff)
+        )
       )
-      (i64.const 0)
-    ))
-    ;; (signed)x % -1 == 0  ->  0 == 0
-    (drop (i32.eq
-      (i32.rem_s
-        (local.get $x)
-        (i32.const -1)
+    )
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 0x80000000)
+        )
       )
-      (i32.const 0)
-    ))
-    ;; (signed)x % 0x80000000 == 0
-    (drop (i32.eq
-      (i32.rem_s
-        (local.get $x)
-        (i32.const 0x80000000)
+    )
+    ;; (unsigned)x % 1
+    (drop
+      (call $urem-32-power-2
+        (i32.rem_u
+          (local.get $x)
+          (i32.const 1)
+        )
       )
-      (i32.const 0)
-    ))
-    ;; (signed)x % 0x80000000 != 0
-    (drop (i32.ne
-      (i32.rem_s
-        (local.get $x)
-        (i32.const 0x80000000)
-      )
-      (i32.const 0)
-    ))
-    ;; (signed)x % 0x8000000000000000 == 0
-    (drop (i64.eq
-      (i64.rem_s
-        (local.get $y)
-        (i64.const 0x8000000000000000)
-      )
-      (i64.const 0)
-    ))
-    ;; (signed)x % 0x8000000000000000 != 0
-    (drop (i64.ne
-      (i64.rem_s
-        (local.get $y)
-        (i64.const 0x8000000000000000)
-      )
-      (i64.const 0)
-    ))
-    ;;
-    (drop (i32.eq
-      (i32.rem_s
-        (local.get $x)
-        (i32.const 3) ;; skip
-      )
-      (i32.const 0)
-    ))
-    (drop (i64.eq
-      (i64.rem_s
-        (local.get $y)
-        (i64.const 3) ;; skip
-      )
-      (i64.const 0)
-    ))
-  )
-  ;; CHECK:      (func $orZero (param $0 i32) (result i32)
-  ;; CHECK-NEXT:  (local.get $0)
-  ;; CHECK-NEXT: )
-  (func $orZero (param $0 i32) (result i32)
-    (i32.or
-      (local.get $0)
-      (i32.const 0)
     )
+    (unreachable)
   )
-  ;; CHECK:      (func $andZero (param $0 i32) (result i32)
+  ;; CHECK:      (func $fdiv-32-power-2 (param $x f32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 0.5)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (call $andZero
-  ;; CHECK-NEXT:     (i32.const 1234)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const -0.5)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (unreachable)
-  ;; CHECK-NEXT: )
-  (func $andZero (param $0 i32) (result i32)
-    (drop
-      (i32.and
-        (local.get $0)
-        (i32.const 0)
-      )
-    )
-    (drop
-      (i32.and
-        (call $andZero (i32.const 1234)) ;; side effects
-        (i32.const 0)
-      )
-    )
-    (unreachable)
-  )
-  ;; CHECK:      (func $abstract-additions (param $x32 i32) (param $x64 i64) (param $y32 f32) (param $y64 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 2.3283064365386963e-10)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 5.421010862427522e-20)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 8507059173023461586584365e13)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 1.1754943508222875e-38)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const -8507059173023461586584365e13)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const -1.1754943508222875e-38)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 5.877471754111438e-39)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 5.877471754111438e-39)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $y32)
-  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const inf)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $y64)
-  ;; CHECK-NEXT:    (f64.const 0)
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const -inf)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $fdiv-32-power-2 (param $x f32)
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 2)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const -2)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 4294967296)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 18446744073709551616)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 0x1p-126)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 0x1p+126)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const -0x1p-126)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const -0x1p+126)
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 0x1p-127) ;; skip
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 0x1p-127) ;; skip
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const 0) ;; skip
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const nan) ;; skip
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const inf) ;; skip
+    ))
+    (drop (f32.div
+      (local.get $x)
+      (f32.const -inf) ;; skip
+    ))
+  )
+  ;; CHECK:      (func $fdiv-64-power-2 (param $x f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 0.5)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const -0.5)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $y32)
-  ;; CHECK-NEXT:    (f32.const 1)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 2.3283064365386963e-10)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $y64)
-  ;; CHECK-NEXT:    (f64.const 1)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 5.421010862427522e-20)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 4494232837155789769323262e283)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 2.2250738585072014e-308)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const -4494232837155789769323262e283)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.and
-  ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const -2.2250738585072014e-308)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 1.1125369292536007e-308)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 8988465674311579538646525e283)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $y32)
-  ;; CHECK-NEXT:    (f32.const 1)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const inf)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $y64)
-  ;; CHECK-NEXT:    (f64.const 1)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f64.const -inf)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $fdiv-64-power-2 (param $x f64)
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 2)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const -2)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 4294967296)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 18446744073709551616)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 0x1p-1022)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 0x1p+1022)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const -0x1p-1022)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const -0x1p+1022)
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 0x1p-1023) ;; skip
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 0x1p+1023) ;; skip
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const 0) ;; skip
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const nan) ;; skip
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const inf) ;; skip
+    ))
+    (drop (f64.div
+      (local.get $x)
+      (f64.const -inf) ;; skip
+    ))
+  )
+  ;; CHECK:      (func $srem-by-const (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $y32)
-  ;; CHECK-NEXT:    (f32.const 1.2000000476837158)
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.rem_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:   (i64.rem_s
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $srem-by-const (param $x i32) (param $y i64)
+    ;; (signed)x % 1
+    (drop (i32.rem_s
+      (local.get $x)
+      (i32.const 1)
+    ))
+    (drop (i64.rem_s
+      (local.get $y)
+      (i64.const 1)
+    ))
+    ;; (signed)x % 0x80000000 -> x & 0x7FFFFFFF
+    (drop (i32.rem_s
+      (local.get $x)
+      (i32.const 0x80000000)
+    ))
+    ;; (signed)x % 0x8000000000000000 -> x & 0x7FFFFFFFFFFFFFFF
+    (drop (i64.rem_s
+      (local.get $y)
+      (i64.const 0x8000000000000000)
+    ))
+  )
+  ;; CHECK:      (func $srem-by-pot-eq-ne-zero (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:    (local.get $x64)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.sub
-  ;; CHECK-NEXT:    (f32.const -0)
-  ;; CHECK-NEXT:    (local.get $y32)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 3)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.sub
-  ;; CHECK-NEXT:    (f64.const -0)
-  ;; CHECK-NEXT:    (local.get $y64)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x32)
-  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 3)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.le_u
-  ;; CHECK-NEXT:    (i32.add
-  ;; CHECK-NEXT:     (local.get $x32)
-  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 20)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x32)
-  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eq
-  ;; CHECK-NEXT:    (local.get $x64)
-  ;; CHECK-NEXT:    (i64.const 10)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x32)
-  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 3)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (i32.add
-  ;; CHECK-NEXT:     (local.get $x32)
-  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.wrap_i64
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 1)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x32)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x32)
-  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x32)
-  ;; CHECK-NEXT:     (i32.const 30)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x32)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (i32.add
-  ;; CHECK-NEXT:     (local.get $x32)
-  ;; CHECK-NEXT:     (i32.const 30)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x32)
-  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x32)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.rem_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.rem_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $abstract-additions (param $x32 i32) (param $x64 i64) (param $y32 f32) (param $y64 f64)
-    (drop
-      (i32.or
-        (i32.const 0)
-        (local.get $x32)
-      )
-    )
-    (drop
-      (i32.shl
-        (local.get $x32)
-        (i32.const 0)
+  (func $srem-by-pot-eq-ne-zero (param $x i32) (param $y i64)
+    ;; eqz((signed)x % 4)
+    (drop (i32.eqz
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 4)
       )
-    )
-    (drop
-      (i32.shr_u
-        (local.get $x32)
-        (i32.const 0)
+    ))
+    (drop (i64.eqz
+      (i64.rem_s
+        (local.get $y)
+        (i64.const 4)
       )
-    )
-    (drop
-      (i32.shr_s
-        (local.get $x32)
-        (i32.const 0)
+    ))
+    ;; eqz((signed)x % -4)
+    (drop (i32.eqz
+      (i32.rem_s
+        (local.get $x)
+        (i32.const -4)
       )
-    )
-    (drop
-      (i64.or
-        (i64.const 0)
-        (local.get $x64)
+    ))
+    (drop (i64.eqz
+      (i64.rem_s
+        (local.get $y)
+        (i64.const -4)
       )
-    )
-    (drop
-      (i64.shl
-        (local.get $x64)
-        (i64.const 0)
+    ))
+    ;; (signed)x % 4 == 0
+    (drop (i32.eq
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 4)
       )
-    )
-    (drop
-      (i64.shr_u
-        (local.get $x64)
-        (i64.const 0)
+      (i32.const 0)
+    ))
+    (drop (i64.eq
+      (i64.rem_s
+        (local.get $y)
+        (i64.const 2)
       )
-    )
-    (drop
-      (i64.shr_s
-        (local.get $x64)
-        (i64.const 0)
+      (i64.const 0)
+    ))
+    ;; (signed)x % -4 == 0
+    (drop (i32.eq
+      (i32.rem_s
+        (local.get $x)
+        (i32.const -4)
       )
-    )
-    (drop
-      (i32.mul
-        (local.get $x32)
-        (i32.const 0)
+      (i32.const 0)
+    ))
+    (drop (i64.eq
+      (i64.rem_s
+        (local.get $y)
+        (i64.const -4)
       )
-    )
-    (drop
-      (i64.mul
-        (local.get $x64)
-        (i64.const 0)
+      (i64.const 0)
+    ))
+    ;; (signed)x % 2 != 0
+    (drop (i32.ne
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 2)
       )
-    )
-    (drop
-      (f32.mul
-        (local.get $y32)
-        (f32.const 0)
+      (i32.const 0)
+    ))
+    (drop (i64.ne
+      (i64.rem_s
+        (local.get $y)
+        (i64.const 2)
       )
-    )
-    (drop
-      (f64.mul
-        (local.get $y64)
-        (f64.const 0)
+      (i64.const 0)
+    ))
+    ;; (signed)x % -1 == 0  ->  0 == 0
+    (drop (i32.eq
+      (i32.rem_s
+        (local.get $x)
+        (i32.const -1)
       )
-    )
-    (drop
-      (i32.mul
-        (local.get $x32)
-        (i32.const 1)
+      (i32.const 0)
+    ))
+    ;; (signed)x % 0x80000000 == 0
+    (drop (i32.eq
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 0x80000000)
       )
-    )
-    (drop
-      (i64.mul
-        (local.get $x64)
-        (i64.const 1)
+      (i32.const 0)
+    ))
+    ;; (signed)x % 0x80000000 != 0
+    (drop (i32.ne
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 0x80000000)
       )
-    )
-    (drop
-      (f32.mul
-        (local.get $y32)
-        (f32.const 1)
+      (i32.const 0)
+    ))
+    ;; (signed)x % 0x8000000000000000 == 0
+    (drop (i64.eq
+      (i64.rem_s
+        (local.get $y)
+        (i64.const 0x8000000000000000)
       )
-    )
-    (drop
-      (f64.mul
-        (local.get $y64)
-        (f64.const 1)
+      (i64.const 0)
+    ))
+    ;; (signed)x % 0x8000000000000000 != 0
+    (drop (i64.ne
+      (i64.rem_s
+        (local.get $y)
+        (i64.const 0x8000000000000000)
       )
-    )
-    (drop
-      (i32.and
-        (local.get $x32)
-        (i32.const 0)
+      (i64.const 0)
+    ))
+    ;;
+    (drop (i32.eq
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 3) ;; skip
       )
-    )
-    (drop
-      (i64.and
-        (local.get $x64)
-        (i64.const 0)
+      (i32.const 0)
+    ))
+    (drop (i64.eq
+      (i64.rem_s
+        (local.get $y)
+        (i64.const 3) ;; skip
       )
+      (i64.const 0)
+    ))
+  )
+  ;; CHECK:      (func $orZero (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (local.get $0)
+  ;; CHECK-NEXT: )
+  (func $orZero (param $0 i32) (result i32)
+    (i32.or
+      (local.get $0)
+      (i32.const 0)
     )
+  )
+  ;; CHECK:      (func $andZero (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (call $andZero
+  ;; CHECK-NEXT:     (i32.const 1234)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $andZero (param $0 i32) (result i32)
     (drop
       (i32.and
-        (unreachable)
+        (local.get $0)
         (i32.const 0)
       )
     )
     (drop
-      (i64.and
-        (unreachable)
-        (i64.const 0)
-      )
-    )
-    (drop
-      (i32.div_s
-        (local.get $x32)
-        (i32.const 1)
-      )
-    )
-    (drop
-      (i32.div_u
-        (local.get $x32)
-        (i32.const 1)
-      )
-    )
-    (drop
-      (i64.div_s
-        (local.get $x64)
-        (i64.const 1)
-      )
-    )
-    (drop
-      (i64.div_u
-        (local.get $x64)
-        (i64.const 1)
-      )
-    )
-    (drop
-      (f32.div
-        (local.get $y32)
-        (f32.const 1)
-      )
-    )
-    (drop
-      (f64.div
-        (local.get $y64)
-        (f64.const 1)
-      )
-    )
-    (drop
-      (f32.div
-        (local.get $y32)
-        (f32.const 1.2)
-      )
-    )
-    (drop
-      (i32.mul
-        (local.get $x32)
-        (i32.const -1)
-      )
-    )
-    (drop
-      (i64.mul
-        (local.get $x64)
-        (i64.const -1)
-      )
-    )
-    (drop
-      (f32.mul
-        (local.get $y32)
-        (f32.const -1)
-      )
-    )
-    (drop
-      (f64.mul
-        (local.get $y64)
-        (f64.const -1)
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.add
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.const 20)
-      )
-    )
-    (drop
-      (i32.le_u
-        (i32.add
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.const 20)
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.sub
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.const 20)
-      )
-    )
-    (drop
-      (i64.eq
-        (i64.add
-          (local.get $x64)
-          (i64.const 10)
-        )
-        (i64.const 20)
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.const 20)
-        (i32.add
-          (local.get $x32)
-          (i32.const 10)
-        )
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.add
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.add
-          (local.get $x32)
-          (i32.const 20)
-        )
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.sub
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.const 20)
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.add
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.sub
-          (local.get $x32)
-          (i32.const 20)
-        )
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.sub
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.add
-          (local.get $x32)
-          (i32.const 20)
-        )
-      )
-    )
-    (drop
-      (i32.eq
-        (i32.sub
-          (local.get $x32)
-          (i32.const 10)
-        )
-        (i32.sub
-          (local.get $x32)
-          (i32.const 20)
-        )
-      )
-    )
-    (drop
-      (i64.le_s
-        (i64.sub
-          (local.get $x64)
-          (i64.const 288230376151711744)
-        )
-        (i64.const 9223372036854775807)
+      (i32.and
+        (call $andZero (i32.const 1234)) ;; side effects
+        (i32.const 0)
       )
     )
+    (unreachable)
   )
-  ;; CHECK:      (func $negatives-are-sometimes-better (param $x i32) (param $y i64) (param $z f32)
+  ;; CHECK:      (func $abstract-additions (param $x32 i32) (param $x64 i64) (param $y32 f32) (param $y64 f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -64)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.add
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -64)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -8192)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1048576)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -134217728)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -64)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.add
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -64)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -8192)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -1048576)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -134217728)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -17179869184)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $y32)
+  ;; CHECK-NEXT:    (f32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -2199023255552)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $y64)
+  ;; CHECK-NEXT:    (f64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -281474976710656)
+  ;; CHECK-NEXT:   (local.get $x32)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x64)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $y32)
+  ;; CHECK-NEXT:    (f32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -36028797018963968)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $y64)
+  ;; CHECK-NEXT:    (f64.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -4611686018427387904)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.add
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (f32.const 64)
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $negatives-are-sometimes-better (param $x i32) (param $y i64) (param $z f32)
-    (drop (i32.add (local.get $x) (i32.const 0x40)))
-    (drop (i32.sub (local.get $x) (i32.const 0x40)))
-    (drop (i32.add (local.get $x) (i32.const 0x2000)))
-    (drop (i32.add (local.get $x) (i32.const 0x100000)))
-    (drop (i32.add (local.get $x) (i32.const 0x8000000)))
-
-    (drop (i64.add (local.get $y) (i64.const 0x40)))
-    (drop (i64.sub (local.get $y) (i64.const 0x40)))
-    (drop (i64.add (local.get $y) (i64.const 0x2000)))
-    (drop (i64.add (local.get $y) (i64.const 0x100000)))
-    (drop (i64.add (local.get $y) (i64.const 0x8000000)))
-
-    (drop (i64.add (local.get $y) (i64.const 0x400000000)))
-    (drop (i64.add (local.get $y) (i64.const 0x20000000000)))
-    (drop (i64.add (local.get $y) (i64.const 0x1000000000000)))
-    (drop (i64.add (local.get $y) (i64.const 0x80000000000000)))
-    (drop (i64.add (local.get $y) (i64.const 0x4000000000000000)))
-
-    (drop (f32.add (local.get $z) (f32.const 0x40)))
-  )
-  ;; CHECK:      (func $shift-a-zero (param $x i32) (param $y i64) (param $z f32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $x32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $x32)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $x64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (local.get $x64)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shl
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $y32)
+  ;; CHECK-NEXT:    (f32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $shift-a-zero (param $x i32) (param $y i64) (param $z f32)
-    (drop
-      (i32.shl
-        (i32.const 0)
-        (local.get $x)
-      )
-    )
-    (drop
-      (i32.shr_u
-        (i32.const 0)
-        (local.get $x)
-      )
-    )
-    (drop
-      (i32.shr_s
-        (i32.const 0)
-        (local.get $x)
-      )
-    )
-    (drop
-      (i64.shl
-        (i64.const 0)
-        (local.get $y)
-      )
-    )
-    (drop
-      (i32.shl
-        (i32.const 0)
-        (unreachable)
-      )
-    )
-  )
-  ;; CHECK:      (func $identical-siblings (param $x i32) (param $y i64) (param $z f64) (param $xx i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 0)
-  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.sub
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $y64)
+  ;; CHECK-NEXT:    (f64.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $xx)
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $y32)
+  ;; CHECK-NEXT:    (f32.const 1.2000000476837158)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (local.get $x32)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.add
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:    (local.get $x64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:    (local.get $y32)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const -0)
+  ;; CHECK-NEXT:    (local.get $y64)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.le_u
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (local.get $x32)
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (local.get $x64)
+  ;; CHECK-NEXT:    (i64.const 10)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (local.get $x32)
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x32)
+  ;; CHECK-NEXT:     (i32.const 30)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (local.get $x32)
+  ;; CHECK-NEXT:     (i32.const 30)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x32)
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x32)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $identical-siblings (param $x i32) (param $y i64) (param $z f64) (param $xx i32)
+  (func $abstract-additions (param $x32 i32) (param $x64 i64) (param $y32 f32) (param $y64 f64)
     (drop
-      (i32.sub
-        (local.get $x)
-        (local.get $x)
+      (i32.or
+        (i32.const 0)
+        (local.get $x32)
       )
     )
     (drop
-      (i64.sub
-        (local.get $y)
-        (local.get $y)
+      (i32.shl
+        (local.get $x32)
+        (i32.const 0)
       )
     )
     (drop
-      (f64.sub
-        (local.get $z)
-        (local.get $z)
+      (i32.shr_u
+        (local.get $x32)
+        (i32.const 0)
       )
     )
     (drop
-      (i32.sub
-        (local.get $x)
-        (local.get $xx)
+      (i32.shr_s
+        (local.get $x32)
+        (i32.const 0)
       )
     )
     (drop
-      (i32.sub
-        (unreachable)
-        (unreachable)
+      (i64.or
+        (i64.const 0)
+        (local.get $x64)
       )
     )
     (drop
-      (i32.add
-        (local.get $x)
-        (local.get $x)
+      (i64.shl
+        (local.get $x64)
+        (i64.const 0)
       )
     )
-    ;; more ops
     (drop
-      (i32.xor
-        (local.get $x)
-        (local.get $x)
+      (i64.shr_u
+        (local.get $x64)
+        (i64.const 0)
       )
     )
     (drop
-      (i32.ne
-        (local.get $x)
-        (local.get $x)
+      (i64.shr_s
+        (local.get $x64)
+        (i64.const 0)
       )
     )
     (drop
-      (i32.lt_s
-        (local.get $x)
-        (local.get $x)
+      (i32.mul
+        (local.get $x32)
+        (i32.const 0)
       )
     )
     (drop
-      (i32.lt_u
-        (local.get $x)
-        (local.get $x)
+      (i64.mul
+        (local.get $x64)
+        (i64.const 0)
       )
     )
     (drop
-      (i32.gt_s
-        (local.get $x)
-        (local.get $x)
+      (f32.mul
+        (local.get $y32)
+        (f32.const 0)
       )
     )
     (drop
-      (i32.gt_u
-        (local.get $x)
-        (local.get $x)
+      (f64.mul
+        (local.get $y64)
+        (f64.const 0)
       )
     )
     (drop
-      (i32.and
-        (local.get $x)
-        (local.get $x)
+      (i32.mul
+        (local.get $x32)
+        (i32.const 1)
       )
     )
     (drop
-      (i32.or
-        (local.get $x)
-        (local.get $x)
+      (i64.mul
+        (local.get $x64)
+        (i64.const 1)
       )
     )
     (drop
-      (i32.eq
-        (local.get $x)
-        (local.get $x)
+      (f32.mul
+        (local.get $y32)
+        (f32.const 1)
       )
     )
     (drop
-      (i32.le_s
-        (local.get $x)
-        (local.get $x)
+      (f64.mul
+        (local.get $y64)
+        (f64.const 1)
       )
     )
     (drop
-      (i32.le_u
-        (local.get $x)
-        (local.get $x)
+      (i32.and
+        (local.get $x32)
+        (i32.const 0)
       )
     )
     (drop
-      (i32.ge_s
-        (local.get $x)
-        (local.get $x)
+      (i64.and
+        (local.get $x64)
+        (i64.const 0)
       )
     )
     (drop
-      (i32.ge_u
-        (local.get $x)
-        (local.get $x)
+      (i32.and
+        (unreachable)
+        (i32.const 0)
       )
     )
     (drop
-      (i64.xor
-        (local.get $y)
-        (local.get $y)
+      (i64.and
+        (unreachable)
+        (i64.const 0)
       )
     )
     (drop
-      (i64.ne
-        (local.get $y)
-        (local.get $y)
+      (i32.div_s
+        (local.get $x32)
+        (i32.const 1)
       )
     )
     (drop
-      (i64.lt_s
-        (local.get $y)
-        (local.get $y)
+      (i32.div_u
+        (local.get $x32)
+        (i32.const 1)
       )
     )
     (drop
-      (i64.lt_u
-        (local.get $y)
-        (local.get $y)
+      (i64.div_s
+        (local.get $x64)
+        (i64.const 1)
       )
     )
     (drop
-      (i64.gt_s
-        (local.get $y)
-        (local.get $y)
+      (i64.div_u
+        (local.get $x64)
+        (i64.const 1)
       )
     )
     (drop
-      (i64.gt_u
-        (local.get $y)
-        (local.get $y)
+      (f32.div
+        (local.get $y32)
+        (f32.const 1)
       )
     )
     (drop
-      (i64.and
-        (local.get $y)
-        (local.get $y)
+      (f64.div
+        (local.get $y64)
+        (f64.const 1)
       )
     )
     (drop
-      (i64.or
-        (local.get $y)
-        (local.get $y)
+      (f32.div
+        (local.get $y32)
+        (f32.const 1.2)
       )
     )
     (drop
-      (i64.eq
-        (local.get $y)
-        (local.get $y)
+      (i32.mul
+        (local.get $x32)
+        (i32.const -1)
       )
     )
     (drop
-      (i64.le_s
-        (local.get $y)
-        (local.get $y)
+      (i64.mul
+        (local.get $x64)
+        (i64.const -1)
       )
     )
     (drop
-      (i64.le_u
-        (local.get $y)
-        (local.get $y)
+      (f32.mul
+        (local.get $y32)
+        (f32.const -1)
       )
     )
     (drop
-      (i64.ge_s
-        (local.get $y)
-        (local.get $y)
+      (f64.mul
+        (local.get $y64)
+        (f64.const -1)
       )
     )
     (drop
-      (i64.ge_u
-        (local.get $y)
-        (local.get $y)
+      (i32.eq
+        (i32.add
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.const 20)
       )
     )
-  )
-  ;; CHECK:      (func $all_ones (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const -1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.tee $x
-  ;; CHECK-NEXT:     (i32.const 1337)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const -1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const -1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $all_ones (param $x i32) (param $y i64)
     (drop
-      (i32.and
-        (local.get $x)
-        (i32.const -1)
+      (i32.le_u
+        (i32.add
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.const 20)
       )
     )
     (drop
-      (i32.or
-        (local.get $x)
-        (i32.const -1)
+      (i32.eq
+        (i32.sub
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.const 20)
       )
     )
     (drop
-      (i32.or
-        (local.tee $x
-          (i32.const 1337)
+      (i64.eq
+        (i64.add
+          (local.get $x64)
+          (i64.const 10)
         )
-        (i32.const -1)
+        (i64.const 20)
       )
     )
     (drop
-      (i64.and
-        (local.get $y)
-        (i64.const -1)
+      (i32.eq
+        (i32.const 20)
+        (i32.add
+          (local.get $x32)
+          (i32.const 10)
+        )
       )
     )
     (drop
-      (i64.or
-        (local.get $y)
-        (i64.const -1)
+      (i32.eq
+        (i32.add
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.add
+          (local.get $x32)
+          (i32.const 20)
+        )
       )
     )
-  )
-  ;; CHECK:      (func $xor (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $xor (param $x i32) (param $y i64)
     (drop
-      (i32.xor
-        (local.get $x)
-        (i32.const 0)
+      (i32.eq
+        (i32.sub
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.const 20)
       )
     )
-  )
-  ;; CHECK:      (func $select-on-const (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 3)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.tee $x
-  ;; CHECK-NEXT:    (i32.const 5)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i32)
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.tee $x
-  ;; CHECK-NEXT:      (i32.const 6)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 7)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.const 4)
-  ;; CHECK-NEXT:    (local.tee $x
-  ;; CHECK-NEXT:     (i32.const 5)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.tee $x
-  ;; CHECK-NEXT:    (i32.const 6)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
+    (drop
+      (i32.eq
+        (i32.add
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.sub
+          (local.get $x32)
+          (i32.const 20)
+        )
+      )
+    )
+    (drop
+      (i32.eq
+        (i32.sub
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.add
+          (local.get $x32)
+          (i32.const 20)
+        )
+      )
+    )
+    (drop
+      (i32.eq
+        (i32.sub
+          (local.get $x32)
+          (i32.const 10)
+        )
+        (i32.sub
+          (local.get $x32)
+          (i32.const 20)
+        )
+      )
+    )
+    (drop
+      (i64.le_s
+        (i64.sub
+          (local.get $x64)
+          (i64.const 288230376151711744)
+        )
+        (i64.const 9223372036854775807)
+      )
+    )
+  )
+  ;; CHECK:      (func $negatives-are-sometimes-better (param $x i32) (param $y i64) (param $z f32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:   (i32.add
   ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:   (i32.sub
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const -8192)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:   (i32.sub
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const -1048576)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:   (i32.sub
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const -134217728)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.gt_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.le_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i64.add
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -8192)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (i32.eqz
-  ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -1048576)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -134217728)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.eqz
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -17179869184)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (i64.eqz
-  ;; CHECK-NEXT:      (local.get $y)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -2199023255552)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.ge_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -281474976710656)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.lt_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -36028797018963968)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.lt_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -4611686018427387904)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.ge_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (f32.const 64)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $negatives-are-sometimes-better (param $x i32) (param $y i64) (param $z f32)
+    (drop (i32.add (local.get $x) (i32.const 0x40)))
+    (drop (i32.sub (local.get $x) (i32.const 0x40)))
+    (drop (i32.add (local.get $x) (i32.const 0x2000)))
+    (drop (i32.add (local.get $x) (i32.const 0x100000)))
+    (drop (i32.add (local.get $x) (i32.const 0x8000000)))
+
+    (drop (i64.add (local.get $y) (i64.const 0x40)))
+    (drop (i64.sub (local.get $y) (i64.const 0x40)))
+    (drop (i64.add (local.get $y) (i64.const 0x2000)))
+    (drop (i64.add (local.get $y) (i64.const 0x100000)))
+    (drop (i64.add (local.get $y) (i64.const 0x8000000)))
+
+    (drop (i64.add (local.get $y) (i64.const 0x400000000)))
+    (drop (i64.add (local.get $y) (i64.const 0x20000000000)))
+    (drop (i64.add (local.get $y) (i64.const 0x1000000000000)))
+    (drop (i64.add (local.get $y) (i64.const 0x80000000000000)))
+    (drop (i64.add (local.get $y) (i64.const 0x4000000000000000)))
+
+    (drop (f32.add (local.get $z) (f32.const 0x40)))
+  )
+  ;; CHECK:      (func $shift-a-zero (param $x i32) (param $y i64) (param $z f32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 2)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:    (i64.eqz
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 2)
-  ;; CHECK-NEXT:    (i64.eqz
-  ;; CHECK-NEXT:     (i64.const 2)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i32.shl
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $select-on-const (param $x i32) (param $y i64)
+  (func $shift-a-zero (param $x i32) (param $y i64) (param $z f32)
     (drop
-      (select
-        (i32.const 2)
-        (local.get $x)
+      (i32.shl
         (i32.const 0)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i32.const 3)
+      (i32.shr_u
+        (i32.const 0)
         (local.get $x)
-        (i32.const 1)
       )
     )
     (drop
-      (select
-        (i32.const 4)
-        (local.tee $x
-          (i32.const 5)
-        )
+      (i32.shr_s
         (i32.const 0)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (local.tee $x
-          (i32.const 6)
-        )
-        (i32.const 7)
-        (i32.const 0)
+      (i64.shl
+        (i64.const 0)
+        (local.get $y)
       )
     )
     (drop
-      (select
-        (i32.const 4)
-        (local.tee $x
-          (i32.const 5)
-        )
-        (i32.const 1)
+      (i32.shl
+        (i32.const 0)
+        (unreachable)
       )
     )
-    (drop
-      (select
-        (local.tee $x
-          (i32.const 6)
-        )
-        (i32.const 7)
-        (i32.const 1)
+  )
+  ;; CHECK:      (func $identical-siblings (param $x i32) (param $y i64) (param $z f64) (param $xx i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $xx)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $identical-siblings (param $x i32) (param $y i64) (param $z f64) (param $xx i32)
+    (drop
+      (i32.sub
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i32.const 1)
-        (i32.const 0)
-        (local.get $x)
+      (i64.sub
+        (local.get $y)
+        (local.get $y)
       )
     )
     (drop
-      (select
-        (i32.const 0)
-        (i32.const 1)
-        (local.get $x)
+      (f64.sub
+        (local.get $z)
+        (local.get $z)
       )
     )
     (drop
-      (select
-        (i32.const 0)
-        (i32.const 1)
-        (i32.lt_s
-          (local.get $x)
-          (i32.const 0)
-        )
+      (i32.sub
+        (local.get $x)
+        (local.get $xx)
       )
     )
     (drop
-      (select
-        (i32.const 1)
-        (i32.const 0)
-        (i32.lt_s
-          (local.get $x)
-          (i32.const 0)
-        )
+      (i32.sub
+        (unreachable)
+        (unreachable)
       )
     )
     (drop
-      (select
-        (i32.const 0)
-        (i32.const 1)
-        (i32.ge_s
-          (local.get $x)
-          (i32.const 0)
-        )
+      (i32.add
+        (local.get $x)
+        (local.get $x)
       )
     )
+    ;; more ops
     (drop
-      (select
-        (i32.const 1)
-        (i32.const 0)
-        (i32.gt_s
-          (local.get $x)
-          (i32.const 0)
-        )
+      (i32.xor
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i32.const 0)
-        (i32.const 1)
-        (i32.gt_s
-          (local.get $x)
-          (i32.const 0)
-        )
+      (i32.ne
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i32.const 1)
-        (i32.const 0)
-        (i32.ge_s
-          (local.get $x)
-          (i32.const 0)
-        )
+      (i32.lt_s
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 1)
-        (i64.const 0)
+      (i32.lt_u
+        (local.get $x)
         (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 0)
-        (i64.const 1)
+      (i32.gt_s
+        (local.get $x)
         (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 1)
-        (i64.const 0)
-        (i64.eqz
-          (local.get $y)
-        )
+      (i32.gt_u
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 0)
-        (i64.const 1)
-        (i64.eqz
-          (local.get $y)
-        )
+      (i32.and
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 0)
-        (i64.const 1)
-        (i64.lt_s
-          (local.get $y)
-          (i64.const 0)
-        )
+      (i32.or
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 1)
-        (i64.const 0)
-        (i64.lt_s
-          (local.get $y)
-          (i64.const 0)
-        )
+      (i32.eq
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 0)
-        (i64.const 1)
-        (i64.ge_s
-          (local.get $y)
-          (i64.const 0)
-        )
+      (i32.le_s
+        (local.get $x)
+        (local.get $x)
       )
     )
     (drop
-      (select
-        (i64.const 1)
-        (i64.const 0)
-        (i64.ge_s
-          (local.get $y)
-          (i64.const 0)
-        )
+      (i32.le_u
+        (local.get $x)
+        (local.get $x)
       )
     )
-    ;; optimize boolean
     (drop
-      (select
+      (i32.ge_s
+        (local.get $x)
         (local.get $x)
-        (i32.const 0)
-        (i32.eqz
-          (i32.const 0)
-        )
       )
     )
     (drop
-      (select
+      (i32.ge_u
+        (local.get $x)
         (local.get $x)
-        (i32.const 2)
-        (i32.eqz
-          (i32.const 2)
-        )
       )
     )
     (drop
-      (select
-        (local.get $x)
-        (i32.const 2)
-        (i32.eqz
-          (i32.eqz
-            (local.get $x)
-          )
-        )
+      (i64.xor
+        (local.get $y)
+        (local.get $y)
       )
     )
     (drop
-      (select
+      (i64.ne
+        (local.get $y)
         (local.get $y)
-        (i64.const 0)
-        (i64.eqz
-          (i64.const 0)
-        )
       )
     )
     (drop
-      (select
+      (i64.lt_s
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.lt_u
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.gt_s
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.gt_u
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.and
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.or
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.eq
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.le_s
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.le_u
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.ge_s
+        (local.get $y)
+        (local.get $y)
+      )
+    )
+    (drop
+      (i64.ge_u
+        (local.get $y)
         (local.get $y)
-        (i64.const 2)
-        (i64.eqz
-          (i64.const 2)
-        )
       )
     )
   )
-  ;; CHECK:      (func $optimize-boolean (param $x i32) (param $y i64)
+  ;; CHECK:      (func $all_ones (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (i32.const 2)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.tee $x
+  ;; CHECK-NEXT:     (i32.const 1337)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $y)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.eqz
-  ;; CHECK-NEXT:     (i64.and
-  ;; CHECK-NEXT:      (local.get $y)
-  ;; CHECK-NEXT:      (i64.const 1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const -1)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $all_ones (param $x i32) (param $y i64)
+    (drop
+      (i32.and
+        (local.get $x)
+        (i32.const -1)
+      )
+    )
+    (drop
+      (i32.or
+        (local.get $x)
+        (i32.const -1)
+      )
+    )
+    (drop
+      (i32.or
+        (local.tee $x
+          (i32.const 1337)
+        )
+        (i32.const -1)
+      )
+    )
+    (drop
+      (i64.and
+        (local.get $y)
+        (i64.const -1)
+      )
+    )
+    (drop
+      (i64.or
+        (local.get $y)
+        (i64.const -1)
+      )
+    )
+  )
+  ;; CHECK:      (func $xor (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.wrap_i64
-  ;; CHECK-NEXT:    (i64.shr_u
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 63)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $xor (param $x i32) (param $y i64)
+    (drop
+      (i32.xor
+        (local.get $x)
+        (i32.const 0)
+      )
+    )
+  )
+  ;; CHECK:      (func $select-on-const (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.shr_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 31)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.shr_u
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 63)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 3)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.shr_u
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 63)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (local.tee $x
+  ;; CHECK-NEXT:    (i32.const 5)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.tee $x
+  ;; CHECK-NEXT:      (i32.const 6)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 7)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 4)
+  ;; CHECK-NEXT:    (local.tee $x
+  ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.wrap_i64
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (local.tee $x
+  ;; CHECK-NEXT:    (i32.const 6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:   (i32.eqz
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.and
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const 1)
+  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:   (i32.gt_s
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:   (i32.le_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:   (i32.ge_s
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (if (result i32)
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (i32.eqz
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (if (result i32)
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i32.eqz
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.eqz
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (i64.eqz
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.ge_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.lt_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.lt_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.ge_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:    (i64.eqz
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 2)
+  ;; CHECK-NEXT:    (i64.eqz
+  ;; CHECK-NEXT:     (i64.const 2)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $optimize-boolean (param $x i32) (param $y i64)
-    ;; bool(-x) -> bool(x)
+  (func $select-on-const (param $x i32) (param $y i64)
     (drop
       (select
-        (i32.const 1)
         (i32.const 2)
-        (i32.sub
-          (i32.const 0)
-          (local.get $x)
-        )
+        (local.get $x)
+        (i32.const 0)
       )
     )
-    ;; i32(bool(expr)) == 1 -> bool(expr)
-    (drop (i32.eq
-      (i32.and
+    (drop
+      (select
+        (i32.const 3)
         (local.get $x)
         (i32.const 1)
       )
-      (i32.const 1)
-    ))
-    ;; i32(bool(expr)) != 1 -> !bool(expr)
-    (drop (i32.ne
-      (i32.and
-        (local.get $x)
+    )
+    (drop
+      (select
+        (i32.const 4)
+        (local.tee $x
+          (i32.const 5)
+        )
+        (i32.const 0)
+      )
+    )
+    (drop
+      (select
+        (local.tee $x
+          (i32.const 6)
+        )
+        (i32.const 7)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (select
+        (i32.const 4)
+        (local.tee $x
+          (i32.const 5)
+        )
         (i32.const 1)
       )
-      (i32.const 1)
-    ))
-    ;; i32(bool(expr)) ^ 1 -> !bool(expr)
-    (drop (i32.xor
-      (i32.and
-        (local.get $x)
+    )
+    (drop
+      (select
+        (local.tee $x
+          (i32.const 6)
+        )
+        (i32.const 7)
         (i32.const 1)
       )
-      (i32.const 1)
-    ))
-    ;; i64(bool(expr)) ^ 1 -> extend(!bool(expr))
-    (drop (i64.xor
-      (i64.and
-        (local.get $y)
-        (i64.const 1)
+    )
+    (drop
+      (select
+        (i32.const 1)
+        (i32.const 0)
+        (local.get $x)
       )
-      (i64.const 1)
-    ))
-    ;; i64(bool(expr)) != 0 -> i32(bool(expr))
-    (drop (i64.ne
-      (i64.shr_u
-        (local.get $y)
-        (i64.const 63)
+    )
+    (drop
+      (select
+        (i32.const 0)
+        (i32.const 1)
+        (local.get $x)
       )
-      (i64.const 0)
-    ))
-    ;; eqz((i32(bool(expr)) != 0) != 0)
-    (drop (i32.eqz
-      (i32.ne
-        (i32.ne
-          (i32.shr_u
-            (local.get $x)
-            (i32.const 31)
-          )
+    )
+    (drop
+      (select
+        (i32.const 0)
+        (i32.const 1)
+        (i32.lt_s
+          (local.get $x)
           (i32.const 0)
         )
+      )
+    )
+    (drop
+      (select
+        (i32.const 1)
         (i32.const 0)
+        (i32.lt_s
+          (local.get $x)
+          (i32.const 0)
+        )
       )
-    ))
-    ;; i32.eqz(wrap(i64(x)))
-    (drop (i32.eqz
-      (i32.wrap_i64
-        (i64.shr_u
-          (local.get $y)
-          (i64.const 63)
+    )
+    (drop
+      (select
+        (i32.const 0)
+        (i32.const 1)
+        (i32.ge_s
+          (local.get $x)
+          (i32.const 0)
         )
       )
-    ))
-    ;; eqz((i64(bool(expr)) != 0) != 0)
-    (drop (i32.eqz
-      (i32.ne
-        (i64.ne
-          (i64.shr_u
-            (local.get $y)
-            (i64.const 63)
-          )
-          (i64.const 0)
-        )
+    )
+    (drop
+      (select
+        (i32.const 1)
         (i32.const 0)
-      )
-    ))
-    ;; eqz((i64(bool(expr)) != 0) != 0)
-    (drop (i32.eqz
-      (i32.ne
-        (i64.ne
-          (local.get $y)
-          (i64.const 0)
+        (i32.gt_s
+          (local.get $x)
+          (i32.const 0)
         )
+      )
+    )
+    (drop
+      (select
         (i32.const 0)
+        (i32.const 1)
+        (i32.gt_s
+          (local.get $x)
+          (i32.const 0)
+        )
       )
-    ))
-    ;; i32.eqz(wrap(i64(x))) -> skip
-    (drop (i32.eqz
-      (i32.wrap_i64
-        (local.get $y)
+    )
+    (drop
+      (select
+        (i32.const 1)
+        (i32.const 0)
+        (i32.ge_s
+          (local.get $x)
+          (i32.const 0)
+        )
       )
-    ))
-    ;; i64(bool(expr)) == 1 -> i32(bool(expr))
-    (drop (i64.eq
-      (i64.and
-        (local.get $y)
+    )
+    (drop
+      (select
         (i64.const 1)
+        (i64.const 0)
+        (local.get $x)
       )
-      (i64.const 1)
-    ))
-    ;; i64(bool(expr)) != 1 -> !i64(bool(expr))
-    (drop (i64.ne
-      (i64.and
-        (local.get $y)
+    )
+    (drop
+      (select
+        (i64.const 0)
         (i64.const 1)
-      )
-      (i64.const 1)
-    ))
-    ;; i32(bool(expr)) & 1 -> bool(expr)
-    (drop (i32.and
-      (i32.and
         (local.get $x)
-        (i32.const 1)
       )
-      (i32.const 1)
-    ))
-    ;; i32(bool(expr)) | 1 -> 1
-    (drop (i32.or
-      (i32.and
-        (local.get $x)
-        (i32.const 1)
+    )
+    (drop
+      (select
+        (i64.const 1)
+        (i64.const 0)
+        (i64.eqz
+          (local.get $y)
+        )
       )
-      (i32.const 1)
-    ))
-    ;; i64(bool(expr)) & 1 -> i64(bool(expr))
-    (drop (i64.and
-      (i64.and
-        (local.get $y)
+    )
+    (drop
+      (select
+        (i64.const 0)
         (i64.const 1)
+        (i64.eqz
+          (local.get $y)
+        )
       )
-      (i64.const 1)
-    ))
-    ;; i64(bool(expr)) | 1 -> 1
-    (drop (i64.or
-      (i64.and
-        (local.get $y)
+    )
+    (drop
+      (select
+        (i64.const 0)
         (i64.const 1)
+        (i64.lt_s
+          (local.get $y)
+          (i64.const 0)
+        )
       )
-      (i64.const 1)
-    ))
-    ;; i32(bool(expr)) != 0 -> i32(bool(expr))
-    (drop (i32.ne
-      (i32.and
-        (local.get $x)
-        (i32.const 1)
+    )
+    (drop
+      (select
+        (i64.const 1)
+        (i64.const 0)
+        (i64.lt_s
+          (local.get $y)
+          (i64.const 0)
+        )
       )
-      (i32.const 0)
-    ))
-    ;; i32(bool(expr)) != 0 -> i32(bool(expr))
-    (drop (i32.ne
-      (i64.ne
-        (local.get $y)
+    )
+    (drop
+      (select
+        (i64.const 0)
+        (i64.const 1)
+        (i64.ge_s
+          (local.get $y)
+          (i64.const 0)
+        )
+      )
+    )
+    (drop
+      (select
+        (i64.const 1)
         (i64.const 0)
+        (i64.ge_s
+          (local.get $y)
+          (i64.const 0)
+        )
       )
-      (i32.const 0)
-    ))
-    ;; (i32(expr) != 0) != 0 -> (expr != 0)
-    (drop (i32.ne
-      (i32.ne
+    )
+    ;; optimize boolean
+    (drop
+      (select
         (local.get $x)
         (i32.const 0)
+        (i32.eqz
+          (i32.const 0)
+        )
       )
-      (i32.const 0)
-    ))
-    ;; (signed)x % 4 ? 1 : 0
-    (drop (if (result i32)
-      (i32.rem_s
+    )
+    (drop
+      (select
         (local.get $x)
-        (i32.const 4)
+        (i32.const 2)
+        (i32.eqz
+          (i32.const 2)
+        )
       )
-      (i32.const 1)
-      (i32.const 0)
-    ))
-    ;; (signed)x % min_s ? 1 : 0
-    (drop (if (result i32)
-      (i32.rem_s
+    )
+    (drop
+      (select
         (local.get $x)
-        (i32.const 0x80000000)
+        (i32.const 2)
+        (i32.eqz
+          (i32.eqz
+            (local.get $x)
+          )
+        )
       )
-      (i32.const 1)
-      (i32.const 0)
-    ))
+    )
+    (drop
+      (select
+        (local.get $y)
+        (i64.const 0)
+        (i64.eqz
+          (i64.const 0)
+        )
+      )
+    )
+    (drop
+      (select
+        (local.get $y)
+        (i64.const 2)
+        (i64.eqz
+          (i64.const 2)
+        )
+      )
+    )
   )
-  ;; CHECK:      (func $optimize-bitwise-oprations (param $x i32) (param $y i32) (param $z i64) (param $w i64)
+  ;; CHECK:      (func $optimize-boolean (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rotl
-  ;; CHECK-NEXT:    (i32.const -2)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.rotl
-  ;; CHECK-NEXT:    (i64.const -2)
-  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-bitwise-oprations (param $x i32) (param $y i32) (param $z i64) (param $w i64)
-    ;; ~(1 << x)  ->  rotl(-2, x)
-    (drop (i32.xor
-      (i32.shl
-        (i32.const 1)
-        (local.get $x)
-      )
-      (i32.const -1)
-    ))
-    (drop (i64.xor
-      (i64.shl
-        (i64.const 1)
-        (local.get $z)
-      )
-      (i64.const -1)
-    ))
-  )
-  ;; CHECK:      (func $getFallthrough
-  ;; CHECK-NEXT:  (local $x0 i32)
-  ;; CHECK-NEXT:  (local $x1 i32)
-  ;; CHECK-NEXT:  (local $x2 i32)
-  ;; CHECK-NEXT:  (local $x3 i32)
-  ;; CHECK-NEXT:  (local $x4 i32)
-  ;; CHECK-NEXT:  (local $x5 i32)
-  ;; CHECK-NEXT:  (local $x6 i32)
-  ;; CHECK-NEXT:  (local $x7 i32)
-  ;; CHECK-NEXT:  (local.set $x0
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $x1
-  ;; CHECK-NEXT:   (local.tee $x2
-  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $x3
-  ;; CHECK-NEXT:   (loop $loop-in (result i32)
-  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x3)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $x4
-  ;; CHECK-NEXT:   (if (result i32)
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (i32.const 2)
-  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.eqz
+  ;; CHECK-NEXT:     (i64.and
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:      (i64.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x4)
-  ;; CHECK-NEXT:    (i32.const 7)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $x5
-  ;; CHECK-NEXT:   (if (result i32)
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   (i32.wrap_i64
+  ;; CHECK-NEXT:    (i64.shr_u
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 63)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x5)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $x6
-  ;; CHECK-NEXT:   (if (result i32)
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (i32.const 3)
-  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.shr_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 31)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x6)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block $out (result i32)
-  ;; CHECK-NEXT:    (local.set $x7
-  ;; CHECK-NEXT:     (br_if $out
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $x7)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.shr_u
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 63)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $getFallthrough ;; unit tests for Properties::getFallthrough
-    (local $x0 i32)
-    (local $x1 i32)
-    (local $x2 i32)
-    (local $x3 i32)
-    (local $x4 i32)
-    (local $x5 i32)
-    (local $x6 i32)
-    (local $x7 i32)
-    ;; the trivial case
-    (local.set $x0 (i32.const 1))
-    (drop (i32.and (local.get $x0) (i32.const 7)))
-    ;; tees
-    (local.set $x1 (local.tee $x2 (i32.const 1)))
-    (drop (i32.and (local.get $x1) (i32.const 7)))
-    ;; loop
-    (local.set $x3 (loop (result i32) (i32.const 1)))
-    (drop (i32.and (local.get $x3) (i32.const 7)))
-    ;; if - two sides, can't
-    (local.set $x4 (if (result i32) (i32.const 1) (i32.const 2) (i32.const 3)))
-    (drop (i32.and (local.get $x4) (i32.const 7)))
-    ;; if - one side, can
-    (local.set $x5 (if (result i32) (i32.const 1) (unreachable) (i32.const 3)))
-    (drop (i32.and (local.get $x5) (i32.const 7)))
-    ;; if - one side, can
-    (local.set $x6 (if (result i32) (i32.const 1) (i32.const 3) (unreachable)))
-    (drop (i32.and (local.get $x6) (i32.const 7)))
-    ;; br_if with value
-    (drop
-      (block $out (result i32)
-        (local.set $x7 (br_if $out (i32.const 1) (i32.const 1)))
-        (drop (i32.and (local.get $x7) (i32.const 7)))
-        (unreachable)
-      )
-    )
-  )
-  ;; CHECK:      (func $tee-with-unreachable-value (result f64)
-  ;; CHECK-NEXT:  (local $var$0 i32)
-  ;; CHECK-NEXT:  (block $label$1 (result f64)
-  ;; CHECK-NEXT:   (local.tee $var$0
-  ;; CHECK-NEXT:    (br_if $label$1
-  ;; CHECK-NEXT:     (f64.const 1)
-  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.shr_u
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 63)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $tee-with-unreachable-value (result f64)
-   (local $var$0 i32)
-   (block $label$1 (result f64)
-    (local.tee $var$0
-     (br_if $label$1 ;; the f64 does not actually flow through this, it's unreachable (and the type is wrong - but unchecked)
-      (f64.const 1)
-      (unreachable)
-     )
-    )
-   )
-  )
-  ;; CHECK:      (func $add-sub-zero-reorder-1 (param $temp i32) (result i32)
-  ;; CHECK-NEXT:  (i32.add
-  ;; CHECK-NEXT:   (i32.add
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:     (local.get $temp)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.tee $temp
-  ;; CHECK-NEXT:     (i32.const 1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $add-sub-zero-reorder-1 (param $temp i32) (result i32)
-   (i32.add
-    (i32.add
-     (i32.sub
-      (i32.const 0) ;; this zero looks like we could remove it by subtracting the get of $temp from the parent, but that would reorder it *after* the tee :(
-      (local.get $temp)
-     )
-     (local.tee $temp ;; cannot move this tee before the get
-      (i32.const 1)
-     )
-    )
-    (i32.const 2)
-   )
-  )
-  ;; CHECK:      (func $add-sub-zero-reorder-2 (param $temp i32) (result i32)
-  ;; CHECK-NEXT:  (i32.add
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.tee $temp
-  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $temp)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $add-sub-zero-reorder-2 (param $temp i32) (result i32)
-   (i32.add
-    (i32.add
-     (local.tee $temp ;; in this order, the tee already comes first, so all is good for the optimization
-      (i32.const 1)
-     )
-     (i32.sub
-      (i32.const 0)
-      (local.get $temp)
-     )
-    )
-    (i32.const 2)
-   )
-  )
-  ;; CHECK:      (func $const-float-zero (param $fx f32) (param $fy f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.sub
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   (i32.wrap_i64
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.sub
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const 0)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.add
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.add
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const -0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.add
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.add
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.sub
-  ;; CHECK-NEXT:    (f32.const 0)
-  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.sub
-  ;; CHECK-NEXT:    (f64.const 0)
-  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.add
-  ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.add
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const 0)
+  ;; CHECK-NEXT:   (if (result i32)
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 3)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.sub
-  ;; CHECK-NEXT:    (f32.const -nan:0x34546d)
-  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   (if (result i32)
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $const-float-zero (param $fx f32) (param $fy f64)
-    ;; x - 0.0   ==>   x
-    (drop (f32.sub
-      (local.get $fx)
-      (f32.const 0)
+  (func $optimize-boolean (param $x i32) (param $y i64)
+    ;; bool(-x) -> bool(x)
+    (drop
+      (select
+        (i32.const 1)
+        (i32.const 2)
+        (i32.sub
+          (i32.const 0)
+          (local.get $x)
+        )
+      )
+    )
+    ;; i32(bool(expr)) == 1 -> bool(expr)
+    (drop (i32.eq
+      (i32.and
+        (local.get $x)
+        (i32.const 1)
+      )
+      (i32.const 1)
     ))
-    (drop (f64.sub
-      (local.get $fy)
-      (f64.const 0)
+    ;; i32(bool(expr)) != 1 -> !bool(expr)
+    (drop (i32.ne
+      (i32.and
+        (local.get $x)
+        (i32.const 1)
+      )
+      (i32.const 1)
     ))
-    ;; x + (-0.0)   ==>   x
-    (drop (f32.add
-      (local.get $fx)
-      (f32.const -0)
+    ;; i32(bool(expr)) ^ 1 -> !bool(expr)
+    (drop (i32.xor
+      (i32.and
+        (local.get $x)
+        (i32.const 1)
+      )
+      (i32.const 1)
     ))
-    (drop (f64.add
-      (local.get $fy)
-      (f64.const -0)
+    ;; i64(bool(expr)) ^ 1 -> extend(!bool(expr))
+    (drop (i64.xor
+      (i64.and
+        (local.get $y)
+        (i64.const 1)
+      )
+      (i64.const 1)
     ))
-    ;; x - (-0.0)   ==>   x + 0.0
-    (drop (f32.sub
-      (local.get $fx)
-      (f32.const -0) ;; skip
+    ;; i64(bool(expr)) != 0 -> i32(bool(expr))
+    (drop (i64.ne
+      (i64.shr_u
+        (local.get $y)
+        (i64.const 63)
+      )
+      (i64.const 0)
     ))
-    (drop (f64.sub
-      (local.get $fy)
-      (f64.const -0) ;; skip
+    ;; eqz((i32(bool(expr)) != 0) != 0)
+    (drop (i32.eqz
+      (i32.ne
+        (i32.ne
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 31)
+          )
+          (i32.const 0)
+        )
+        (i32.const 0)
+      )
     ))
-    ;; 0.0 - x   ==>   0.0 - x
-    (drop (f32.sub
-      (f32.const 0)
-      (local.get $fx) ;; skip
+    ;; i32.eqz(wrap(i64(x)))
+    (drop (i32.eqz
+      (i32.wrap_i64
+        (i64.shr_u
+          (local.get $y)
+          (i64.const 63)
+        )
+      )
     ))
-    (drop (f64.sub
-      (f64.const 0)
-      (local.get $fy) ;; skip
+    ;; eqz((i64(bool(expr)) != 0) != 0)
+    (drop (i32.eqz
+      (i32.ne
+        (i64.ne
+          (i64.shr_u
+            (local.get $y)
+            (i64.const 63)
+          )
+          (i64.const 0)
+        )
+        (i32.const 0)
+      )
     ))
-    ;; x + 0.0   ==>   x + 0.0
-    (drop (f32.add
-      (local.get $fx) ;; skip
-      (f32.const 0)
+    ;; eqz((i64(bool(expr)) != 0) != 0)
+    (drop (i32.eqz
+      (i32.ne
+        (i64.ne
+          (local.get $y)
+          (i64.const 0)
+        )
+        (i32.const 0)
+      )
     ))
-    (drop (f64.add
-      (local.get $fy) ;; skip
-      (f64.const 0)
+    ;; i32.eqz(wrap(i64(x))) -> skip
+    (drop (i32.eqz
+      (i32.wrap_i64
+        (local.get $y)
+      )
     ))
-    (drop (f32.sub
-      (f32.const -nan:0x34546d) ;; skip
-      (f32.const 0)
+    ;; i64(bool(expr)) == 1 -> i32(bool(expr))
+    (drop (i64.eq
+      (i64.and
+        (local.get $y)
+        (i64.const 1)
+      )
+      (i64.const 1)
+    ))
+    ;; i64(bool(expr)) != 1 -> !i64(bool(expr))
+    (drop (i64.ne
+      (i64.and
+        (local.get $y)
+        (i64.const 1)
+      )
+      (i64.const 1)
+    ))
+    ;; i32(bool(expr)) & 1 -> bool(expr)
+    (drop (i32.and
+      (i32.and
+        (local.get $x)
+        (i32.const 1)
+      )
+      (i32.const 1)
+    ))
+    ;; i32(bool(expr)) | 1 -> 1
+    (drop (i32.or
+      (i32.and
+        (local.get $x)
+        (i32.const 1)
+      )
+      (i32.const 1)
+    ))
+    ;; i64(bool(expr)) & 1 -> i64(bool(expr))
+    (drop (i64.and
+      (i64.and
+        (local.get $y)
+        (i64.const 1)
+      )
+      (i64.const 1)
+    ))
+    ;; i64(bool(expr)) | 1 -> 1
+    (drop (i64.or
+      (i64.and
+        (local.get $y)
+        (i64.const 1)
+      )
+      (i64.const 1)
+    ))
+    ;; i32(bool(expr)) != 0 -> i32(bool(expr))
+    (drop (i32.ne
+      (i32.and
+        (local.get $x)
+        (i32.const 1)
+      )
+      (i32.const 0)
+    ))
+    ;; i32(bool(expr)) != 0 -> i32(bool(expr))
+    (drop (i32.ne
+      (i64.ne
+        (local.get $y)
+        (i64.const 0)
+      )
+      (i32.const 0)
+    ))
+    ;; (i32(expr) != 0) != 0 -> (expr != 0)
+    (drop (i32.ne
+      (i32.ne
+        (local.get $x)
+        (i32.const 0)
+      )
+      (i32.const 0)
+    ))
+    ;; (signed)x % 4 ? 1 : 0
+    (drop (if (result i32)
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 4)
+      )
+      (i32.const 1)
+      (i32.const 0)
+    ))
+    ;; (signed)x % min_s ? 1 : 0
+    (drop (if (result i32)
+      (i32.rem_s
+        (local.get $x)
+        (i32.const 0x80000000)
+      )
+      (i32.const 1)
+      (i32.const 0)
     ))
   )
-  ;; CHECK:      (func $rhs-is-neg-one (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
+  ;; CHECK:      (func $fold-eqz-eqz (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.add
+  ;; CHECK-NEXT:   (i64.ne
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $fold-eqz-eqz (param $x i32) (param $y i64)
+    ;; i32.eqz(i32.eqz(x))  ->  x != 0
+    (drop (i32.eqz (i32.eqz (local.get $x))))
+    ;; i32.eqz(i64.eqz(y))  ->  y != 0
+    (drop (i32.eqz (i64.eqz (local.get $y))))
+  )
+  ;; CHECK:      (func $optimize-bitwise-oprations (param $x i32) (param $y i32) (param $z i64) (param $w i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.rotl
+  ;; CHECK-NEXT:    (i32.const -2)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i64.rotl
+  ;; CHECK-NEXT:    (i64.const -2)
+  ;; CHECK-NEXT:    (local.get $z)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-bitwise-oprations (param $x i32) (param $y i32) (param $z i64) (param $w i64)
+    ;; ~(1 << x)  ->  rotl(-2, x)
+    (drop (i32.xor
+      (i32.shl
+        (i32.const 1)
+        (local.get $x)
+      )
+      (i32.const -1)
+    ))
+    (drop (i64.xor
+      (i64.shl
+        (i64.const 1)
+        (local.get $z)
+      )
+      (i64.const -1)
+    ))
+  )
+  ;; CHECK:      (func $getFallthrough
+  ;; CHECK-NEXT:  (local $x0 i32)
+  ;; CHECK-NEXT:  (local $x1 i32)
+  ;; CHECK-NEXT:  (local $x2 i32)
+  ;; CHECK-NEXT:  (local $x3 i32)
+  ;; CHECK-NEXT:  (local $x4 i32)
+  ;; CHECK-NEXT:  (local $x5 i32)
+  ;; CHECK-NEXT:  (local $x6 i32)
+  ;; CHECK-NEXT:  (local $x7 i32)
+  ;; CHECK-NEXT:  (local.set $x0
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ge_s
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:   (local.get $x0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x1
+  ;; CHECK-NEXT:   (local.tee $x2
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_s
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $x1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x3
+  ;; CHECK-NEXT:   (loop $loop-in (result i32)
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (local.get $x3)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x4
+  ;; CHECK-NEXT:   (if (result i32)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x4)
+  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x5
+  ;; CHECK-NEXT:   (if (result i32)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (local.get $x5)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x6
+  ;; CHECK-NEXT:   (if (result i32)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.lt_s
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:   (local.get $x6)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block $out (result i32)
+  ;; CHECK-NEXT:    (local.set $x7
+  ;; CHECK-NEXT:     (br_if $out
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x7)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $getFallthrough ;; unit tests for Properties::getFallthrough
+    (local $x0 i32)
+    (local $x1 i32)
+    (local $x2 i32)
+    (local $x3 i32)
+    (local $x4 i32)
+    (local $x5 i32)
+    (local $x6 i32)
+    (local $x7 i32)
+    ;; the trivial case
+    (local.set $x0 (i32.const 1))
+    (drop (i32.and (local.get $x0) (i32.const 7)))
+    ;; tees
+    (local.set $x1 (local.tee $x2 (i32.const 1)))
+    (drop (i32.and (local.get $x1) (i32.const 7)))
+    ;; loop
+    (local.set $x3 (loop (result i32) (i32.const 1)))
+    (drop (i32.and (local.get $x3) (i32.const 7)))
+    ;; if - two sides, can't
+    (local.set $x4 (if (result i32) (i32.const 1) (i32.const 2) (i32.const 3)))
+    (drop (i32.and (local.get $x4) (i32.const 7)))
+    ;; if - one side, can
+    (local.set $x5 (if (result i32) (i32.const 1) (unreachable) (i32.const 3)))
+    (drop (i32.and (local.get $x5) (i32.const 7)))
+    ;; if - one side, can
+    (local.set $x6 (if (result i32) (i32.const 1) (i32.const 3) (unreachable)))
+    (drop (i32.and (local.get $x6) (i32.const 7)))
+    ;; br_if with value
+    (drop
+      (block $out (result i32)
+        (local.set $x7 (br_if $out (i32.const 1) (i32.const 1)))
+        (drop (i32.and (local.get $x7) (i32.const 7)))
+        (unreachable)
+      )
+    )
+  )
+  ;; CHECK:      (func $tee-with-unreachable-value (result f64)
+  ;; CHECK-NEXT:  (local $var$0 i32)
+  ;; CHECK-NEXT:  (block $label$1 (result f64)
+  ;; CHECK-NEXT:   (local.tee $var$0
+  ;; CHECK-NEXT:    (br_if $label$1
+  ;; CHECK-NEXT:     (f64.const 1)
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $tee-with-unreachable-value (result f64)
+   (local $var$0 i32)
+   (block $label$1 (result f64)
+    (local.tee $var$0
+     (br_if $label$1 ;; the f64 does not actually flow through this, it's unreachable (and the type is wrong - but unchecked)
+      (f64.const 1)
+      (unreachable)
+     )
+    )
+   )
+  )
+  ;; CHECK:      (func $add-sub-zero-reorder-1 (param $temp i32) (result i32)
+  ;; CHECK-NEXT:  (i32.add
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:     (local.get $temp)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.tee $temp
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $add-sub-zero-reorder-1 (param $temp i32) (result i32)
+   (i32.add
+    (i32.add
+     (i32.sub
+      (i32.const 0) ;; this zero looks like we could remove it by subtracting the get of $temp from the parent, but that would reorder it *after* the tee :(
+      (local.get $temp)
+     )
+     (local.tee $temp ;; cannot move this tee before the get
+      (i32.const 1)
+     )
+    )
+    (i32.const 2)
+   )
+  )
+  ;; CHECK:      (func $add-sub-zero-reorder-2 (param $temp i32) (result i32)
+  ;; CHECK-NEXT:  (i32.add
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (local.tee $temp
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $temp)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 2)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $add-sub-zero-reorder-2 (param $temp i32) (result i32)
+   (i32.add
+    (i32.add
+     (local.tee $temp ;; in this order, the tee already comes first, so all is good for the optimization
+      (i32.const 1)
+     )
+     (i32.sub
+      (i32.const 0)
+      (local.get $temp)
+     )
+    )
+    (i32.const 2)
+   )
+  )
+  ;; CHECK:      (func $const-float-zero (param $fx f32) (param $fy f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f32.const -0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eq
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:   (f64.add
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const -0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f32.const -0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:   (f64.add
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const -0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (f64.add
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (f32.sub
-  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:    (f32.const 0)
   ;; CHECK-NEXT:    (local.get $fx)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (f64.sub
-  ;; CHECK-NEXT:    (f64.const -0)
+  ;; CHECK-NEXT:    (f64.const 0)
   ;; CHECK-NEXT:    (local.get $fy)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.eq
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const -1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.add
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (f32.const -nan:0x34546d)
+  ;; CHECK-NEXT:    (f32.const -0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $rhs-is-neg-one (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
-    (drop (i32.sub
-      (local.get $x)
-      (i32.const -1)
-    ))
-    (drop (i64.sub
-      (local.get $y)
-      (i64.const -1)
-    ))
-    ;; (unsigned)x > -1   ==>   0
-    (drop (i32.gt_u
-      (local.get $x)
-      (i32.const -1)
-    ))
-    (drop (i64.gt_u
-      (local.get $y)
-      (i64.const -1)
-    ))
-    (drop (i32.gt_s
-      (local.get $x)
-      (i32.const -1)
-    ))
-    (drop (i64.gt_s
-      (local.get $y)
-      (i64.const -1)
-    ))
-    (drop (i64.extend_i32_s
-      (i64.gt_u
-        (i64.const 0)
-        (i64.const -1)
-      )
-    ))
-    ;; (unsigned)x <= -1   ==>   1
-    (drop (i32.le_u
-      (local.get $x)
-      (i32.const -1)
-    ))
-    (drop (i64.le_u
-      (local.get $y)
-      (i64.const -1)
-    ))
-    (drop (i32.le_s
-      (local.get $x)
-      (i32.const -1)
-    ))
-    (drop (i64.le_s
-      (local.get $y)
-      (i64.const -1)
+  (func $const-float-zero (param $fx f32) (param $fy f64)
+    ;; x - 0.0   ==>   x
+    (drop (f32.sub
+      (local.get $fx)
+      (f32.const 0)
     ))
-    ;; (unsigned)x >= -1   ==>   x == -1
-    (drop (i32.ge_u
-      (local.get $x)
-      (i32.const -1)
+    (drop (f64.sub
+      (local.get $fy)
+      (f64.const 0)
     ))
-    (drop (i64.ge_u
-      (local.get $y)
-      (i64.const -1)
+    ;; x + (-0.0)   ==>   x
+    (drop (f32.add
+      (local.get $fx)
+      (f32.const -0)
     ))
-    ;; (unsigned)x < -1   ==>   x != -1
-    (drop (i32.lt_u
-      (local.get $x)
-      (i32.const -1)
+    (drop (f64.add
+      (local.get $fy)
+      (f64.const -0)
     ))
-    (drop (i64.lt_u
-      (local.get $y)
-      (i64.const -1)
+    ;; x - (-0.0)   ==>   x + 0.0
+    (drop (f32.sub
+      (local.get $fx)
+      (f32.const -0) ;; skip
     ))
-    ;; x * -1
-    (drop (i32.mul
-      (local.get $x)
-      (i32.const -1)
+    (drop (f64.sub
+      (local.get $fy)
+      (f64.const -0) ;; skip
     ))
-    (drop (i64.mul
-      (local.get $y)
-      (i64.const -1)
+    ;; 0.0 - x   ==>   0.0 - x
+    (drop (f32.sub
+      (f32.const 0)
+      (local.get $fx) ;; skip
     ))
-    (drop (f32.mul    ;; skip
-      (local.get $fx)
-      (f32.const -1)
+    (drop (f64.sub
+      (f64.const 0)
+      (local.get $fy) ;; skip
     ))
-    (drop (f64.mul    ;; skip
-      (local.get $fy)
-      (f64.const -1)
+    ;; x + 0.0   ==>   x + 0.0
+    (drop (f32.add
+      (local.get $fx) ;; skip
+      (f32.const 0)
     ))
-    ;; (unsigned)x / -1
-    (drop (i32.div_u
-      (local.get $x)
-      (i32.const -1)
+    (drop (f64.add
+      (local.get $fy) ;; skip
+      (f64.const 0)
     ))
-    (drop (i64.div_u
-      (local.get $y)
-      (i64.const -1)
+    (drop (f32.sub
+      (f32.const -nan:0x34546d) ;; skip
+      (f32.const 0)
     ))
   )
-  ;; CHECK:      (func $rhs-is-const (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
+
+  ;; CHECK:      (func $simplify-add-sub-with-neg-float (param $a f32) (param $b f32) (param $x f64) (param $y f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (local.get $a)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.eq
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const -9223372036854775808)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.div_s
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -2147483648)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (local.get $b)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:   (f64.sub
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2)
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (local.get $b)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:   (f64.add
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483647)
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shr_u
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 31)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.neg
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $b)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i64.eq
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const -1)
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.neg
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shr_u
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 63)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (f32.neg
+  ;; CHECK-NEXT:     (local.tee $a
+  ;; CHECK-NEXT:      (f32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $a)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify-add-sub-with-neg-float (param $a f32) (param $b f32) (param $x f64) (param $y f64)
+    ;; -x + y     ==>   y - x
+    (drop (f32.add (f32.neg (local.get $a)) (local.get $b)))
+    (drop (f64.add (f64.neg (local.get $x)) (local.get $y)))
+
+    ;; x + (-y)   ==>   x - y
+    (drop (f32.add (local.get $a) (f32.neg (local.get $b))))
+    (drop (f64.add (local.get $x) (f64.neg (local.get $y))))
+
+    ;; x - (-y)   ==>   x + y
+    (drop (f32.sub (local.get $a) (f32.neg (local.get $b))))
+    (drop (f64.sub (local.get $x) (f64.neg (local.get $y))))
+
+    ;; skips
+    ;; -x - y  ->  skip
+    (drop (f32.sub (f32.neg (local.get $a)) (local.get $b)))
+    (drop (f64.sub (f64.neg (local.get $x)) (local.get $y)))
+    ;; -x + y  ->  skip,  iff can't reorder
+    (drop (f32.add (f32.neg (local.tee $a (f32.const 1.0))) (local.get $a)))
+  )
+  ;; CHECK:      (func $simplify-add-sub-with-neg-float-zeros (param $a f32) (param $b f32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:    (local.get $b)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const -0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:    (local.get $b)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify-add-sub-with-neg-float-zeros (param $a f32) (param $b f32)
+    ;; edge cases for -x + y
+    (drop (f32.add (f32.neg (f32.const 0)) (local.get $b)))
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const 0)))
+    (drop (f32.add (f32.neg (f32.const -0)) (local.get $b)))
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const -0)))
+    ;; edge cases for x + (-y)
+    (drop (f32.add (f32.const 0) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const 0))))
+    (drop (f32.add (f32.const -0) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const -0))))
+    ;; edge cases for x - (-y)
+    (drop (f32.sub (f32.const 0) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const 0))))
+    (drop (f32.sub (f32.const -0) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const -0))))
+  )
+  ;; CHECK:      (func $simplify-add-sub-with-neg-float-nans (param $a f32) (param $b f32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const nan:0x400000)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -nan:0x400000)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const nan:0x400000)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -nan:0x400000)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (f32.const nan:0x400000)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (f32.const -nan:0x400000)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify-add-sub-with-neg-float-nans (param $a f32) (param $b f32)
+    ;; edge cases for -x + y
+    (drop (f32.add (f32.neg (f32.const nan)) (local.get $b)))
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const nan)))
+    (drop (f32.add (f32.neg (f32.const -nan)) (local.get $b)))
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const -nan)))
+    ;; edge cases for x + (-y)
+    (drop (f32.add (f32.const nan) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const nan))))
+    (drop (f32.add (f32.const -nan) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const -nan))))
+    ;; edge cases for x - (-y)
+    (drop (f32.sub (f32.const nan) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const nan))))
+    (drop (f32.sub (f32.const -nan) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const -nan))))
+  )
+  ;; CHECK:      (func $simplify-add-sub-with-neg-float-infs (param $a f32) (param $b f32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const -inf)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify-add-sub-with-neg-float-infs (param $a f32) (param $b f32)
+    ;; edge cases for -x + y
+    (drop (f32.add (f32.neg (f32.const inf)) (local.get $b)))
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const inf)))
+    (drop (f32.add (f32.neg (f32.const -inf)) (local.get $b)))
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const -inf)))
+    ;; edge cases for x + (-y)
+    (drop (f32.add (f32.const inf) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const inf))))
+    (drop (f32.add (f32.const -inf) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const -inf))))
+    ;; edge cases for x - (-y)
+    (drop (f32.sub (f32.const inf) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const inf))))
+    (drop (f32.sub (f32.const -inf) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const -inf))))
+  )
+  ;; CHECK:      (func $simplify-add-sub-with-neg-float-mins (param $a f32) (param $b f32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const -1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const 1.401298464324817e-45)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const 1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -1.401298464324817e-45)
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const 1.401298464324817e-45)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const -1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -1.401298464324817e-45)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const 1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const 1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const 1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (f32.const -1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (f32.const -1.401298464324817e-45)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify-add-sub-with-neg-float-mins (param $a f32) (param $b f32)
+    ;; edge cases for -x + y
+    (drop (f32.add (f32.neg (f32.const 1.401298464324817e-45)) (local.get $b)))  ;; +min value
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const 1.401298464324817e-45)))  ;; +min value
+    (drop (f32.add (f32.neg (f32.const -1.401298464324817e-45)) (local.get $b))) ;; -min value
+    (drop (f32.add (f32.neg (local.get $a)) (f32.const -1.401298464324817e-45))) ;; -min value
+    ;; edge cases for x + (-y)
+    (drop (f32.add (f32.const 1.401298464324817e-45) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const 1.401298464324817e-45))))
+    (drop (f32.add (f32.const -1.401298464324817e-45) (f32.neg (local.get $b))))
+    (drop (f32.add (local.get $a) (f32.neg (f32.const -1.401298464324817e-45))))
+    ;; edge cases for x - (-y)
+    (drop (f32.sub (f32.const 1.401298464324817e-45) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const 1.401298464324817e-45))))
+    (drop (f32.sub (f32.const -1.401298464324817e-45) (f32.neg (local.get $b))))
+    (drop (f32.sub (local.get $a) (f32.neg (f32.const -1.401298464324817e-45))))
+  )
+  ;; CHECK:      (func $rhs-is-neg-one (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.add
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 2147483647)
+  ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:   (i64.add
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:    (i64.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_s
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:   (i64.ge_s
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:   (i64.extend_i32_s
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.lt_s
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:   (i64.lt_s
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.eq
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 2147483647)
+  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i64.eq
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:    (i64.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.sub
-  ;; CHECK-NEXT:    (f32.const -0)
-  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const 2.1)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const -2)
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -0)
   ;; CHECK-NEXT:    (local.get $fx)
-  ;; CHECK-NEXT:    (f32.const -inf)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const -0)
   ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $fy)
-  ;; CHECK-NEXT:    (f64.const -nan:0x8000000000000)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (f64.const -5)
-  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.eq
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const -1)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $rhs-is-const (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
-    ;; signed divs
-    ;; i32(x) / -2147483648  ->  x == -2147483648
-    (drop (i32.div_s
+  (func $rhs-is-neg-one (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
+    (drop (i32.sub
       (local.get $x)
-      (i32.const -2147483648)
+      (i32.const -1)
     ))
-    ;; i64(x) / -9223372036854775808  ->  x == -9223372036854775808
-    (drop (i64.div_s
+    (drop (i64.sub
       (local.get $y)
-      (i64.const -9223372036854775808)
+      (i64.const -1)
     ))
-    ;; skip
-    (drop (i64.div_s
+    ;; (unsigned)x > -1   ==>   0
+    (drop (i32.gt_u
+      (local.get $x)
+      (i32.const -1)
+    ))
+    (drop (i64.gt_u
       (local.get $y)
-      (i64.const -2147483648)
+      (i64.const -1)
     ))
-
-    ;; unsigned divs
-    ;; u32(x) / -2  =>  x >= -2
-    (drop (i32.div_u
-      (local.get $x)
-      (i32.const -2)
-    ))
-    ;; u32(x) / -1  =>  x == -1
-    (drop (i32.div_u
+    (drop (i32.gt_s
       (local.get $x)
       (i32.const -1)
     ))
-    ;; u32(x) / (i32.min + 1)
-    (drop (i32.div_u
-      (local.get $x)
-      (i32.const -2147483647)
-    ))
-    ;; u32(x) / i32.min  =>  x >>> 31
-    (drop (i32.div_u
-      (local.get $x)
-      (i32.const -2147483648)
-    ))
-    ;; u64(x) / -1  =>  u64(x == -1)
-    (drop (i64.div_u
+    (drop (i64.gt_s
       (local.get $y)
       (i64.const -1)
     ))
-    ;; u64(x) / i64.min  =>  x >>> 63
-    (drop (i64.div_u
-      (local.get $y)
-      (i64.const -9223372036854775808)
-    ))
-
-    ;; (unsigned)x >= 0  =>  i32(1)
-    (drop (i32.ge_u
-      (local.get $x)
-      (i32.const 0)
-    ))
-    (drop (i64.ge_u
-      (local.get $y)
-      (i64.const 0)
-    ))
-
-    ;; (unsigned)x < 0  =>  i32(0)
-    (drop (i32.lt_u
-      (local.get $x)
-      (i32.const 0)
-    ))
-    (drop (i64.lt_u
-      (local.get $y)
-      (i64.const 0)
-    ))
-
-    ;; (unsigned)x > 0  =>  x != 0
-    (drop (i32.gt_u
-      (local.get $x)
-      (i32.const 0)
-    ))
-    (drop (i64.gt_u
-      (local.get $y)
-      (i64.const 0)
+    (drop (i64.extend_i32_s
+      (i64.gt_u
+        (i64.const 0)
+        (i64.const -1)
+      )
     ))
-
-    ;; (unsigned)x <= 0  =>  x == 0
+    ;; (unsigned)x <= -1   ==>   1
     (drop (i32.le_u
       (local.get $x)
-      (i32.const 0)
+      (i32.const -1)
     ))
     (drop (i64.le_u
       (local.get $y)
-      (i64.const 0)
+      (i64.const -1)
     ))
-
-    ;; i32(x) <= 0x7fffffff  =>  i32(1)
     (drop (i32.le_s
       (local.get $x)
-      (i32.const 0x7fffffff)
+      (i32.const -1)
     ))
-    ;; i64(x) <= 0x7fffffffffffffff  =>  i32(1)
     (drop (i64.le_s
       (local.get $y)
-      (i64.const 0x7fffffffffffffff)
-    ))
-
-    ;; i32(x) >= 0x80000000  =>  i32(1)
-    (drop (i32.ge_s
-      (local.get $x)
-      (i32.const 0x80000000)
-    ))
-    ;; i64(x) >= 0x8000000000000000  =>  i32(1)
-    (drop (i64.ge_s
-      (local.get $y)
-      (i64.const 0x8000000000000000)
-    ))
-
-    ;; i32(x) < 0x80000000  =>  0
-    (drop (i32.lt_s
-      (local.get $x)
-      (i32.const 0x80000000)
-    ))
-    ;; i64(x) < 0x8000000000000000  =>  0
-    (drop (i64.lt_s
-      (local.get $y)
-      (i64.const 0x8000000000000000)
+      (i64.const -1)
     ))
-
-    ;; i32(x) > 0x7fffffff  =>  0
-    (drop (i32.gt_s
+    ;; (unsigned)x >= -1   ==>   x == -1
+    (drop (i32.ge_u
       (local.get $x)
-      (i32.const 0x7fffffff)
+      (i32.const -1)
     ))
-    ;; i64(x) > 0x7fffffffffffffff  =>  0
-    (drop (i64.gt_s
+    (drop (i64.ge_u
       (local.get $y)
-      (i64.const 0x7fffffffffffffff)
+      (i64.const -1)
     ))
-
-    ;; i32(x) < 0x7fffffff  =>  x != 0x7fffffff
-    (drop (i32.lt_s
+    ;; (unsigned)x < -1   ==>   x != -1
+    (drop (i32.lt_u
       (local.get $x)
-      (i32.const 0x7fffffff)
+      (i32.const -1)
     ))
-    ;; i64(x) < 0x7fffffffffffffff  =>  x != 0x7fffffffffffffff
-    (drop (i64.lt_s
+    (drop (i64.lt_u
       (local.get $y)
-      (i64.const 0x7fffffffffffffff)
+      (i64.const -1)
     ))
-
-    ;; i32(x) > 0x80000000  =>  x != 0x80000000
-    (drop (i32.gt_s
+    ;; x * -1
+    (drop (i32.mul
       (local.get $x)
-      (i32.const 0x80000000)
+      (i32.const -1)
     ))
-    ;; i64(x) > 0x8000000000000000  =>  x != 0x8000000000000000
-    (drop (i64.gt_s
+    (drop (i64.mul
       (local.get $y)
-      (i64.const 0x8000000000000000)
+      (i64.const -1)
     ))
-
-    ;; i32(x) <= 0x80000000  =>  x == 0x80000000
-    (drop (i32.le_s
-      (local.get $x)
-      (i32.const 0x80000000)
+    (drop (f32.mul    ;; skip
+      (local.get $fx)
+      (f32.const -1)
     ))
-    ;; i64(x) <= 0x8000000000000000  =>  x == 0x8000000000000000
-    (drop (i64.le_s
-      (local.get $y)
-      (i64.const 0x8000000000000000)
+    (drop (f64.mul    ;; skip
+      (local.get $fy)
+      (f64.const -1)
     ))
-
-    ;; i32(x) >= 0x7fffffff  =>  x == 0x7fffffff
-    (drop (i32.ge_s
+    ;; (unsigned)x / -1
+    (drop (i32.div_u
       (local.get $x)
-      (i32.const 0x7fffffff)
+      (i32.const -1)
     ))
-    ;; i64(x) >= 0x7fffffffffffffff  =>  x == 0x7fffffffffffffff
-    (drop (i64.ge_s
+    (drop (i64.div_u
       (local.get $y)
-      (i64.const 0x7fffffffffffffff)
-    ))
-
-    ;; -x * 1  =>  x * -1
-    (drop (f32.mul
-      (f32.neg
-        (local.get $fx)
-      )
-      (f32.const 1)
-    ))
-    ;; -x * -2.1  =>  x * 2.1
-    (drop (f64.mul
-      (f64.neg
-        (local.get $fy)
-      )
-      (f64.const -2.1)
-    ))
-    ;; 2 * -x  =>  x * -2
-    (drop (f64.mul
-      (f64.const 2)
-      (f64.neg
-        (local.get $fy)
-      )
-    ))
-    ;; -x / inf  =>  x / -inf
-    (drop (f32.div
-      (f32.neg
-        (local.get $fx)
-      )
-      (f32.const inf)
-    ))
-    ;; -x / -0.0  =>  x / 0.0
-    (drop (f64.div
-      (f64.neg
-        (local.get $fy)
-      )
-      (f64.const -0.0)
-    ))
-    ;; -x / nan  =>  x / -nan
-    (drop (f64.div
-      (f64.neg
-        (local.get $fy)
-      )
-      (f64.const nan)
-    ))
-    ;; 5.0 / -x  =>  -5 / x
-    (drop (f64.div
-      (f64.const 5)
-      (f64.neg
-        (local.get $fy)
-      )
+      (i64.const -1)
     ))
   )
-  ;; CHECK:      (func $lhs-is-neg-one (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const -1)
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $rhs-is-const (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const -1)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const -1)
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.eq
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const -9223372036854775808)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const -1)
+  ;; CHECK-NEXT:   (i64.div_s
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -2147483648)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const -1)
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.const -1)
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shr_s
-  ;; CHECK-NEXT:    (i32.const -1)
-  ;; CHECK-NEXT:    (call $ne0)
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483647)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.shr_u
-  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 31)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $lhs-is-neg-one (param $x i32) (param $y i64)
-    ;; -1 >> x  ==>   -1
-    (drop (i32.shr_s
-      (i32.const -1)
-      (local.get $x)
-    ))
-    (drop (i64.shr_s
-      (i64.const -1)
-      (local.get $y)
-    ))
-    ;; rotl(-1, x)  ==>   -1
-    (drop (i32.rotl
-      (i32.const -1)
-      (local.get $x)
-    ))
-    (drop (i64.rotl
-      (i64.const -1)
-      (local.get $y)
-    ))
-    ;; rotr(-1, x)  ==>   -1
-    (drop (i32.rotr
-      (i32.const -1)
-      (local.get $x)
-    ))
-    (drop (i64.rotr
-      (i64.const -1)
-      (local.get $y)
-    ))
-    ;; skip
-    (drop (i32.shr_s
-      (i32.const -1)
-      (call $ne0) ;; side effect
-    ))
-    ;; skip
-    (drop (i32.shr_u
-      (i32.const -1)
-      (local.get $x)
-    ))
-  )
-  ;; CHECK:      (func $lhs-is-const (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i64.eq
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const -1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shr_u
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 63)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   (i64.ne
   ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (i32.const -2)
+  ;; CHECK-NEXT:   (i32.eqz
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
-  ;; CHECK-NEXT:    (i64.const -2)
+  ;; CHECK-NEXT:   (i64.eqz
   ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2147483647)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:   (i64.ne
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:    (i64.const 9223372036854775807)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $lhs-is-const (param $x i32) (param $y i64)
-    ;; 0 - (x - 1)
-    (drop (i32.sub
-      (i32.const 0)
-      (i32.sub
-        (local.get $x)
-        (i32.const 1)
-      )
-    ))
-    (drop (i64.sub
-      (i64.const 0)
-      (i64.sub
-        (local.get $y)
-        (i64.const 1)
-      )
-    ))
-    ;; -1 - (x + 1)
-    (drop (i32.sub
-      (i32.const -1)
-      (i32.add
-        (local.get $x)
-        (i32.const 1)
-      )
-    ))
-    (drop (i64.sub
-      (i64.const -1)
-      (i64.add
-        (local.get $y)
-        (i64.const 1)
-      )
-    ))
-    ;; 1 - (2 - x)
-    (drop (i32.sub
-      (i32.const 1)
-      (i32.sub
-        (i32.const 2)
-        (local.get $x)
-      )
-    ))
-    (drop (i64.sub
-      (i64.const 1)
-      (i64.sub
-        (i64.const 2)
-        (local.get $y)
-      )
-    ))
-    ;; 0 - (0x80000000 - x)
-    (drop (i32.sub
-      (i32.const 0)
-      (i32.sub
-        (i32.const 0x80000000)
-        (local.get $x)
-      )
-    ))
-  )
-  ;; CHECK:      (func $pre-combine-or (param $x i32) (param $y i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
   ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eq
   ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const -9223372036854775808)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:   (i32.eq
   ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 2147483647)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eq
   ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 9223372036854775807)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.eq
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.gt_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const -0)
+  ;; CHECK-NEXT:    (local.get $fx)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.eq
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.gt_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const 2.1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.gt_s
-  ;; CHECK-NEXT:     (call $ne0)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.eq
-  ;; CHECK-NEXT:     (call $ne0)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const -2)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.lt_s
-  ;; CHECK-NEXT:     (call $ne0)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.eq
-  ;; CHECK-NEXT:     (call $ne0)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $fx)
+  ;; CHECK-NEXT:    (f32.const -inf)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $pre-combine-or (param $x i32) (param $y i32)
-    (drop (i32.or
-      (i32.gt_s
-        (local.get $x)
-        (local.get $y)
-      )
-      (i32.eq
-        (local.get $y) ;; ordering should not stop us
-        (local.get $x)
-      )
-    ))
-    (drop (i32.or
-      (i32.eq ;; ordering should not stop us
-        (local.get $y)
-        (local.get $x)
-      )
-      (i32.gt_s
-        (local.get $x)
-        (local.get $y)
-      )
-    ))
-    (drop (i32.or
-      (i32.gt_s
-        (local.get $x)
-        (local.get $y)
-      )
-      (i32.eq
-        (local.get $x)
-        (i32.const 1) ;; not equal
-      )
-    ))
-    (drop (i32.or
-      (i32.gt_s
-        (local.get $x)
-        (i32.const 1) ;; not equal
-      )
-      (i32.eq
-        (local.get $x)
-        (local.get $y)
-      )
-    ))
-    (drop (i32.or
-      (i32.gt_s
-        (call $ne0) ;; side effects
-        (local.get $y)
-      )
-      (i32.eq
-        (call $ne0)
-        (local.get $y)
-      )
-    ))
-    (drop (i32.or
-      (i32.gt_s
-        (local.get $y)
-        (call $ne0) ;; side effects
-      )
-      (i32.eq
-        (local.get $y)
-        (call $ne0)
-      )
-    ))
-  )
-  ;; CHECK:      (func $combine-or (param $x i32) (param $y i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:    (f64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $combine-or (param $x i32) (param $y i32)
-    (drop (i32.or
-      (i32.gt_s
-        (local.get $x)
-        (local.get $y)
-      )
-      (i32.eq
-        (local.get $x)
-        (local.get $y)
-      )
-    ))
-    ;; TODO: more stuff here
-  )
-  ;; CHECK:      (func $select-into-arms (param $x i32) (param $y i32)
-  ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $select-into-arms (param $x i32) (param $y i32)
-    (if
-      (select
-        (i32.eqz (i32.eqz (local.get $x)))
-        (i32.eqz (i32.eqz (local.get $y)))
-        (local.get $y)
-      )
-      (unreachable)
-    )
-  )
-  ;; CHECK:      (func $select-with-same-arm-and-cond-32 (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (f64.const -5)
+  ;; CHECK-NEXT:    (local.get $fy)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i32)
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (f32.sub
+  ;; CHECK-NEXT:    (f32.const 3.5)
+  ;; CHECK-NEXT:    (local.get $fx)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i32)
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (f64.sub
+  ;; CHECK-NEXT:    (f64.const 5)
+  ;; CHECK-NEXT:    (local.get $fy)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $select-with-same-arm-and-cond-32 (param $x i32)
-    ;; i32(x) ? i32(x) : 0  ==>  x
-    (drop (select
+  (func $rhs-is-const (param $x i32) (param $y i64) (param $fx f32) (param $fy f64)
+    ;; signed divs
+    ;; i32(x) / -2147483648  ->  x == -2147483648
+    (drop (i32.div_s
       (local.get $x)
-      (i32.const 0)
+      (i32.const -2147483648)
+    ))
+    ;; i64(x) / -9223372036854775808  ->  x == -9223372036854775808
+    (drop (i64.div_s
+      (local.get $y)
+      (i64.const -9223372036854775808)
+    ))
+    ;; skip
+    (drop (i64.div_s
+      (local.get $y)
+      (i64.const -2147483648)
+    ))
+
+    ;; unsigned divs
+    ;; u32(x) / -2  =>  x >= -2
+    (drop (i32.div_u
       (local.get $x)
+      (i32.const -2)
     ))
-    ;; i32(x) ? 0 : i32(x)  ==>  {x, 0}
-    (drop (select
-      (i32.const 0)
+    ;; u32(x) / -1  =>  x == -1
+    (drop (i32.div_u
       (local.get $x)
+      (i32.const -1)
+    ))
+    ;; u32(x) / (i32.min + 1)
+    (drop (i32.div_u
       (local.get $x)
+      (i32.const -2147483647)
     ))
-    ;; i32(x) == 0 ? i32(x) : 0  ==>  {x, 0}
-    (drop (select
+    ;; u32(x) / i32.min  =>  x >>> 31
+    (drop (i32.div_u
       (local.get $x)
-      (i32.const 0)
-      (i32.eqz (local.get $x))
+      (i32.const -2147483648)
+    ))
+    ;; u64(x) / -1  =>  u64(x == -1)
+    (drop (i64.div_u
+      (local.get $y)
+      (i64.const -1)
+    ))
+    ;; u64(x) / i64.min  =>  x >>> 63
+    (drop (i64.div_u
+      (local.get $y)
+      (i64.const -9223372036854775808)
     ))
-  )
 
-  ;; CHECK:      (func $select-with-same-arm-and-cond-64 (param $x i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i64)
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i64)
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block (result i64)
-  ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $select-with-same-arm-and-cond-64 (param $x i64)
-    ;; i64(x) != 0 ? i64(x) : 0  ==>  x
-    (drop (select
+    ;; (unsigned)x >= 0  =>  i32(1)
+    (drop (i32.ge_u
       (local.get $x)
-      (i64.const 0)
-      (i64.ne
-        (local.get $x)
-        (i64.const 0)
-      )
+      (i32.const 0)
     ))
-    ;; i64(x) == 0 ? 0 : i64(x)  ==>  x
-    (drop (select
+    (drop (i64.ge_u
+      (local.get $y)
       (i64.const 0)
+    ))
+
+    ;; (unsigned)x < 0  =>  i32(0)
+    (drop (i32.lt_u
       (local.get $x)
-      (i64.eqz
-        (local.get $x)
-      )
+      (i32.const 0)
     ))
-    ;; i64(x) != 0 ? 0 : i64(x)  ==>  0
-    (drop (select
+    (drop (i64.lt_u
+      (local.get $y)
       (i64.const 0)
-      (local.get $x)
-      (i64.ne
-        (local.get $x)
-        (i64.const 0)
-      )
     ))
-    ;; i64(x) == 0 ? i64(x) : 0  ==>  {x, 0}
-    (drop (select
+
+    ;; (unsigned)x > 0  =>  x != 0
+    (drop (i32.gt_u
       (local.get $x)
+      (i32.const 0)
+    ))
+    (drop (i64.gt_u
+      (local.get $y)
       (i64.const 0)
-      (i64.eqz
-        (local.get $x)
-      )
     ))
-    (drop (select
+
+    ;; (unsigned)x <= 0  =>  x == 0
+    (drop (i32.le_u
       (local.get $x)
+      (i32.const 0)
+    ))
+    (drop (i64.le_u
+      (local.get $y)
       (i64.const 0)
-      (i64.eq
-        (local.get $x)
-        (i64.const 0)
-      )
     ))
-  )
 
-  ;; CHECK:      (func $select-with-same-arm-and-cond-skips (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -1)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.const -1)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i64.const -1)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.ne
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i64.ne
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $select-with-same-arm-and-cond-skips (param $x i32) (param $y i64)
-    ;; skip not equals
-    (drop (select
+    ;; i32(x) <= 0x7fffffff  =>  i32(1)
+    (drop (i32.le_s
       (local.get $x)
-      (i32.const 0)
-      (i32.wrap_i64 (local.get $y))
+      (i32.const 0x7fffffff)
     ))
-    (drop (select
-      (i32.const 0)
-      (i32.sub (i32.const 0) (local.get $x))
+    ;; i64(x) <= 0x7fffffffffffffff  =>  i32(1)
+    (drop (i64.le_s
+      (local.get $y)
+      (i64.const 0x7fffffffffffffff)
+    ))
+
+    ;; i32(x) >= 0x80000000  =>  i32(1)
+    (drop (i32.ge_s
       (local.get $x)
+      (i32.const 0x80000000)
+    ))
+    ;; i64(x) >= 0x8000000000000000  =>  i32(1)
+    (drop (i64.ge_s
+      (local.get $y)
+      (i64.const 0x8000000000000000)
     ))
 
-    ;; skip not zero
-    (drop (select
+    ;; i32(x) < 0x80000000  =>  0
+    (drop (i32.lt_s
       (local.get $x)
-      (i32.const -1)
+      (i32.const 0x80000000)
+    ))
+    ;; i64(x) < 0x8000000000000000  =>  0
+    (drop (i64.lt_s
+      (local.get $y)
+      (i64.const 0x8000000000000000)
+    ))
+
+    ;; i32(x) > 0x7fffffff  =>  0
+    (drop (i32.gt_s
       (local.get $x)
+      (i32.const 0x7fffffff)
     ))
-    (drop (select
-      (i32.const -1)
+    ;; i64(x) > 0x7fffffffffffffff  =>  0
+    (drop (i64.gt_s
+      (local.get $y)
+      (i64.const 0x7fffffffffffffff)
+    ))
+
+    ;; i32(x) < 0x7fffffff  =>  x != 0x7fffffff
+    (drop (i32.lt_s
       (local.get $x)
+      (i32.const 0x7fffffff)
+    ))
+    ;; i64(x) < 0x7fffffffffffffff  =>  x != 0x7fffffffffffffff
+    (drop (i64.lt_s
+      (local.get $y)
+      (i64.const 0x7fffffffffffffff)
+    ))
+
+    ;; i32(x) > 0x80000000  =>  x != 0x80000000
+    (drop (i32.gt_s
       (local.get $x)
+      (i32.const 0x80000000)
     ))
-    (drop (select
-      (i64.const -1)
+    ;; i64(x) > 0x8000000000000000  =>  x != 0x8000000000000000
+    (drop (i64.gt_s
       (local.get $y)
-      (i64.ne
-        (local.get $y)
-        (i64.const 0)
-      )
+      (i64.const 0x8000000000000000)
     ))
-    (drop (select
-      (i64.const 0)
+
+    ;; i32(x) <= 0x80000000  =>  x == 0x80000000
+    (drop (i32.le_s
+      (local.get $x)
+      (i32.const 0x80000000)
+    ))
+    ;; i64(x) <= 0x8000000000000000  =>  x == 0x8000000000000000
+    (drop (i64.le_s
       (local.get $y)
-      (i64.ne
-        (local.get $y)
-        (i64.const 1)
+      (i64.const 0x8000000000000000)
+    ))
+
+    ;; i32(x) >= 0x7fffffff  =>  x == 0x7fffffff
+    (drop (i32.ge_s
+      (local.get $x)
+      (i32.const 0x7fffffff)
+    ))
+    ;; i64(x) >= 0x7fffffffffffffff  =>  x == 0x7fffffffffffffff
+    (drop (i64.ge_s
+      (local.get $y)
+      (i64.const 0x7fffffffffffffff)
+    ))
+
+    ;; -x * 1  =>  x * -1
+    (drop (f32.mul
+      (f32.neg
+        (local.get $fx)
+      )
+      (f32.const 1)
+    ))
+    ;; -x * -2.1  =>  x * 2.1
+    (drop (f64.mul
+      (f64.neg
+        (local.get $fy)
+      )
+      (f64.const -2.1)
+    ))
+    ;; 2 * -x  =>  x * -2
+    (drop (f64.mul
+      (f64.const 2)
+      (f64.neg
+        (local.get $fy)
+      )
+    ))
+    ;; -x / inf  =>  x / -inf
+    (drop (f32.div
+      (f32.neg
+        (local.get $fx)
+      )
+      (f32.const inf)
+    ))
+    ;; -x / -0.0  =>  x / 0.0
+    (drop (f64.div
+      (f64.neg
+        (local.get $fy)
+      )
+      (f64.const -0.0)
+    ))
+    ;; -x / nan  =>  x / -nan
+    (drop (f64.div
+      (f64.neg
+        (local.get $fy)
+      )
+      (f64.const nan)
+    ))
+    ;; 5.0 / -x  =>  -5 / x
+    (drop (f64.div
+      (f64.const 5)
+      (f64.neg
+        (local.get $fy)
       )
     ))
-  )
 
-  ;; CHECK:      (func $select-with-same-arm-and-cond-skips-side-effects (param $x i32) (param $y i64)
+    ;; -x + C   ->   C - x
+    (drop (f32.add
+      (f32.neg
+        (local.get $fx)
+      )
+      (f32.const 3.5)
+    ))
+    (drop (f64.sub
+      (f64.neg
+        (local.get $fy)
+      )
+      (f64.const -5)
+    ))
+  )
+  ;; CHECK:      (func $rhs-is-const-nan (param $x f32) (param $y f64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.div_u
-  ;; CHECK-NEXT:     (i32.const 10)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (i32.div_u
-  ;; CHECK-NEXT:     (i32.const 10)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:    (call $ne0)
-  ;; CHECK-NEXT:    (call $ne0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (call $select-sign-64-lt
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:    (i64.eqz
-  ;; CHECK-NEXT:     (call $select-sign-64-lt
-  ;; CHECK-NEXT:      (local.get $y)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:    (call $select-sign-64-lt
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.eqz
-  ;; CHECK-NEXT:     (call $select-sign-64-lt
-  ;; CHECK-NEXT:      (local.get $y)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $select-with-same-arm-and-cond-skips-side-effects (param $x i32) (param $y i64)
-    ;; skip with side effects
-    (drop (select
-      (i32.div_u (i32.const 10) (local.get $x))
-      (i32.const 0)
-      (i32.div_u (i32.const 10) (local.get $x))
-    ))
-    (drop (select
-      (i32.const 0)
-      (call $ne0)
-      (call $ne0)
-    ))
-    (drop (select
-      (call $select-sign-64-lt (local.get $y))
-      (i64.const 0)
-      (i64.eqz
-        (call $select-sign-64-lt (local.get $y))
-      )
-    ))
-    (drop (select
-      (i64.const 0)
-      (call $select-sign-64-lt (local.get $y))
-      (i64.eqz
-        (call $select-sign-64-lt (local.get $y))
-      )
-    ))
-  )
-  ;; CHECK:      (func $optimize-boolean-context (param $x i32) (param $y i32)
-  ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-boolean-context (param $x i32) (param $y i32)
-    ;; 0 - x   ==>   x
-    (if
-      (i32.sub
-        (i32.const 0)
-        (local.get $x)
-      )
-      (unreachable)
-    )
-    (drop (select
-      (local.get $x)
-      (local.get $y)
-      (i32.sub
-        (i32.const 0)
-        (local.get $x)
-      )
-    ))
-  )
-
-  ;; CHECK:      (func $optimize-combined-by-and-equals-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.or
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-and-equals-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) == 0) & (i32(y) == 0)   ==>   i32(x | y) == 0
-    (drop (i32.and
-      (i32.eq (local.get $x) (i32.const 0))
-      (i32.eq (local.get $y) (i32.const 0))
-    ))
-    ;; (i64(x) == 0) & (i64(y) == 0)   ==>   i64(x | y) == 0
-    (drop (i32.and
-      (i64.eq (local.get $a) (i64.const 0))
-      (i64.eq (local.get $b) (i64.const 0))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-or-noequal-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (i64.or
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-or-noequal-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) != 0) | (i32(y) != 0)   ==>   i32(x | y) != 0
-    (drop (i32.or
-      (i32.ne (local.get $x) (i32.const 0))
-      (i32.ne (local.get $y) (i32.const 0))
-    ))
-    ;; (i64(x) != 0) | (i64(y) != 0)   ==>   i64(x | y) != 0
-    (drop (i32.or
-      (i64.ne (local.get $a) (i64.const 0))
-      (i64.ne (local.get $b) (i64.const 0))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-or-gt-or-eq-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ge_s
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.ge_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.ge_s
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-or-gt-or-eq-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) >= 0) | (i32(y) >= 0)   ==>   i32(x & y) >= 0
-    (drop (i32.or
-      (i32.ge_s (local.get $x) (i32.const 0))
-      (i32.ge_s (local.get $y) (i32.const 0))
-    ))
-    ;; (i64(x) >= 0) | (i64(y) >= 0)   ==>   i64(x & y) >= 0
-    (drop (i32.or
-      (i64.ge_s (local.get $a) (i64.const 0))
-      (i64.ge_s (local.get $b) (i64.const 0))
-    ))
-
-    ;; skips
-    ;; (i32(x) >= 0) | (i64(y) >= 0)   ==>   skip
-    (drop (i32.or
-      (i32.ge_s (local.get $x) (i32.const 0))
-      (i64.ge_s (local.get $a) (i64.const 0))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-or-ne-to-minus-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const -1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const -1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.ne
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const -1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.ne
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (i64.const -1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-or-ne-to-minus-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) != -1) | (i32(y) != -1)   ==>   i32(x & y) != -1
-    (drop (i32.or
-      (i32.ne (local.get $x) (i32.const -1))
-      (i32.ne (local.get $y) (i32.const -1))
-    ))
-    ;; (i64(x) != -1) | (i64(y) == -1)   ==>   i64(x & y) != -1
-    (drop (i32.or
-      (i64.ne (local.get $a) (i64.const -1))
-      (i64.ne (local.get $b) (i64.const -1))
-    ))
-
-    ;; skips
-    ;; (i32(x) != -1) | (i64(y) != -1)   ==>   skip
-    (drop (i32.or
-      (i32.ne (local.get $x) (i32.const -1))
-      (i64.ne (local.get $a) (i64.const -1))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-or-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f32.const nan:0x400000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.lt_s
-  ;; CHECK-NEXT:    (i64.or
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (f64.const nan:0x8000000000000)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.lt_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.lt_s
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.copysign
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const nan:0x400000)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.lt_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.le_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.copysign
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (f32.const nan:0x200000)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.lt_s
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.le_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-or-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) < 0) | (i32(y) < 0)   ==>   i32(x | y) < 0
-    (drop (i32.or
-      (i32.lt_s (local.get $x) (i32.const 0))
-      (i32.lt_s (local.get $y) (i32.const 0))
-    ))
-    ;; (i64(x) < 0) | (i64(y) < 0)   ==>   i64(x | y) < 0
-    (drop (i32.or
-      (i64.lt_s (local.get $a) (i64.const 0))
-      (i64.lt_s (local.get $b) (i64.const 0))
-    ))
-
-    ;; skips
-    ;; (i32(x) < 0) | (i64(a) < 0)   ==>   skip
-    (drop (i32.or
-      (i32.lt_s (local.get $x) (i32.const 0))
-      (i64.lt_s (local.get $a) (i64.const 0))
-    ))
-    ;; (i32(x) <= 0) | (i32(y) < 0)   ==>   skip
-    (drop (i32.or
-      (i32.le_s (local.get $x) (i32.const 0))
-      (i32.lt_s (local.get $y) (i32.const 0))
-    ))
-    ;; (i32(x) < 1) | (i32(y) < 0)   ==>   skip
-    (drop (i32.or
-      (i32.lt_s (local.get $x) (i32.const 1))
-      (i32.lt_s (local.get $y) (i32.const 0))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-and-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.lt_s
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-and-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) < 0) & (i32(y) < 0)   ==>   i32(x & y) < 0
-    (drop (i32.and
-      (i32.lt_s (local.get $x) (i32.const 0))
-      (i32.lt_s (local.get $y) (i32.const 0))
-    ))
-    ;; (i64(x) < 0) & (i64(y) < 0)   ==>   i64(x & y) < 0
-    (drop (i32.and
-      (i64.lt_s (local.get $a) (i64.const 0))
-      (i64.lt_s (local.get $b) (i64.const 0))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-and-greatequal-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (f64.copysign
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (f64.const -nan:0x8000000000000)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ge_s
-  ;; CHECK-NEXT:    (i64.or
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (i32.ge_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.ge_s
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (i64.const 0)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-and-greatequal-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) >= 0) & (i32(y) >= 0)   ==>   i32(x | y) >= 0
-    (drop (i32.and
-      (i32.ge_s (local.get $x) (i32.const 0))
-      (i32.ge_s (local.get $y) (i32.const 0))
-    ))
-    ;; (i64(x) >= 0) & (i64(y) >= 0)   ==>   i64(x | y) >= 0
-    (drop (i32.and
-      (i64.ge_s (local.get $a) (i64.const 0))
-      (i64.ge_s (local.get $b) (i64.const 0))
-    ))
-
-    ;; skips
-    ;; (i32(x) >= 0) & (i64(y) >= 0)   ==>   skip
-    (drop (i32.and
-      (i32.ge_s (local.get $x) (i32.const 0))
-      (i64.ge_s (local.get $a) (i64.const 0))
-    ))
-  )
-  ;; CHECK:      (func $optimize-combined-by-and-equal-neg-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const -1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eq
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (local.get $b)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const -1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (i32.eq
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const -1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.eq
-  ;; CHECK-NEXT:     (local.get $a)
-  ;; CHECK-NEXT:     (i64.const -1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-combined-by-and-equal-neg-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
-    ;; (i32(x) == -1) & (i32(y) == -1)   ==>   i32(x & y) == -1
-    (drop (i32.and
-      (i32.eq (local.get $x) (i32.const -1))
-      (i32.eq (local.get $y) (i32.const -1))
-    ))
-    ;; (i64(x) == -1) & (i64(y) == -1)   ==>   i64(x & y) == -1
-    (drop (i32.and
-      (i64.eq (local.get $a) (i64.const -1))
-      (i64.eq (local.get $b) (i64.const -1))
-    ))
-
-    ;; skips
-    ;; (i32(x) == -1) & (i64(y) == -1)   ==>   skip
-    (drop (i32.and
-      (i32.eq (local.get $x) (i32.const -1))
-      (i64.eq (local.get $a) (i64.const -1))
-    ))
-  )
-  ;; CHECK:      (func $optimize-relationals (param $x i32) (param $y i32) (param $X i64) (param $Y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483647)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 2147483647)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eq
-  ;; CHECK-NEXT:    (local.get $X)
-  ;; CHECK-NEXT:    (local.get $Y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eq
-  ;; CHECK-NEXT:    (local.get $X)
-  ;; CHECK-NEXT:    (local.get $Y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.ne
-  ;; CHECK-NEXT:    (local.get $X)
-  ;; CHECK-NEXT:    (local.get $Y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.gt_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $rhs-is-const-nan (param $x f32) (param $y f64)
+
+    ;; arithmetic ops
+
+    ;; x + nan'   =>   nan'
+    (drop (f32.add (local.get $x) (f32.const nan)))
+    (drop (f32.add (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.add (local.get $y) (f64.const -nan)))
+    ;; x - nan'   =>   nan'
+    (drop (f32.sub (local.get $x) (f32.const nan)))
+    (drop (f32.sub (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.sub (local.get $y) (f64.const -nan)))
+    ;; x * nan'   =>   nan'
+    (drop (f32.mul (local.get $x) (f32.const nan)))
+    (drop (f32.mul (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.mul (local.get $y) (f64.const -nan)))
+    ;; x / nan'   =>   nan'
+    (drop (f32.div (local.get $x) (f32.const nan)))
+    (drop (f32.div (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.div (local.get $y) (f64.const -nan)))
+
+    ;; min / max ops
+
+    ;; min(x, nan')   =>   nan'
+    (drop (f32.min (local.get $x) (f32.const nan)))
+    (drop (f32.min (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.min (local.get $y) (f64.const -nan)))
+    ;; max(x, nan')   =>   nan'
+    (drop (f32.max (local.get $x) (f32.const nan)))
+    (drop (f32.max (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.max (local.get $y) (f64.const -nan)))
+
+    ;; copysign ops (should be skipped)
+
+    ;; copysign(x, nan)   =>  skip
+    (drop (f32.copysign (local.get $x) (f32.const nan)))
+    (drop (f32.copysign (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.copysign (local.get $y) (f64.const -nan)))
+
+    ;; relational ops
+
+    ;; x != nan   =>   1
+    (drop (f32.ne (local.get $x) (f32.const nan)))
+    (drop (f32.ne (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.ne (local.get $y) (f64.const -nan)))
+    ;; x == nan   =>   0
+    (drop (f32.eq (local.get $x) (f32.const nan)))
+    (drop (f32.eq (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.eq (local.get $y) (f64.const -nan)))
+    ;; x >  nan   =>   0
+    (drop (f32.gt (local.get $x) (f32.const nan)))
+    (drop (f32.gt (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.gt (local.get $y) (f64.const -nan)))
+    ;; x >= nan   =>   0
+    (drop (f32.ge (local.get $x) (f32.const nan)))
+    (drop (f32.ge (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.ge (local.get $y) (f64.const -nan)))
+    ;; x <  nan   =>   0
+    (drop (f32.lt (local.get $x) (f32.const nan)))
+    (drop (f32.lt (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.lt (local.get $y) (f64.const -nan)))
+    ;; x <= nan   =>   0
+    (drop (f32.le (local.get $x) (f32.const nan)))
+    (drop (f32.le (local.get $x) (f32.const nan:0x200000)))
+    (drop (f64.le (local.get $y) (f64.const -nan)))
+  )
+  ;; CHECK:      (func $lhs-is-neg-one (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.le_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i64.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i64.const -1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:    (call $ne0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:   (i32.shr_u
+  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.lt_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const -2147483648)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $lhs-is-neg-one (param $x i32) (param $y i64)
+    ;; -1 >> x  ==>   -1
+    (drop (i32.shr_s
+      (i32.const -1)
+      (local.get $x)
+    ))
+    (drop (i64.shr_s
+      (i64.const -1)
+      (local.get $y)
+    ))
+    ;; rotl(-1, x)  ==>   -1
+    (drop (i32.rotl
+      (i32.const -1)
+      (local.get $x)
+    ))
+    (drop (i64.rotl
+      (i64.const -1)
+      (local.get $y)
+    ))
+    ;; rotr(-1, x)  ==>   -1
+    (drop (i32.rotr
+      (i32.const -1)
+      (local.get $x)
+    ))
+    (drop (i64.rotr
+      (i64.const -1)
+      (local.get $y)
+    ))
+    ;; skip
+    (drop (i32.shr_s
+      (i32.const -1)
+      (call $ne0) ;; side effect
+    ))
+    ;; skip
+    (drop (i32.shr_u
+      (i32.const -1)
+      (local.get $x)
+    ))
+  )
+  ;; CHECK:      (func $lhs-is-const (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const -2147483648)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.gt_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (block $block (result i32)
-  ;; CHECK-NEXT:      (i32.const -2147483648)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (i32.const -2)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.gt_s
-  ;; CHECK-NEXT:    (i32.sub
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (block $block23 (result i32)
-  ;; CHECK-NEXT:      (i32.const -2147483648)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (i64.const -2)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $optimize-relationals (param $x i32) (param $y i32) (param $X i64) (param $Y i64)
-    ;; eqz(x + 0x7FFFFFFF)  ->  x == -2147483647
-    (drop (i32.eqz
-      (i32.add
+  (func $lhs-is-const (param $x i32) (param $y i64)
+    ;; 0 - (x - 1)
+    (drop (i32.sub
+      (i32.const 0)
+      (i32.sub
         (local.get $x)
-        (i32.const 0x7FFFFFFF)
+        (i32.const 1)
       )
     ))
-    ;; eqz(x + 0x80000000)  ->  x == -2147483648
-    (drop (i32.eqz
-      (i32.add
-        (local.get $x)
-        (i32.const 0x80000000)
+    (drop (i64.sub
+      (i64.const 0)
+      (i64.sub
+        (local.get $y)
+        (i64.const 1)
       )
     ))
-    ;; eqz(x + 0x80000001)  ->  x == 2147483647
-    (drop (i32.eqz
+    ;; -1 - (x + 1)
+    (drop (i32.sub
+      (i32.const -1)
       (i32.add
         (local.get $x)
-        (i32.const 0x80000001)
+        (i32.const 1)
       )
     ))
-    ;; eqz(x - y)
-    (drop (i32.eqz
-      (i32.sub
-        (local.get $x)
+    (drop (i64.sub
+      (i64.const -1)
+      (i64.add
         (local.get $y)
+        (i64.const 1)
       )
     ))
-    (drop (i64.eqz
-      (i64.sub
-        (local.get $X)
-        (local.get $Y)
-      )
-    ))
-    ;; x - y == 0
-    (drop (i32.eq
+    ;; 1 - (2 - x)
+    (drop (i32.sub
+      (i32.const 1)
       (i32.sub
+        (i32.const 2)
         (local.get $x)
-        (local.get $y)
       )
-      (i32.const 0)
     ))
-    (drop (i64.eq
+    (drop (i64.sub
+      (i64.const 1)
       (i64.sub
-        (local.get $X)
-        (local.get $Y)
+        (i64.const 2)
+        (local.get $y)
       )
-      (i64.const 0)
     ))
-    ;; x - y != 0
-    (drop (i32.ne
+    ;; 0 - (0x80000000 - x)
+    (drop (i32.sub
+      (i32.const 0)
       (i32.sub
+        (i32.const 0x80000000)
         (local.get $x)
-        (local.get $y)
       )
-      (i32.const 0)
     ))
-    (drop (i64.ne
-      (i64.sub
-        (local.get $X)
-        (local.get $Y)
+  )
+  ;; CHECK:      (func $pre-combine-or (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.eq
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.gt_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.eq
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.gt_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.gt_s
+  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.eq
+  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.lt_s
+  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.eq
+  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $pre-combine-or (param $x i32) (param $y i32)
+    (drop (i32.or
+      (i32.gt_s
+        (local.get $x)
+        (local.get $y)
+      )
+      (i32.eq
+        (local.get $y) ;; ordering should not stop us
+        (local.get $x)
       )
-      (i64.const 0)
     ))
-    ;; i32(x - y) > 0  ->  x > y
-    (drop (i32.gt_s
-      (i32.sub
+    (drop (i32.or
+      (i32.eq ;; ordering should not stop us
+        (local.get $y)
+        (local.get $x)
+      )
+      (i32.gt_s
         (local.get $x)
         (local.get $y)
       )
-      (i32.const 0)
     ))
-    ;; i32(x - y) >= 0  ->  x >= y
-    (drop (i32.ge_s
-      (i32.sub
+    (drop (i32.or
+      (i32.gt_s
         (local.get $x)
         (local.get $y)
       )
-      (i32.const 0)
-    ))
-    ;; u32(x - y) > 0  ->  x != y
-    (drop (i32.gt_u
-      (i32.sub
+      (i32.eq
         (local.get $x)
-        (local.get $y)
+        (i32.const 1) ;; not equal
       )
-      (i32.const 0)
     ))
-    ;; u32(x - y) >= 0  ->  1
-    (drop (i32.ge_u
-      (i32.sub
+    (drop (i32.or
+      (i32.gt_s
         (local.get $x)
-        (local.get $y)
-      )
-      (i32.const 0)
-    ))
-    ;; u64(x - y) >= 0  ->  i32(1)
-    (drop (i64.ge_u
-      (i64.sub
-        (local.get $X)
-        (local.get $Y)
+        (i32.const 1) ;; not equal
       )
-      (i64.const 0)
-    ))
-    ;; i32(x - y) < 0  ->  x < y
-    (drop (i32.lt_s
-      (i32.sub
+      (i32.eq
         (local.get $x)
         (local.get $y)
       )
-      (i32.const 0)
     ))
-    ;; i32(x - y) <= 0  ->  x <= y
-    (drop (i32.le_s
-      (i32.sub
-        (local.get $x)
+    (drop (i32.or
+      (i32.gt_s
+        (call $ne0) ;; side effects
         (local.get $y)
       )
-      (i32.const 0)
-    ))
-    ;; u32(x - y) < 0  ->  0
-    (drop (i32.lt_u
-      (i32.sub
-        (local.get $x)
+      (i32.eq
+        (call $ne0)
         (local.get $y)
       )
-      (i32.const 0)
-    ))
-    ;; u64(x - y) < 0  ->  i32(0)
-    (drop (i64.lt_u
-      (i64.sub
-        (local.get $X)
-        (local.get $Y)
-      )
-      (i64.const 0)
     ))
-    ;; u32(x - y) <= 0  ->  x == y
-    (drop (i32.le_u
-      (i32.sub
-        (local.get $x)
+    (drop (i32.or
+      (i32.gt_s
         (local.get $y)
+        (call $ne0) ;; side effects
       )
-      (i32.const 0)
-    ))
-    ;; i32(x - 0x80000000) == 0  ->  x == 0x80000000
-    (drop (i32.eq
-      (i32.sub
-        (local.get $x)
-        (i32.const 0x80000000)
-      )
-      (i32.const 0)
-    ))
-    ;; i32(x - 0x80000000) != 0  ->  x == 0x80000000
-    (drop (i32.ne
-      (i32.sub
-        (local.get $x)
-        (i32.const 0x80000000)
-      )
-      (i32.const 0)
-    ))
-    ;; i32(x - { 0x80000000 }) < 0  ->  skip
-    (drop (i32.lt_s
-      (i32.sub
-        (local.get $x)
-        (i32.const 0x80000000)
-      )
-      (i32.const 0)
-    ))
-    ;; i32(x - { 0x80000000 }) >= 0  ->  skip
-    (drop (i32.ge_s
-      (i32.sub
-        (local.get $x)
-        (i32.const 0x80000000)
+      (i32.eq
+        (local.get $y)
+        (call $ne0)
       )
-      (i32.const 0)
     ))
-    ;; i32(x - { 0x80000000 }) > 0  ->  skip
-    (drop (i32.gt_s
-      (i32.sub
+  )
+  ;; CHECK:      (func $combine-or (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $combine-or (param $x i32) (param $y i32)
+    (drop (i32.or
+      (i32.gt_s
         (local.get $x)
-        (block (result i32)
-          (i32.const 0x80000000)
-        )
+        (local.get $y)
       )
-      (i32.const 0)
-    ))
-    ;; i32(x - { 0x80000000 }) <= 0  ->  skip
-    (drop (i32.gt_s
-      (i32.sub
+      (i32.eq
         (local.get $x)
-        (block (result i32)
-          (i32.const 0x80000000)
-        )
+        (local.get $y)
       )
-      (i32.const 0)
     ))
+    ;; TODO: more stuff here
   )
-  ;; CHECK:      (func $unsigned-context (param $x i32) (param $y i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.div_u
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK:      (func $select-into-arms (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-into-arms (param $x i32) (param $y i32)
+    (if
+      (select
+        (i32.eqz (i32.eqz (local.get $x)))
+        (i32.eqz (i32.eqz (local.get $y)))
+        (local.get $y)
+      )
+      (unreachable)
+    )
+  )
+  ;; CHECK:      (func $select-with-same-arm-and-cond-32 (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.div_s
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const -3)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eq
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shr_u
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-with-same-arm-and-cond-32 (param $x i32)
+    ;; i32(x) ? i32(x) : 0  ==>  x
+    (drop (select
+      (local.get $x)
+      (i32.const 0)
+      (local.get $x)
+    ))
+    ;; i32(x) ? 0 : i32(x)  ==>  {x, 0}
+    (drop (select
+      (i32.const 0)
+      (local.get $x)
+      (local.get $x)
+    ))
+    ;; i32(x) == 0 ? i32(x) : 0  ==>  {x, 0}
+    (drop (select
+      (local.get $x)
+      (i32.const 0)
+      (i32.eqz (local.get $x))
+    ))
+  )
+
+  ;; CHECK:      (func $select-with-same-arm-and-cond-64 (param $x i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.div_s
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const -1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rem_u
-  ;; CHECK-NEXT:    (i32.and
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 3)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shr_u
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (block (result i64)
+  ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_u
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (block (result i64)
+  ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ge_s
-  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:   (block (result i64)
+  ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i32.const 2147483647)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.const -7)
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $unsigned-context (param $x i32) (param $y i64)
-    (drop (i32.div_s
-      (i32.and
+  (func $select-with-same-arm-and-cond-64 (param $x i64)
+    ;; i64(x) != 0 ? i64(x) : 0  ==>  x
+    (drop (select
+      (local.get $x)
+      (i64.const 0)
+      (i64.ne
         (local.get $x)
-        (i32.const 0x7fffffff)
+        (i64.const 0)
       )
-      (i32.const 3)
     ))
-    (drop (i32.div_s
-      (i32.and
+    ;; i64(x) == 0 ? 0 : i64(x)  ==>  x
+    (drop (select
+      (i64.const 0)
+      (local.get $x)
+      (i64.eqz
         (local.get $x)
-        (i32.const 0x7fffffff)
-      )
-      (i32.const -3) ;; skip
-    ))
-    (drop (i32.div_s
-      (i32.and
-        (local.get $x)
-        (i32.const 0x7fffffff)
-      )
-      (i32.const 0x80000000) ;; skip
-    ))
-    (drop (i64.div_s
-      (i64.and
-        (local.get $y)
-        (i64.const 0x7fffffffffffffff)
-      )
-      (i64.const 2)
-    ))
-     (drop (i64.div_s
-      (i64.and
-        (local.get $y)
-        (i64.const 0x7fffffffffffffff)
-      )
-      (i64.const -1) ;; skip
-    ))
-    (drop (i32.rem_s
-      (i32.and
-        (local.get $x)
-        (i32.const 0x7fffffff)
       )
-      (i32.const 3)
     ))
-    (drop (i32.shr_s
-      (i32.and
+    ;; i64(x) != 0 ? 0 : i64(x)  ==>  0
+    (drop (select
+      (i64.const 0)
+      (local.get $x)
+      (i64.ne
         (local.get $x)
-        (i32.const 0x7fffffff)
+        (i64.const 0)
       )
-      (i32.const 7)
     ))
-    (drop (i32.ge_s
-      (i32.and
+    ;; i64(x) == 0 ? i64(x) : 0  ==>  {x, 0}
+    (drop (select
+      (local.get $x)
+      (i64.const 0)
+      (i64.eqz
         (local.get $x)
-        (i32.const 0x7fffffff)
       )
-      (i32.const 7)
     ))
-    (drop (i32.ge_s
-      (i32.and
+    (drop (select
+      (local.get $x)
+      (i64.const 0)
+      (i64.eq
         (local.get $x)
-        (i32.const 0x7fffffff)
+        (i64.const 0)
       )
-      (i32.const -7) ;; skip
     ))
   )
-  ;; CHECK:      (func $optimize-float-mul-by-two (param $0 f64) (param $1 f32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.add
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.add
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:    (local.get $1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (call $tee-with-unreachable-value)
-  ;; CHECK-NEXT:    (f64.const 2)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $0)
-  ;; CHECK-NEXT:    (f64.const -2)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $optimize-float-mul-by-two (param $0 f64) (param $1 f32)
-    (drop (f64.mul
-      (local.get $0)
-      (f64.const 2)
-    ))
-    (drop (f32.mul
-      (local.get $1)
-      (f32.const 2)
-    ))
 
-    (drop (f64.mul
-      (call $tee-with-unreachable-value) ;; side effect
-      (f64.const 2)
-    ))
-    (drop (f64.mul
-      (f64.neg (local.get $0)) ;; complex expression
-      (f64.const 2)
-    ))
-  )
-  ;; CHECK:      (func $duplicate-elimination (param $x i32) (param $y i32) (param $z i32) (param $w f64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (local.get $w)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
+  ;; CHECK:      (func $select-with-same-arm-and-cond-skips (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.ceil
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.floor
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.trunc
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.nearest
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.nearest
-  ;; CHECK-NEXT:    (f64.trunc
-  ;; CHECK-NEXT:     (local.get $w)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.ne
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.trunc
-  ;; CHECK-NEXT:    (f64.nearest
-  ;; CHECK-NEXT:     (local.get $w)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:    (i64.eq
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $select-with-same-arm-and-cond-skips (param $x i32) (param $y i64)
+    ;; skip not equals
+    (drop (select
+      (local.get $x)
+      (i32.const 0)
+      (i32.wrap_i64 (local.get $y))
+    ))
+    (drop (select
+      (i32.const 0)
+      (i32.sub (i32.const 0) (local.get $x))
+      (local.get $x)
+    ))
+
+    ;; skip not zero
+    (drop (select
+      (local.get $x)
+      (i32.const -1)
+      (local.get $x)
+    ))
+    (drop (select
+      (i32.const -1)
+      (local.get $x)
+      (local.get $x)
+    ))
+    (drop (select
+      (i64.const -1)
+      (local.get $y)
+      (i64.ne
+        (local.get $y)
+        (i64.const 0)
+      )
+    ))
+    (drop (select
+      (i64.const 0)
+      (local.get $y)
+      (i64.ne
+        (local.get $y)
+        (i64.const 1)
+      )
+    ))
+  )
+
+  ;; CHECK:      (func $select-with-same-arm-and-cond-skips-side-effects (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $w)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.neg
-  ;; CHECK-NEXT:    (local.get $w)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $w)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.div_u
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.div_u
+  ;; CHECK-NEXT:     (i32.const 10)
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (call $ne0)
+  ;; CHECK-NEXT:    (call $ne0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (call $select-sign-64-lt
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:    (i64.eqz
+  ;; CHECK-NEXT:     (call $select-sign-64-lt
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.ne
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i64.const 0)
+  ;; CHECK-NEXT:    (call $select-sign-64-lt
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.eqz
+  ;; CHECK-NEXT:     (call $select-sign-64-lt
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT: )
+  (func $select-with-same-arm-and-cond-skips-side-effects (param $x i32) (param $y i64)
+    ;; skip with side effects
+    (drop (select
+      (i32.div_u (i32.const 10) (local.get $x))
+      (i32.const 0)
+      (i32.div_u (i32.const 10) (local.get $x))
+    ))
+    (drop (select
+      (i32.const 0)
+      (call $ne0)
+      (call $ne0)
+    ))
+    (drop (select
+      (call $select-sign-64-lt (local.get $y))
+      (i64.const 0)
+      (i64.eqz
+        (call $select-sign-64-lt (local.get $y))
+      )
+    ))
+    (drop (select
+      (i64.const 0)
+      (call $select-sign-64-lt (local.get $y))
+      (i64.eqz
+        (call $select-sign-64-lt (local.get $y))
+      )
+    ))
+  )
+  ;; CHECK:      (func $optimize-boolean-context (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rem_s
+  ;; CHECK-NEXT:   (select
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rem_u
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-boolean-context (param $x i32) (param $y i32)
+    ;; 0 - x   ==>   x
+    (if
+      (i32.sub
+        (i32.const 0)
+        (local.get $x)
+      )
+      (unreachable)
+    )
+    (drop (select
+      (local.get $x)
+      (local.get $y)
+      (i32.sub
+        (i32.const 0)
+        (local.get $x)
+      )
+    ))
+  )
+
+  ;; CHECK:      (func $optimize-combined-by-and-equals-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.sub
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.or
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (i64.or
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-and-equals-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) == 0) & (i32(y) == 0)   ==>   i32(x | y) == 0
+    (drop (i32.and
+      (i32.eq (local.get $x) (i32.const 0))
+      (i32.eq (local.get $y) (i32.const 0))
+    ))
+    ;; (i64(x) == 0) & (i64(y) == 0)   ==>   i64(x | y) == 0
+    (drop (i32.and
+      (i64.eq (local.get $a) (i64.const 0))
+      (i64.eq (local.get $b) (i64.const 0))
+    ))
+  )
+  ;; CHECK:      (func $optimize-combined-by-or-noequal-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (i64.or
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-or-noequal-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) != 0) | (i32(y) != 0)   ==>   i32(x | y) != 0
+    (drop (i32.or
+      (i32.ne (local.get $x) (i32.const 0))
+      (i32.ne (local.get $y) (i32.const 0))
+    ))
+    ;; (i64(x) != 0) | (i64(y) != 0)   ==>   i64(x | y) != 0
+    (drop (i32.or
+      (i64.ne (local.get $a) (i64.const 0))
+      (i64.ne (local.get $b) (i64.const 0))
+    ))
+  )
+  ;; CHECK:      (func $optimize-combined-by-or-gt-or-eq-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i64.ge_s
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.ge_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.ge_s
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-or-gt-or-eq-to-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) >= 0) | (i32(y) >= 0)   ==>   i32(x & y) >= 0
+    (drop (i32.or
+      (i32.ge_s (local.get $x) (i32.const 0))
+      (i32.ge_s (local.get $y) (i32.const 0))
+    ))
+    ;; (i64(x) >= 0) | (i64(y) >= 0)   ==>   i64(x & y) >= 0
+    (drop (i32.or
+      (i64.ge_s (local.get $a) (i64.const 0))
+      (i64.ge_s (local.get $b) (i64.const 0))
+    ))
+
+    ;; skips
+    ;; (i32(x) >= 0) | (i64(y) >= 0)   ==>   skip
+    (drop (i32.or
+      (i32.ge_s (local.get $x) (i32.const 0))
+      (i64.ge_s (local.get $a) (i64.const 0))
+    ))
+  )
+  ;; CHECK:      (func $optimize-combined-by-or-ne-to-minus-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const -1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:    (i32.ne
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const -1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.ne
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (i64.const -1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-or-ne-to-minus-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) != -1) | (i32(y) != -1)   ==>   i32(x & y) != -1
+    (drop (i32.or
+      (i32.ne (local.get $x) (i32.const -1))
+      (i32.ne (local.get $y) (i32.const -1))
+    ))
+    ;; (i64(x) != -1) | (i64(y) == -1)   ==>   i64(x & y) != -1
+    (drop (i32.or
+      (i64.ne (local.get $a) (i64.const -1))
+      (i64.ne (local.get $b) (i64.const -1))
+    ))
+
+    ;; skips
+    ;; (i32(x) != -1) | (i64(y) != -1)   ==>   skip
+    (drop (i32.or
+      (i32.ne (local.get $x) (i32.const -1))
+      (i64.ne (local.get $a) (i64.const -1))
+    ))
+  )
+  ;; CHECK:      (func $optimize-combined-by-or-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   (i32.lt_s
   ;; CHECK-NEXT:    (i32.or
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (call $ne0)
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (i64.lt_s
+  ;; CHECK-NEXT:    (i64.or
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:    (i32.lt_s
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.lt_s
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (i64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (call $ne0)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (call $ne0)
-  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (call $ne0)
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:    (i32.lt_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.le_s
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rem_s
-  ;; CHECK-NEXT:    (i32.rem_s
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.lt_s
   ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.le_s
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-or-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) < 0) | (i32(y) < 0)   ==>   i32(x | y) < 0
+    (drop (i32.or
+      (i32.lt_s (local.get $x) (i32.const 0))
+      (i32.lt_s (local.get $y) (i32.const 0))
+    ))
+    ;; (i64(x) < 0) | (i64(y) < 0)   ==>   i64(x | y) < 0
+    (drop (i32.or
+      (i64.lt_s (local.get $a) (i64.const 0))
+      (i64.lt_s (local.get $b) (i64.const 0))
+    ))
+
+    ;; skips
+    ;; (i32(x) < 0) | (i64(a) < 0)   ==>   skip
+    (drop (i32.or
+      (i32.lt_s (local.get $x) (i32.const 0))
+      (i64.lt_s (local.get $a) (i64.const 0))
+    ))
+    ;; (i32(x) <= 0) | (i32(y) < 0)   ==>   skip
+    (drop (i32.or
+      (i32.le_s (local.get $x) (i32.const 0))
+      (i32.lt_s (local.get $y) (i32.const 0))
+    ))
+    ;; (i32(x) < 1) | (i32(y) < 0)   ==>   skip
+    (drop (i32.or
+      (i32.lt_s (local.get $x) (i32.const 1))
+      (i32.lt_s (local.get $y) (i32.const 0))
+    ))
+  )
+  ;; CHECK:      (func $optimize-combined-by-and-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.rem_u
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:    (i32.rem_u
+  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:    (i32.and
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.or
-  ;; CHECK-NEXT:     (local.tee $x
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:   (i64.lt_s
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-and-lessthan-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) < 0) & (i32(y) < 0)   ==>   i32(x & y) < 0
+    (drop (i32.and
+      (i32.lt_s (local.get $x) (i32.const 0))
+      (i32.lt_s (local.get $y) (i32.const 0))
+    ))
+    ;; (i64(x) < 0) & (i64(y) < 0)   ==>   i64(x & y) < 0
+    (drop (i32.and
+      (i64.lt_s (local.get $a) (i64.const 0))
+      (i64.lt_s (local.get $b) (i64.const 0))
+    ))
+  )
+  ;; CHECK:      (func $optimize-combined-by-and-greatequal-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:   (i32.ge_s
   ;; CHECK-NEXT:    (i32.or
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.tee $x
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.xor
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i32.xor
-  ;; CHECK-NEXT:     (local.tee $x
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:   (i64.ge_s
+  ;; CHECK-NEXT:    (i64.or
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.xor
-  ;; CHECK-NEXT:    (i32.xor
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (i32.ge_s
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.tee $x
-  ;; CHECK-NEXT:      (i32.const 1)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.ge_s
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (i64.const 0)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $duplicate-elimination (param $x i32) (param $y i32) (param $z i32) (param $w f64)
-    ;; unary
-    (drop (f64.abs (f64.abs (local.get $w))))
-    (drop (f64.ceil (f64.ceil (local.get $w))))
-    (drop (f64.floor (f64.floor (local.get $w))))
-    (drop (f64.trunc (f64.trunc (local.get $w))))
-    (drop (f64.nearest (f64.nearest (local.get $w))))
-
-    (drop (f64.nearest (f64.trunc (local.get $w)))) ;; skip
-    (drop (f64.trunc (f64.nearest (local.get $w)))) ;; skip
-
-    (drop (f64.neg (f64.neg (local.get $w))))
-    (drop (f64.neg (f64.neg (f64.neg (local.get $w)))))
-    (drop (f64.neg (f64.neg (f64.neg (f64.neg (local.get $w))))))
-
-    (drop (i32.eqz (i32.eqz (local.get $x)))) ;; skip
-    (drop (i32.eqz (i32.eqz (i32.eqz (local.get $x)))))
-    (drop (i32.eqz (i32.eqz (i64.eqz (i64.const 1)))))
-    (drop (i32.eqz (i32.eqz (i32.ne (local.get $x) (i32.const 2)))))
-
-    (drop (i32.eqz
-      (i32.eqz
-        (i32.and
-          (local.get $x)
-          (i32.const 1)
-        )
-      )
+  (func $optimize-combined-by-and-greatequal-zero (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) >= 0) & (i32(y) >= 0)   ==>   i32(x | y) >= 0
+    (drop (i32.and
+      (i32.ge_s (local.get $x) (i32.const 0))
+      (i32.ge_s (local.get $y) (i32.const 0))
     ))
-
-    ;; binary
-    ;; ((signed)x % y) % y
-    (drop (i32.rem_s
-      (i32.rem_s
-        (local.get $x)
-        (local.get $y)
-      )
-      (local.get $y)
+    ;; (i64(x) >= 0) & (i64(y) >= 0)   ==>   i64(x | y) >= 0
+    (drop (i32.and
+      (i64.ge_s (local.get $a) (i64.const 0))
+      (i64.ge_s (local.get $b) (i64.const 0))
     ))
-    ;; ((unsigned)x % y) % y
-    (drop (i32.rem_u
-      (i32.rem_u
-        (local.get $x)
-        (local.get $y)
-      )
-      (local.get $y)
+
+    ;; skips
+    ;; (i32(x) >= 0) & (i64(y) >= 0)   ==>   skip
+    (drop (i32.and
+      (i32.ge_s (local.get $x) (i32.const 0))
+      (i64.ge_s (local.get $a) (i64.const 0))
     ))
-    ;; 0 - (0 - y)
-    (drop (i32.sub
-      (i32.const 0)
-      (i32.sub
-        (i32.const 0)
-        (local.get $y)
-      )
+  )
+  ;; CHECK:      (func $optimize-combined-by-and-equal-neg-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (i32.eq
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const -1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.eq
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (i64.const -1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-combined-by-and-equal-neg-one (param $x i32) (param $y i32) (param $a i64) (param $b i64)
+    ;; (i32(x) == -1) & (i32(y) == -1)   ==>   i32(x & y) == -1
+    (drop (i32.and
+      (i32.eq (local.get $x) (i32.const -1))
+      (i32.eq (local.get $y) (i32.const -1))
     ))
-    ;; x - (x - y)
-    (drop (i32.sub
-      (local.get $x)
-      (i32.sub
-        (local.get $x)
-        (local.get $y)
-      )
+    ;; (i64(x) == -1) & (i64(y) == -1)   ==>   i64(x & y) == -1
+    (drop (i32.and
+      (i64.eq (local.get $a) (i64.const -1))
+      (i64.eq (local.get $b) (i64.const -1))
     ))
-    ;; y - (x - y)   -   skip
-    (drop (i32.sub
-      (local.get $y)
-      (i32.sub
-        (local.get $x)
-        (local.get $y)
-      )
+
+    ;; skips
+    ;; (i32(x) == -1) & (i64(y) == -1)   ==>   skip
+    (drop (i32.and
+      (i32.eq (local.get $x) (i32.const -1))
+      (i64.eq (local.get $a) (i64.const -1))
     ))
-    ;; x ^ (x ^ y)
-    (drop (i32.xor
-      (local.get $x)
-      (i32.xor
+  )
+  ;; CHECK:      (func $optimize-relationals (param $x i32) (param $y i32) (param $X i64) (param $Y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483647)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 2147483647)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (local.get $X)
+  ;; CHECK-NEXT:    (local.get $Y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (local.get $X)
+  ;; CHECK-NEXT:    (local.get $Y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.ne
+  ;; CHECK-NEXT:    (local.get $X)
+  ;; CHECK-NEXT:    (local.get $Y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.gt_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.le_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.lt_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const -2147483648)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const -2147483648)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.gt_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (i32.const -2147483648)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.gt_s
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (block (result i32)
+  ;; CHECK-NEXT:      (i32.const -2147483648)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-relationals (param $x i32) (param $y i32) (param $X i64) (param $Y i64)
+    ;; eqz(x + 0x7FFFFFFF)  ->  x == -2147483647
+    (drop (i32.eqz
+      (i32.add
         (local.get $x)
-        (local.get $y)
+        (i32.const 0x7FFFFFFF)
       )
     ))
-    ;; x ^ (y ^ x)
-    (drop (i32.xor
-      (local.get $x)
-      (i32.xor
-        (local.get $y)
+    ;; eqz(x + 0x80000000)  ->  x == -2147483648
+    (drop (i32.eqz
+      (i32.add
         (local.get $x)
+        (i32.const 0x80000000)
       )
     ))
-    ;; (x ^ y) ^ x
-    (drop (i32.xor
-      (i32.xor
+    ;; eqz(x + 0x80000001)  ->  x == 2147483647
+    (drop (i32.eqz
+      (i32.add
         (local.get $x)
-        (local.get $y)
+        (i32.const 0x80000001)
       )
-      (local.get $x)
     ))
-    ;; (y ^ x) ^ x
-    (drop (i32.xor
-      (i32.xor
-        (local.get $y)
+    ;; eqz(x - y)
+    (drop (i32.eqz
+      (i32.sub
         (local.get $x)
+        (local.get $y)
       )
-      (local.get $x)
     ))
-    ;; x ^ (x ^ x)
-    (drop (i32.xor
-      (local.get $x)
-      (i32.xor
-        (local.get $x)
-        (local.get $x)
+    (drop (i64.eqz
+      (i64.sub
+        (local.get $X)
+        (local.get $Y)
       )
     ))
-    ;; x & (x & y)
-    (drop (i32.and
-      (local.get $x)
-      (i32.and
+    ;; x - y == 0
+    (drop (i32.eq
+      (i32.sub
         (local.get $x)
         (local.get $y)
       )
+      (i32.const 0)
     ))
-    ;; x & (y & x)
-    (drop (i32.and
-      (local.get $x)
-      (i32.and
-        (local.get $y)
-        (local.get $x)
+    (drop (i64.eq
+      (i64.sub
+        (local.get $X)
+        (local.get $Y)
       )
+      (i64.const 0)
     ))
-    ;; (x & y) & x
-    (drop (i32.and
-      (i32.and
+    ;; x - y != 0
+    (drop (i32.ne
+      (i32.sub
         (local.get $x)
         (local.get $y)
       )
-      (local.get $x)
+      (i32.const 0)
     ))
-    ;; (y & x) & x
-    (drop (i32.and
-      (i32.and
-        (local.get $y)
-        (local.get $x)
+    (drop (i64.ne
+      (i64.sub
+        (local.get $X)
+        (local.get $Y)
       )
-      (local.get $x)
+      (i64.const 0)
     ))
-    ;; x | (x | y)
-    (drop (i32.or
-      (local.get $x)
-      (i32.or
+    ;; i32(x - y) > 0  ->  x > y
+    (drop (i32.gt_s
+      (i32.sub
         (local.get $x)
         (local.get $y)
       )
+      (i32.const 0)
     ))
-    ;; x | (y | x)
-    (drop (i32.or
-      (local.get $x)
-      (i32.or
+    ;; i32(x - y) >= 0  ->  x >= y
+    (drop (i32.ge_s
+      (i32.sub
+        (local.get $x)
         (local.get $y)
+      )
+      (i32.const 0)
+    ))
+    ;; u32(x - y) > 0  ->  x != y
+    (drop (i32.gt_u
+      (i32.sub
         (local.get $x)
+        (local.get $y)
       )
+      (i32.const 0)
     ))
-    ;; (x | y) | x
-    (drop (i32.or
-      (i32.or
+    ;; u32(x - y) >= 0  ->  1
+    (drop (i32.ge_u
+      (i32.sub
         (local.get $x)
         (local.get $y)
       )
-      (local.get $x)
+      (i32.const 0)
     ))
-    ;; (y | x) | x
-    (drop (i32.or
-      (i32.or
+    ;; u64(x - y) >= 0  ->  i32(1)
+    (drop (i64.ge_u
+      (i64.sub
+        (local.get $X)
+        (local.get $Y)
+      )
+      (i64.const 0)
+    ))
+    ;; i32(x - y) < 0  ->  x < y
+    (drop (i32.lt_s
+      (i32.sub
+        (local.get $x)
         (local.get $y)
+      )
+      (i32.const 0)
+    ))
+    ;; i32(x - y) <= 0  ->  x <= y
+    (drop (i32.le_s
+      (i32.sub
         (local.get $x)
+        (local.get $y)
       )
-      (local.get $x)
+      (i32.const 0)
     ))
-    ;; (y | x) | z   -   skip
-    (drop (i32.or
-      (i32.or
+    ;; u32(x - y) < 0  ->  0
+    (drop (i32.lt_u
+      (i32.sub
+        (local.get $x)
         (local.get $y)
+      )
+      (i32.const 0)
+    ))
+    ;; u64(x - y) < 0  ->  i32(0)
+    (drop (i64.lt_u
+      (i64.sub
+        (local.get $X)
+        (local.get $Y)
+      )
+      (i64.const 0)
+    ))
+    ;; u32(x - y) <= 0  ->  x == y
+    (drop (i32.le_u
+      (i32.sub
         (local.get $x)
+        (local.get $y)
       )
-      (local.get $z)
+      (i32.const 0)
     ))
-    ;; (z | x) | y   -   skip
-    (drop (i32.or
-      (i32.or
-        (local.get $z)
+    ;; i32(x - 0x80000000) == 0  ->  x == 0x80000000
+    (drop (i32.eq
+      (i32.sub
         (local.get $x)
+        (i32.const 0x80000000)
       )
-      (local.get $y)
+      (i32.const 0)
     ))
-    ;; (SE() | x) | x
-    (drop (i32.or
-      (i32.or
-        (call $ne0) ;; side effect
+    ;; i32(x - 0x80000000) != 0  ->  x == 0x80000000
+    (drop (i32.ne
+      (i32.sub
         (local.get $x)
+        (i32.const 0x80000000)
       )
-      (local.get $x)
+      (i32.const 0)
     ))
-    ;; (x | SE()) | SE()   -   skip
-    (drop (i32.or
-      (i32.or
+    ;; i32(x - { 0x80000000 }) < 0  ->  skip
+    (drop (i32.lt_s
+      (i32.sub
         (local.get $x)
-        (call $ne0) ;; side effect
+        (i32.const 0x80000000)
       )
-      (call $ne0) ;; side effect
+      (i32.const 0)
     ))
-    ;; x | (SE() | x)
-    (drop (i32.or
-      (local.get $x)
-      (i32.or
+    ;; i32(x - { 0x80000000 }) >= 0  ->  skip
+    (drop (i32.ge_s
+      (i32.sub
         (local.get $x)
-        (call $ne0) ;; side effect
+        (i32.const 0x80000000)
       )
+      (i32.const 0)
     ))
-    ;; SE() | (x | SE())   -   skip
-    (drop (i32.or
-      (call $ne0) ;; side effect
-      (i32.or
-        (call $ne0) ;; side effect
+    ;; i32(x - { 0x80000000 }) > 0  ->  skip
+    (drop (i32.gt_s
+      (i32.sub
         (local.get $x)
+        (block (result i32)
+          (i32.const 0x80000000)
+        )
       )
+      (i32.const 0)
     ))
-    ;; (y % x) % y   -   skip
-    (drop (i32.rem_s
-      (i32.rem_s
-        (local.get $y)
+    ;; i32(x - { 0x80000000 }) <= 0  ->  skip
+    (drop (i32.gt_s
+      (i32.sub
         (local.get $x)
+        (block (result i32)
+          (i32.const 0x80000000)
+        )
       )
-      (local.get $y)
+      (i32.const 0)
     ))
-    ;; y % (x % y)   -   skip
-    (drop (i32.rem_u
-      (local.get $y)
-      (i32.rem_u
+  )
+  ;; CHECK:      (func $unsigned-context (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.div_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.div_s
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const -3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shr_u
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.div_s
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 9223372036854775807)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const -1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.rem_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 2147483647)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $unsigned-context (param $x i32) (param $y i64)
+    (drop (i32.div_s
+      (i32.and
         (local.get $x)
-        (local.get $y)
+        (i32.const 0x7fffffff)
       )
+      (i32.const 3)
     ))
-    ;; x | (y | x)   where x and y cannot be reordered  -  skip
-    (drop
-      (i32.or
+    (drop (i32.div_s
+      (i32.and
         (local.get $x)
-        (i32.or
-          (local.tee $x
-            (i32.const 1)
-          )
-          (local.get $x)
-        )
+        (i32.const 0x7fffffff)
       )
-    )
+      (i32.const -3) ;; skip
+    ))
+    (drop (i32.div_s
+      (i32.and
+        (local.get $x)
+        (i32.const 0x7fffffff)
+      )
+      (i32.const 0x80000000) ;; skip
+    ))
+    (drop (i64.div_s
+      (i64.and
+        (local.get $y)
+        (i64.const 0x7fffffffffffffff)
+      )
+      (i64.const 2)
+    ))
+     (drop (i64.div_s
+      (i64.and
+        (local.get $y)
+        (i64.const 0x7fffffffffffffff)
+      )
+      (i64.const -1) ;; skip
+    ))
+    (drop (i32.rem_s
+      (i32.and
+        (local.get $x)
+        (i32.const 0x7fffffff)
+      )
+      (i32.const 3)
+    ))
+    (drop (i32.shr_s
+      (i32.and
+        (local.get $x)
+        (i32.const 0x7fffffff)
+      )
+      (i32.const 7)
+    ))
+    (drop (i32.ge_s
+      (i32.and
+        (local.get $x)
+        (i32.const 0x7fffffff)
+      )
+      (i32.const 7)
+    ))
+    (drop (i32.ge_s
+      (i32.and
+        (local.get $x)
+        (i32.const 0x7fffffff)
+      )
+      (i32.const -7) ;; skip
+    ))
+  )
+  ;; CHECK:      (func $optimize-float-mul-by-two (param $0 f64) (param $1 f32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.add
+  ;; CHECK-NEXT:    (local.get $0)
+  ;; CHECK-NEXT:    (local.get $0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.add
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:    (local.get $1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (call $tee-with-unreachable-value)
+  ;; CHECK-NEXT:    (f64.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $0)
+  ;; CHECK-NEXT:    (f64.const -2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-float-mul-by-two (param $0 f64) (param $1 f32)
+    (drop (f64.mul
+      (local.get $0)
+      (f64.const 2)
+    ))
+    (drop (f32.mul
+      (local.get $1)
+      (f32.const 2)
+    ))
+
+    (drop (f64.mul
+      (call $tee-with-unreachable-value) ;; side effect
+      (f64.const 2)
+    ))
+    (drop (f64.mul
+      (f64.neg (local.get $0)) ;; complex expression
+      (f64.const 2)
+    ))
+  )
+  ;; CHECK:      (func $duplicate-elimination (param $x i32) (param $y i32) (param $z i32) (param $w f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.ceil
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.floor
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.trunc
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.nearest
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.nearest
+  ;; CHECK-NEXT:    (f64.trunc
+  ;; CHECK-NEXT:     (local.get $w)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.trunc
+  ;; CHECK-NEXT:    (f64.nearest
+  ;; CHECK-NEXT:     (local.get $w)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $w)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.neg
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $w)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.rem_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.rem_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.sub
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (call $ne0)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (call $ne0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (call $ne0)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (call $ne0)
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (call $ne0)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.rem_s
+  ;; CHECK-NEXT:    (i32.rem_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.rem_u
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:    (i32.rem_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (local.tee $x
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.or
+  ;; CHECK-NEXT:    (i32.or
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.tee $x
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.xor
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.xor
+  ;; CHECK-NEXT:     (local.tee $x
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.xor
+  ;; CHECK-NEXT:    (i32.xor
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.tee $x
+  ;; CHECK-NEXT:      (i32.const 1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $duplicate-elimination (param $x i32) (param $y i32) (param $z i32) (param $w f64)
+    ;; unary
+    (drop (f64.abs (f64.abs (local.get $w))))
+    (drop (f64.ceil (f64.ceil (local.get $w))))
+    (drop (f64.floor (f64.floor (local.get $w))))
+    (drop (f64.trunc (f64.trunc (local.get $w))))
+    (drop (f64.nearest (f64.nearest (local.get $w))))
+
+    (drop (f64.nearest (f64.trunc (local.get $w)))) ;; skip
+    (drop (f64.trunc (f64.nearest (local.get $w)))) ;; skip
+
+    (drop (f64.neg (f64.neg (local.get $w))))
+    (drop (f64.neg (f64.neg (f64.neg (local.get $w)))))
+    (drop (f64.neg (f64.neg (f64.neg (f64.neg (local.get $w))))))
+
+    (drop (i32.eqz (i32.eqz (local.get $x)))) ;; skip
+    (drop (i32.eqz (i32.eqz (i32.eqz (local.get $x)))))
+    (drop (i32.eqz (i32.eqz (i64.eqz (i64.const 1)))))
+    (drop (i32.eqz (i32.eqz (i32.ne (local.get $x) (i32.const 2)))))
+
+    (drop (i32.eqz
+      (i32.eqz
+        (i32.and
+          (local.get $x)
+          (i32.const 1)
+        )
+      )
+    ))
+
+    ;; binary
+    ;; ((signed)x % y) % y
+    (drop (i32.rem_s
+      (i32.rem_s
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $y)
+    ))
+    ;; ((unsigned)x % y) % y
+    (drop (i32.rem_u
+      (i32.rem_u
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $y)
+    ))
+    ;; 0 - (0 - y)
+    (drop (i32.sub
+      (i32.const 0)
+      (i32.sub
+        (i32.const 0)
+        (local.get $y)
+      )
+    ))
+    ;; x - (x - y)
+    (drop (i32.sub
+      (local.get $x)
+      (i32.sub
+        (local.get $x)
+        (local.get $y)
+      )
+    ))
+    ;; y - (x - y)   -   skip
+    (drop (i32.sub
+      (local.get $y)
+      (i32.sub
+        (local.get $x)
+        (local.get $y)
+      )
+    ))
+    ;; x ^ (x ^ y)
+    (drop (i32.xor
+      (local.get $x)
+      (i32.xor
+        (local.get $x)
+        (local.get $y)
+      )
+    ))
+    ;; x ^ (y ^ x)
+    (drop (i32.xor
+      (local.get $x)
+      (i32.xor
+        (local.get $y)
+        (local.get $x)
+      )
+    ))
+    ;; (x ^ y) ^ x
+    (drop (i32.xor
+      (i32.xor
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $x)
+    ))
+    ;; (y ^ x) ^ x
+    (drop (i32.xor
+      (i32.xor
+        (local.get $y)
+        (local.get $x)
+      )
+      (local.get $x)
+    ))
+    ;; x ^ (x ^ x)
+    (drop (i32.xor
+      (local.get $x)
+      (i32.xor
+        (local.get $x)
+        (local.get $x)
+      )
+    ))
+    ;; x & (x & y)
+    (drop (i32.and
+      (local.get $x)
+      (i32.and
+        (local.get $x)
+        (local.get $y)
+      )
+    ))
+    ;; x & (y & x)
+    (drop (i32.and
+      (local.get $x)
+      (i32.and
+        (local.get $y)
+        (local.get $x)
+      )
+    ))
+    ;; (x & y) & x
+    (drop (i32.and
+      (i32.and
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $x)
+    ))
+    ;; (y & x) & x
+    (drop (i32.and
+      (i32.and
+        (local.get $y)
+        (local.get $x)
+      )
+      (local.get $x)
+    ))
+    ;; x | (x | y)
+    (drop (i32.or
+      (local.get $x)
+      (i32.or
+        (local.get $x)
+        (local.get $y)
+      )
+    ))
+    ;; x | (y | x)
+    (drop (i32.or
+      (local.get $x)
+      (i32.or
+        (local.get $y)
+        (local.get $x)
+      )
+    ))
+    ;; (x | y) | x
+    (drop (i32.or
+      (i32.or
+        (local.get $x)
+        (local.get $y)
+      )
+      (local.get $x)
+    ))
+    ;; (y | x) | x
+    (drop (i32.or
+      (i32.or
+        (local.get $y)
+        (local.get $x)
+      )
+      (local.get $x)
+    ))
+    ;; (y | x) | z   -   skip
+    (drop (i32.or
+      (i32.or
+        (local.get $y)
+        (local.get $x)
+      )
+      (local.get $z)
+    ))
+    ;; (z | x) | y   -   skip
+    (drop (i32.or
+      (i32.or
+        (local.get $z)
+        (local.get $x)
+      )
+      (local.get $y)
+    ))
+    ;; (SE() | x) | x
+    (drop (i32.or
+      (i32.or
+        (call $ne0) ;; side effect
+        (local.get $x)
+      )
+      (local.get $x)
+    ))
+    ;; (x | SE()) | SE()   -   skip
+    (drop (i32.or
+      (i32.or
+        (local.get $x)
+        (call $ne0) ;; side effect
+      )
+      (call $ne0) ;; side effect
+    ))
+    ;; x | (SE() | x)
+    (drop (i32.or
+      (local.get $x)
+      (i32.or
+        (local.get $x)
+        (call $ne0) ;; side effect
+      )
+    ))
+    ;; SE() | (x | SE())   -   skip
+    (drop (i32.or
+      (call $ne0) ;; side effect
+      (i32.or
+        (call $ne0) ;; side effect
+        (local.get $x)
+      )
+    ))
+    ;; (y % x) % y   -   skip
+    (drop (i32.rem_s
+      (i32.rem_s
+        (local.get $y)
+        (local.get $x)
+      )
+      (local.get $y)
+    ))
+    ;; y % (x % y)   -   skip
+    (drop (i32.rem_u
+      (local.get $y)
+      (i32.rem_u
+        (local.get $x)
+        (local.get $y)
+      )
+    ))
+    ;; x | (y | x)   where x and y cannot be reordered  -  skip
+    (drop
+      (i32.or
+        (local.get $x)
+        (i32.or
+          (local.tee $x
+            (i32.const 1)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (i32.or
+        (i32.or
+          (local.get $x)
+          (local.tee $x
+            (i32.const 1)
+          )
+        )
+        (local.get $x)
+      )
+    )
+    ;; x ^ (y ^ x)   where x and y cannot be reordered  -  skip
+    (drop
+      (i32.xor
+        (local.get $x)
+        (i32.xor
+          (local.tee $x
+            (i32.const 1)
+          )
+          (local.get $x)
+        )
+      )
+    )
+    (drop
+      (i32.xor
+        (i32.xor
+          (local.get $x)
+          (local.tee $x
+            (i32.const 1)
+          )
+        )
+        (local.get $x)
+      )
+    )
+  )
+
+  ;; i32.wrap_i64(i64.extend_i32_s(x))  ==>  x
+  ;; i32.wrap_i64(i64.extend_i32_u(x))  ==>  x
+
+  ;; CHECK:      (func $sign-and-zero-extention-elimination-1 (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $sign-and-zero-extention-elimination-1 (param $x i32)
+    (drop (i32.wrap_i64 (i64.extend_i32_s (local.get $x))))
+    (drop (i32.wrap_i64 (i64.extend_i32_u (local.get $x))))
+  )
+  ;; i64.extend_i32_u(i32.wrap_i64(x))  =>  x,  where maxBits(x) <= 32
+  ;; i64.extend_i32_s(i32.wrap_i64(x))  =>  x,  where maxBits(x) <= 31
+
+  ;; CHECK:      (func $sign-and-zero-extention-elimination-2 (param $x i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i64.const 4294967295)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.and
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i64.const 2147483647)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_s
+  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_s
+  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK-NEXT:     (i64.and
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (i64.const 4294967295)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $sign-and-zero-extention-elimination-2 (param $x i64)
+    (drop (i64.extend_i32_u (i32.wrap_i64 (i64.and (local.get $x) (i64.const 0x00000000FFFFFFFF)))))
+    (drop (i64.extend_i32_s (i32.wrap_i64 (i64.and (local.get $x) (i64.const 0x000000007FFFFFFF)))))
+
+    (drop (i64.extend_i32_u (i32.wrap_i64 (local.get $x)))) ;; skip
+    (drop (i64.extend_i32_s (i32.wrap_i64 (local.get $x)))) ;; skip
+    (drop (i64.extend_i32_s (i32.wrap_i64 (i64.and (local.get $x) (i64.const 0x00000000FFFFFFFF))))) ;; skip
+  )
+  ;; CHECK:      (func $optimize-shifts (param $x i32) (param $y i32) (param $z i64) (param $w i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shl
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shl
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.shr_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shl
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shl
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shr_s
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shr_u
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shl
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $w)
+  ;; CHECK-NEXT:     (i64.const 32)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.shr_u
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $w)
+  ;; CHECK-NEXT:     (i64.const 31)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-shifts (param $x i32) (param $y i32) (param $z i64) (param $w i64)
+    ;; i32
+    (drop (i32.shl
+      (local.get $x)
+      (i32.const 32)
+    ))
+    (drop (i32.shr_s
+      (local.get $x)
+      (i32.const 32)
+    ))
+    (drop (i32.shr_u
+      (local.get $x)
+      (i32.const 64)
+    ))
+    (drop (i32.rotl
+      (local.get $x)
+      (i32.const 64)
+    ))
+    (drop (i32.rotr
+      (local.get $x)
+      (i32.const 64)
+    ))
+    ;; i64
+    (drop (i64.shl
+      (local.get $z)
+      (i64.const 64)
+    ))
+    (drop (i64.shr_s
+      (local.get $z)
+      (i64.const 64)
+    ))
+    (drop (i64.shr_u
+      (local.get $z)
+      (i64.const 128)
+    ))
+    (drop (i64.rotl
+      (local.get $z)
+      (i64.const 128)
+    ))
+    (drop (i64.rotr
+      (local.get $z)
+      (i64.const 128)
+    ))
+
+    ;; i32
+    (drop (i32.shl
+      (local.get $x)
+      (i32.and
+        (local.get $y)
+        (i32.const 31)
+      )
+    ))
+    (drop (i32.shl
+      (local.get $x)
+      (i32.and
+        (local.get $y)
+        (i32.const 63)
+      )
+    ))
+    (drop (i32.shr_s
+      (local.get $x)
+      (i32.and
+        (local.get $y)
+        (i32.const 31)
+      )
+    ))
+    (drop (i32.shr_u
+      (local.get $x)
+      (i32.and
+        (local.get $y)
+        (i32.const 31)
+      )
+    ))
+    ;; i64
+    (drop (i64.shl
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 63)
+      )
+    ))
+    (drop (i64.shl
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 127)
+      )
+    ))
+    (drop (i64.shr_s
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 63)
+      )
+    ))
+    (drop (i64.shr_u
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 63)
+      )
+    ))
+    ;; i32(x) >> (y & 32)  ->  x
+    (drop (i32.shr_u
+      (local.get $x)
+      (i32.and
+        (local.get $y)
+        (i32.const 32)
+      )
+    ))
+    ;; i64(x) >> (y & 64)  ->  x
+    (drop (i64.shr_u
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 128)
+      )
+    ))
+
+    ;; skip
+    (drop (i64.shl
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 32)
+      )
+    ))
+    ;; skip
+    (drop (i64.shr_u
+      (local.get $z)
+      (i64.and
+        (local.get $w)
+        (i64.const 31)
+      )
+    ))
+  )
+  ;; CHECK:      (func $optimize-float-points (param $x0 f64) (param $x1 f64) (param $y0 f32) (param $y1 f32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (f64.add
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.add
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.mul
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (f32.mul
+  ;; CHECK-NEXT:     (local.get $y1)
+  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.mul
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (f64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (f32.mul
+  ;; CHECK-NEXT:     (f32.const 0)
+  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.mul
+  ;; CHECK-NEXT:     (f64.add
+  ;; CHECK-NEXT:      (local.get $x0)
+  ;; CHECK-NEXT:      (local.get $x1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (f64.add
+  ;; CHECK-NEXT:      (local.get $x0)
+  ;; CHECK-NEXT:      (local.get $x0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.sub
+  ;; CHECK-NEXT:     (f64.const 0)
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (f32.sub
+  ;; CHECK-NEXT:     (f32.const 0)
+  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (f64.add
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (f64.add
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.div
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (f32.div
+  ;; CHECK-NEXT:     (local.get $y1)
+  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.mul
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.mul
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.mul
+  ;; CHECK-NEXT:     (call $get-f64)
+  ;; CHECK-NEXT:     (call $get-f64)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.div
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.div
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.div
+  ;; CHECK-NEXT:     (local.get $x0)
+  ;; CHECK-NEXT:     (f64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (f32.div
+  ;; CHECK-NEXT:     (f32.const 0)
+  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.abs
+  ;; CHECK-NEXT:    (f64.div
+  ;; CHECK-NEXT:     (f64.add
+  ;; CHECK-NEXT:      (local.get $x0)
+  ;; CHECK-NEXT:      (local.get $x1)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (f64.add
+  ;; CHECK-NEXT:      (local.get $x0)
+  ;; CHECK-NEXT:      (local.get $x0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $optimize-float-points (param $x0 f64) (param $x1 f64) (param $y0 f32) (param $y1 f32)
+    ;; abs(x) * abs(x)   ==>   x * x
+    (drop (f64.mul
+      (f64.abs (local.get $x0))
+      (f64.abs (local.get $x0))
+    ))
+    (drop (f32.mul
+      (f32.abs (local.get $y0))
+      (f32.abs (local.get $y0))
+    ))
+    (drop (f64.mul
+      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
+      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
+    ))
+
+    ;; abs(x) * abs(y)   ==>   abs(x * y)
+    (drop (f64.mul
+      (f64.abs (local.get $x0))
+      (f64.abs (local.get $x1))
+    ))
+    (drop (f32.mul
+      (f32.abs (local.get $y1))
+      (f32.abs (local.get $y0))
+    ))
+
+    (drop (f64.mul
+      (f64.abs (local.get $x0))
+      (f64.abs (f64.const 0)) ;; skip
+    ))
+    (drop (f32.mul
+      (f32.abs (f32.const 0)) ;; skip
+      (f32.abs (local.get $y0))
+    ))
+    (drop (f64.mul
+      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
+      (f64.abs (f64.add (local.get $x0) (local.get $x0)))
+    ))
+
+
+    ;; abs(-x)   ==>   abs(x)
+    (drop (f64.abs
+      (f64.neg (local.get $x0))
+    ))
+    (drop (f32.abs
+      (f32.neg (local.get $y0))
+    ))
+
+    ;; abs(0 - x)   ==>   skip for non-fast math
+    (drop (f64.abs
+      (f64.sub
+        (f64.const 0)
+        (local.get $x0)
+      )
+    ))
+    (drop (f32.abs
+      (f32.sub
+        (f32.const 0)
+        (local.get $y0)
+      )
+    ))
+
+    ;; abs(x) / abs(x)   ==>   x / x
+    (drop (f64.div
+      (f64.abs (local.get $x0))
+      (f64.abs (local.get $x0))
+    ))
+    (drop (f32.div
+      (f32.abs (local.get $y0))
+      (f32.abs (local.get $y0))
+    ))
+    (drop (f64.div
+      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
+      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
+    ))
+
+    ;; abs(x) / abs(y)   ==>   abs(x / y)
+    (drop (f64.div
+      (f64.abs (local.get $x0))
+      (f64.abs (local.get $x1))
+    ))
+    (drop (f32.div
+      (f32.abs (local.get $y1))
+      (f32.abs (local.get $y0))
+    ))
+
+    ;; abs(x * x)   ==>   x * x
+    (drop (f64.abs
+      (f64.mul
+        (local.get $x0)
+        (local.get $x0)
+      )
+    ))
+    (drop (f32.abs
+      (f32.mul
+        (local.get $y0)
+        (local.get $y0)
+      )
+    ))
+    ;; this one cannot be optimized as the runtime values may differ
+    (drop (f64.abs
+      (f64.mul
+        (call $get-f64)
+        (call $get-f64)
+      )
+    ))
+
+    ;; abs(x / x)   ==>   x / x
+    (drop (f64.abs
+      (f64.div
+        (local.get $x0)
+        (local.get $x0)
+      )
+    ))
+    (drop (f32.abs
+      (f32.div
+        (local.get $y0)
+        (local.get $y0)
+      )
+    ))
+
+    (drop (f64.div
+      (f64.abs (local.get $x0))
+      (f64.abs (f64.const 0)) ;; skip
+    ))
+    (drop (f32.div
+      (f32.abs (f32.const 0)) ;; skip
+      (f32.abs (local.get $y0))
+    ))
+    (drop (f64.div
+      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
+      (f64.abs (f64.add (local.get $x0) (local.get $x0)))
+    ))
+  )
+  ;; CHECK:      (func $ternary (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (select
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (select
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (select
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (select
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (if (result i32)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary (param $x i32) (param $y i32)
+    (drop
+      (select
+        (i32.const 0)
+        (i32.eqz
+          (local.get $y)
+        )
+        (local.get $x)
+      )
+    )
+    (drop
+      (select
+        (i32.const 1)
+        (i32.eqz
+          (local.get $y)
+        )
+        (local.get $x)
+      )
+    )
+    (drop
+      (select
+        (i32.eqz
+          (local.get $y)
+        )
+        (i32.const 0)
+        (local.get $x)
+      )
+    )
+    (drop
+      (select
+        (i32.eqz
+          (local.get $y)
+        )
+        (i32.const 1)
+        (local.get $x)
+      )
+    )
+    ;; if works too
+    (drop
+      (if (result i32)
+        (local.get $x)
+        (i32.eqz
+          (local.get $y)
+        )
+        (i32.const 1)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-i64-0 (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (if (result i64)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i64.const 1)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-i64-0 (param $x i32) (param $y i64)
+    (drop
+      (if (result i32)
+        (local.get $x)
+        (i32.const 0)
+        (i64.eqz
+          (local.get $y)
+        )
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-i64-1 (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.eqz
+  ;; CHECK-NEXT:    (if (result i64)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i64.const 0)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-i64-1 (param $x i32) (param $y i64)
+    (drop
+      (if (result i32)
+        (local.get $x)
+        (i64.eqz
+          (local.get $y)
+        )
+        (i32.const 1)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-no (param $x i32) (param $y i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:    (i32.eqz
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-no (param $x i32) (param $y i32)
+    (drop
+      (select
+        (i32.const 2) ;; only 0 and 1 work
+        (i32.eqz
+          (local.get $y)
+        )
+        (local.get $x)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-no-unreachable-1 (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (if (result i32)
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-no-unreachable-1 (param $x i32) (result i32)
+    (if (result i32)
+      (local.get $x)
+      ;; one arm is an eqz, the other is 0 or 1, so we can put an eqz on the
+      ;; outside in theory, but we'd need to be careful with the unreachable
+      ;; type here. ignore this case, as DCE is the proper optimization anyhow.
+      (i32.eqz
+        (unreachable)
+      )
+      (i32.const 0)
+    )
+  )
+  ;; CHECK:      (func $ternary-no-unreachable-2 (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (if (result i32)
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-no-unreachable-2 (param $x i32) (result i32)
+    (if (result i32)
+      (local.get $x)
+      ;; as before, but flipped
+      (i32.const 0)
+      (i32.eqz
+        (unreachable)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (select
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms (param $x i32) (param $y i32) (param $z i32)
+    (drop
+      (select
+        (i32.eqz (local.get $x))
+        (i32.eqz (local.get $y))
+        (local.get $z)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-if (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (if (result i32)
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-if (param $x i32) (param $y i32) (param $z i32)
+    (drop
+      (if (result i32)
+        (local.get $z)
+        (i32.eqz (local.get $x))
+        (i32.eqz (local.get $y))
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.demote_f64
+  ;; CHECK-NEXT:    (f64.floor
+  ;; CHECK-NEXT:     (if (result f64)
+  ;; CHECK-NEXT:      (local.get $z)
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32)
+    (drop
+      ;; the if's type begins as f32, but after moving code out it will be
+      ;; f64
+      (if (result f32)
+        (local.get $z)
+        (f32.demote_f64 (f64.floor (local.get $x)))
+        (f32.demote_f64 (f64.floor (local.get $y)))
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-more (param $x f32) (param $y f32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.floor
+  ;; CHECK-NEXT:    (f32.neg
+  ;; CHECK-NEXT:     (select
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:      (local.get $z)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-more (param $x f32) (param $y f32) (param $z i32)
+    (drop
+      (select
+        (f32.floor (f32.neg (local.get $x)))
+        (f32.floor (f32.neg (local.get $y)))
+        (local.get $z)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-morer (param $x f32) (param $y f32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.abs
+  ;; CHECK-NEXT:    (f32.floor
+  ;; CHECK-NEXT:     (f32.neg
+  ;; CHECK-NEXT:      (select
+  ;; CHECK-NEXT:       (local.get $x)
+  ;; CHECK-NEXT:       (local.get $y)
+  ;; CHECK-NEXT:       (local.get $z)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-morer (param $x f32) (param $y f32) (param $z i32)
+    (drop
+      (select
+        (f32.abs (f32.floor (f32.neg (local.get $x))))
+        (f32.abs (f32.floor (f32.neg (local.get $y))))
+        (local.get $z)
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-and-type-is-none (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (if (result i32)
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-and-type-is-none (param $x i32) (param $y i32) (param $z i32)
+    (if
+      (local.get $z)
+      (drop (i32.eqz (local.get $x)))
+      (drop (i32.eqz (local.get $y)))
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-and-type-is-none-child-types-mismatch (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (f64.const 2.34)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-and-type-is-none-child-types-mismatch (param $x i32) (param $y i32) (param $z i32)
+    (if
+      (local.get $z)
+      ;; the drop cannot be hoisted out, since the children's type mismatch
+      ;; would not allow us to give a proper type to the if.
+      (drop (i32.const 1))
+      (drop (f64.const 2.34))
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-but-block (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (block (result i32)
+  ;; CHECK-NEXT:     (i32.eqz
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (block (result i32)
+  ;; CHECK-NEXT:     (i32.eqz
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-but-block (param $x i32) (param $y i32) (param $z i32)
     (drop
-      (i32.or
-        (i32.or
-          (local.get $x)
-          (local.tee $x
-            (i32.const 1)
-          )
+      (select
+        ;; identical arms, but they are control flow structures
+        (block (result i32)
+          (i32.eqz (local.get $x))
         )
-        (local.get $x)
+        (block (result i32)
+          (i32.eqz (local.get $y))
+        )
+        (local.get $z)
       )
     )
-    ;; x ^ (y ^ x)   where x and y cannot be reordered  -  skip
+  )
+  ;; CHECK:      (func $ternary-identical-arms-but-binary (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-but-binary (param $x i32) (param $y i32) (param $z i32)
     (drop
-      (i32.xor
-        (local.get $x)
-        (i32.xor
-          (local.tee $x
-            (i32.const 1)
-          )
+      (select
+        ;; identical arms, but they are binaries, not unaries
+        (i32.add
           (local.get $x)
+          (local.get $x)
+        )
+        (i32.add
+          (local.get $y)
+          (local.get $y)
         )
+        (local.get $z)
       )
     )
-    (drop
-      (i32.xor
-        (i32.xor
+  )
+  ;; CHECK:      (func $ternary-identical-arms-br_if-same (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (br_if $block
+  ;; CHECK-NEXT:    (if (result i32)
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-br_if-same (param $x i32) (param $y i32) (param $z i32)
+    (block $block
+      (if
+        (local.get $z)
+        ;; two br_ifs with the same target are shallowly identical
+        (br_if $block
           (local.get $x)
-          (local.tee $x
-            (i32.const 1)
-          )
         )
-        (local.get $x)
+        (br_if $block
+          (local.get $y)
+        )
       )
     )
   )
-
-  ;; i32.wrap_i64(i64.extend_i32_s(x))  ==>  x
-  ;; i32.wrap_i64(i64.extend_i32_u(x))  ==>  x
-
-  ;; CHECK:      (func $sign-and-zero-extention-elimination-1 (param $x i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK:      (func $ternary-identical-arms-br_if-different (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (block $block1
+  ;; CHECK-NEXT:   (block $block2
+  ;; CHECK-NEXT:    (if
+  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:     (br_if $block1
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (br_if $block2
+  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $sign-and-zero-extention-elimination-1 (param $x i32)
-    (drop (i32.wrap_i64 (i64.extend_i32_s (local.get $x))))
-    (drop (i32.wrap_i64 (i64.extend_i32_u (local.get $x))))
+  (func $ternary-identical-arms-br_if-different (param $x i32) (param $y i32) (param $z i32)
+    (block $block1
+      (block $block2
+        (if
+          (local.get $z)
+          ;; two br_ifs with different targets are not shallowly identical
+          (br_if $block1
+            (local.get $x)
+          )
+          (br_if $block2
+            (local.get $y)
+          )
+        )
+      )
+    )
   )
-  ;; i64.extend_i32_u(i32.wrap_i64(x))  =>  x,  where maxBits(x) <= 32
-  ;; i64.extend_i32_s(i32.wrap_i64(x))  =>  x,  where maxBits(x) <= 31
-
-  ;; CHECK:      (func $sign-and-zero-extention-elimination-2 (param $x i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i64.const 4294967295)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.and
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (i64.const 2147483647)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK:      (func $ternary-identical-arms-return (param $x i32) (param $y i32) (param $z i32) (result i32)
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (if (result i32)
+  ;; CHECK-NEXT:     (local.get $z)
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_s
-  ;; CHECK-NEXT:    (i32.wrap_i64
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-return (param $x i32) (param $y i32) (param $z i32) (result i32)
+    (block $block
+      (if
+        (local.get $z)
+        (return
+          (local.get $x)
+        )
+        (return
+          (local.get $y)
+        )
+      )
+    )
+  )
+  ;; CHECK:      (func $ternary-identical-arms-return-select (param $x i32) (param $y i32) (param $z i32) (result i32)
+  ;; CHECK-NEXT:  (block $block
+  ;; CHECK-NEXT:   (select
+  ;; CHECK-NEXT:    (return
   ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_s
-  ;; CHECK-NEXT:    (i32.wrap_i64
-  ;; CHECK-NEXT:     (i64.and
-  ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:      (i64.const 4294967295)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    (return
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (local.get $z)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $sign-and-zero-extention-elimination-2 (param $x i64)
-    (drop (i64.extend_i32_u (i32.wrap_i64 (i64.and (local.get $x) (i64.const 0x00000000FFFFFFFF)))))
-    (drop (i64.extend_i32_s (i32.wrap_i64 (i64.and (local.get $x) (i64.const 0x000000007FFFFFFF)))))
-
-    (drop (i64.extend_i32_u (i32.wrap_i64 (local.get $x)))) ;; skip
-    (drop (i64.extend_i32_s (i32.wrap_i64 (local.get $x)))) ;; skip
-    (drop (i64.extend_i32_s (i32.wrap_i64 (i64.and (local.get $x) (i64.const 0x00000000FFFFFFFF))))) ;; skip
+  (func $ternary-identical-arms-return-select (param $x i32) (param $y i32) (param $z i32) (result i32)
+    (block $block
+      ;; we cannot optimize a select currently as the return has side effects
+      (select
+        (return
+          (local.get $x)
+        )
+        (return
+          (local.get $y)
+        )
+        (local.get $z)
+      )
+    )
   )
-  ;; CHECK:      (func $optimize-shifts (param $x i32) (param $y i32) (param $z i64) (param $w i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $z)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $z)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $z)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $z)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $z)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shl
+  ;; CHECK:      (func $send-i32 (param $0 i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $send-i32 (param i32))
+  ;; CHECK:      (func $ternary-identical-arms-call (param $x i32) (param $y i32) (param $z i32)
+  ;; CHECK-NEXT:  (call $send-i32
+  ;; CHECK-NEXT:   (if (result i32)
+  ;; CHECK-NEXT:    (local.get $z)
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shl
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $ternary-identical-arms-call (param $x i32) (param $y i32) (param $z i32)
+    (if
+      (local.get $z)
+      (call $send-i32
+        (local.get $x)
+      )
+      (call $send-i32
+        (local.get $y)
+      )
+    )
+  )
+  ;; CHECK:      (func $if-dont-change-to-unreachable (param $x i32) (param $y i32) (param $z i32) (result i32)
+  ;; CHECK-NEXT:  (if (result i32)
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (local.get $y)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (return
+  ;; CHECK-NEXT:    (local.get $z)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $if-dont-change-to-unreachable (param $x i32) (param $y i32) (param $z i32) (result i32)
+    ;; if we move the returns outside, we'd become unreachable; avoid that.
+    (if (result i32)
+      (local.get $x)
+      (return
+        (local.get $y)
+      )
+      (return
+        (local.get $z)
+      )
+    )
+  )
+
+  ;; f32.reinterpret_i32(i32.load(x))  =>  f32.load(x)
+  ;; f64.reinterpret_i64(i64.load(x))  =>  f64.load(x)
+  ;; i32.reinterpret_f32(f32.load(x))  =>  i32.load(x)
+  ;; i64.reinterpret_f64(f64.load(x))  =>  i64.load(x)
+
+  ;; CHECK:      (func $simplify_reinterpret_and_load (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shr_s
+  ;; CHECK-NEXT:   (f32.load
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.shr_u
+  ;; CHECK-NEXT:   (f64.load
   ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shl
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shl
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (i64.load
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shr_s
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (f32.reinterpret_i32
+  ;; CHECK-NEXT:    (i32.load8_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shr_u
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (local.get $w)
+  ;; CHECK-NEXT:   (f64.reinterpret_i64
+  ;; CHECK-NEXT:    (i64.load32_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT: )
+  (func $simplify_reinterpret_and_load (param $x i32)
+    (drop (f32.reinterpret_i32 (i32.load (local.get $x))))
+    (drop (f64.reinterpret_i64 (i64.load (local.get $x))))
+    (drop (i32.reinterpret_f32 (f32.load (local.get $x))))
+    (drop (i64.reinterpret_f64 (f64.load (local.get $x))))
+    (drop (f32.reinterpret_i32 (i32.load8_s (local.get $x))))     ;; skip
+    (drop (f64.reinterpret_i64 (i64.load32_u (local.get $x))))    ;; skip
+  )
+
+  ;; f32.store(y, f32.reinterpret_i32(x))  =>  i32.store(y, x)
+  ;; f64.store(y, f64.reinterpret_i64(x))  =>  i64.store(y, x)
+  ;; i32.store(y, i32.reinterpret_f32(x))  =>  f32.store(y, x)
+  ;; i64.store(y, i64.reinterpret_f64(x))  =>  f64.store(y, x)
+
+  ;; CHECK:      (func $simplify_store_and_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 8)
   ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:  (i64.store
+  ;; CHECK-NEXT:   (i32.const 16)
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (f32.store
+  ;; CHECK-NEXT:   (i32.const 24)
   ;; CHECK-NEXT:   (local.get $z)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shl
+  ;; CHECK-NEXT:  (f64.store
+  ;; CHECK-NEXT:   (i32.const 32)
+  ;; CHECK-NEXT:   (local.get $w)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store8
+  ;; CHECK-NEXT:   (i32.const 40)
+  ;; CHECK-NEXT:   (i32.reinterpret_f32
   ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $w)
-  ;; CHECK-NEXT:     (i64.const 32)
-  ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shr_u
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (i64.and
-  ;; CHECK-NEXT:     (local.get $w)
-  ;; CHECK-NEXT:     (i64.const 31)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:  (i64.store32
+  ;; CHECK-NEXT:   (i32.const 44)
+  ;; CHECK-NEXT:   (i64.reinterpret_f64
+  ;; CHECK-NEXT:    (local.get $w)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $optimize-shifts (param $x i32) (param $y i32) (param $z i64) (param $w i64)
-    ;; i32
-    (drop (i32.shl
-      (local.get $x)
-      (i32.const 32)
-    ))
-    (drop (i32.shr_s
-      (local.get $x)
-      (i32.const 32)
-    ))
-    (drop (i32.shr_u
-      (local.get $x)
-      (i32.const 64)
-    ))
-    (drop (i32.rotl
-      (local.get $x)
-      (i32.const 64)
-    ))
-    (drop (i32.rotr
-      (local.get $x)
-      (i32.const 64)
-    ))
-    ;; i64
-    (drop (i64.shl
-      (local.get $z)
-      (i64.const 64)
-    ))
-    (drop (i64.shr_s
-      (local.get $z)
-      (i64.const 64)
-    ))
-    (drop (i64.shr_u
-      (local.get $z)
-      (i64.const 128)
-    ))
-    (drop (i64.rotl
-      (local.get $z)
-      (i64.const 128)
-    ))
-    (drop (i64.rotr
-      (local.get $z)
-      (i64.const 128)
-    ))
+  (func $simplify_store_and_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+    (f32.store (i32.const 8) (f32.reinterpret_i32 (local.get $x)))
+    (f64.store (i32.const 16) (f64.reinterpret_i64 (local.get $y)))
+    (i32.store (i32.const 24) (i32.reinterpret_f32 (local.get $z)))
+    (i64.store (i32.const 32) (i64.reinterpret_f64 (local.get $w)))
+    (i32.store8 (i32.const 40) (i32.reinterpret_f32 (local.get $z)))       ;; skip
+    (i64.store32 (i32.const 44) (i64.reinterpret_f64 (local.get $w)))      ;; skip
+  )
 
-    ;; i32
-    (drop (i32.shl
-      (local.get $x)
-      (i32.and
-        (local.get $y)
-        (i32.const 31)
-      )
-    ))
-    (drop (i32.shl
-      (local.get $x)
-      (i32.and
-        (local.get $y)
-        (i32.const 63)
-      )
-    ))
-    (drop (i32.shr_s
-      (local.get $x)
-      (i32.and
-        (local.get $y)
-        (i32.const 31)
-      )
-    ))
-    (drop (i32.shr_u
-      (local.get $x)
-      (i32.and
-        (local.get $y)
-        (i32.const 31)
-      )
-    ))
-    ;; i64
-    (drop (i64.shl
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 63)
-      )
-    ))
-    (drop (i64.shl
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 127)
-      )
-    ))
-    (drop (i64.shr_s
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 63)
-      )
-    ))
-    (drop (i64.shr_u
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 63)
-      )
-    ))
-    ;; i32(x) >> (y & 32)  ->  x
-    (drop (i32.shr_u
-      (local.get $x)
-      (i32.and
-        (local.get $y)
-        (i32.const 32)
-      )
-    ))
-    ;; i64(x) >> (y & 64)  ->  x
-    (drop (i64.shr_u
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 128)
-      )
-    ))
+  ;; i32.reinterpret_f32(f32.reinterpret_i32(x))  =>  x
+  ;; i64.reinterpret_f64(f64.reinterpret_i64(x))  =>  x
+  ;; f32.reinterpret_i32(i32.reinterpret_f32(x))  =>  x
+  ;; f64.reinterpret_i64(i64.reinterpret_f64(x))  =>  x
 
-    ;; skip
-    (drop (i64.shl
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 32)
-      )
-    ))
-    ;; skip
-    (drop (i64.shr_u
-      (local.get $z)
-      (i64.and
-        (local.get $w)
-        (i64.const 31)
-      )
-    ))
+  ;; CHECK:      (func $eliminate_reinterpret_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $w)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $eliminate_reinterpret_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+    (drop (i32.reinterpret_f32 (f32.reinterpret_i32 (local.get $x))))
+    (drop (i64.reinterpret_f64 (f64.reinterpret_i64 (local.get $y))))
+    (drop (f32.reinterpret_i32 (i32.reinterpret_f32 (local.get $z))))
+    (drop (f64.reinterpret_i64 (i64.reinterpret_f64 (local.get $w))))
   )
-  ;; CHECK:      (func $optimize-float-points (param $x0 f64) (param $x1 f64) (param $y0 f32) (param $y1 f32)
+
+  ;; CHECK:      (func $simplify_int_float_conversion_roundtrips (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x0)
-  ;; CHECK-NEXT:    (local.get $x0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $y0)
-  ;; CHECK-NEXT:    (local.get $y0)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (f64.add
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:   (i32.trunc_f64_u
+  ;; CHECK-NEXT:    (f64.convert_i32_s
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (f64.add
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.trunc_f64_s
+  ;; CHECK-NEXT:    (f64.convert_i32_u
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.mul
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (local.get $x1)
+  ;; CHECK-NEXT:   (i32.trunc_f32_u
+  ;; CHECK-NEXT:    (f32.convert_i32_u
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (f32.mul
-  ;; CHECK-NEXT:     (local.get $y1)
-  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:   (i32.trunc_f64_u
+  ;; CHECK-NEXT:    (f64.convert_i64_u
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.mul
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (f64.const 0)
+  ;; CHECK-NEXT:   (i64.trunc_f64_s
+  ;; CHECK-NEXT:    (f64.convert_i64_s
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (f32.mul
-  ;; CHECK-NEXT:     (f32.const 0)
-  ;; CHECK-NEXT:     (local.get $y0)
+  ;; CHECK-NEXT:   (i64.trunc_f32_u
+  ;; CHECK-NEXT:    (f32.convert_i32_s
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.mul
-  ;; CHECK-NEXT:     (f64.add
-  ;; CHECK-NEXT:      (local.get $x0)
-  ;; CHECK-NEXT:      (local.get $x1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (f64.add
-  ;; CHECK-NEXT:      (local.get $x0)
-  ;; CHECK-NEXT:      (local.get $x0)
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:   (i64.trunc_f32_s
+  ;; CHECK-NEXT:    (f32.convert_i64_s
+  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify_int_float_conversion_roundtrips (param $x i32) (param $y i64)
+    (drop (i32.trunc_f64_u (f64.convert_i32_u (local.get $x))))
+    (drop (i32.trunc_f64_s (f64.convert_i32_s (local.get $x))))
+
+    ;; skips
+    (drop (i32.trunc_f64_u (f64.convert_i32_s (local.get $x))))
+    (drop (i32.trunc_f64_s (f64.convert_i32_u (local.get $x))))
+    (drop (i32.trunc_f32_u (f32.convert_i32_u (local.get $x))))
+    (drop (i32.trunc_f64_u (f64.convert_i64_u (local.get $y))))
+    (drop (i64.trunc_f64_s (f64.convert_i64_s (local.get $y))))
+    (drop (i64.trunc_f32_u (f32.convert_i32_s (local.get $x))))
+    (drop (i64.trunc_f32_s (f32.convert_i64_s (local.get $y))))
+  )
+
+  ;; CHECK:      (func $simplify_rounding_after_conversions_i32_to_f64 (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   (f64.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   (f64.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.sub
-  ;; CHECK-NEXT:     (f64.const 0)
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (f32.sub
-  ;; CHECK-NEXT:     (f32.const 0)
-  ;; CHECK-NEXT:     (local.get $y0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x0)
-  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   (f64.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $y0)
-  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   (f64.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (f64.add
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (local.get $x1)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (f64.add
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (local.get $x1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.div
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (local.get $x1)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f64.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify_rounding_after_conversions_i32_to_f64 (param $x i32)
+    (drop (f64.ceil (f64.convert_i32_u (local.get $x))))
+    (drop (f64.ceil (f64.convert_i32_s (local.get $x))))
+
+    (drop (f64.floor (f64.convert_i32_u (local.get $x))))
+    (drop (f64.floor (f64.convert_i32_s (local.get $x))))
+
+    (drop (f64.trunc (f64.convert_i32_u (local.get $x))))
+    (drop (f64.trunc (f64.convert_i32_s (local.get $x))))
+
+    (drop (f64.nearest (f64.convert_i32_u (local.get $x))))
+    (drop (f64.nearest (f64.convert_i32_s (local.get $x))))
+  )
+
+  ;; CHECK:      (func $simplify_rounding_after_conversions_i32_to_f32 (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (f32.div
-  ;; CHECK-NEXT:     (local.get $y1)
-  ;; CHECK-NEXT:     (local.get $y0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.mul
-  ;; CHECK-NEXT:    (local.get $x0)
-  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   (f32.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.mul
-  ;; CHECK-NEXT:    (local.get $y0)
-  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   (f32.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.div
-  ;; CHECK-NEXT:    (local.get $x0)
-  ;; CHECK-NEXT:    (local.get $x0)
+  ;; CHECK-NEXT:   (f32.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.div
-  ;; CHECK-NEXT:    (local.get $y0)
-  ;; CHECK-NEXT:    (local.get $y0)
+  ;; CHECK-NEXT:   (f32.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.div
-  ;; CHECK-NEXT:     (local.get $x0)
-  ;; CHECK-NEXT:     (f64.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (f32.div
-  ;; CHECK-NEXT:     (f32.const 0)
-  ;; CHECK-NEXT:     (local.get $y0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i32_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.abs
-  ;; CHECK-NEXT:    (f64.div
-  ;; CHECK-NEXT:     (f64.add
-  ;; CHECK-NEXT:      (local.get $x0)
-  ;; CHECK-NEXT:      (local.get $x1)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (f64.add
-  ;; CHECK-NEXT:      (local.get $x0)
-  ;; CHECK-NEXT:      (local.get $x0)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i32_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $optimize-float-points (param $x0 f64) (param $x1 f64) (param $y0 f32) (param $y1 f32)
-    ;; abs(x) * abs(x)   ==>   x * x
-    (drop (f64.mul
-      (f64.abs (local.get $x0))
-      (f64.abs (local.get $x0))
-    ))
-    (drop (f32.mul
-      (f32.abs (local.get $y0))
-      (f32.abs (local.get $y0))
-    ))
-    (drop (f64.mul
-      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
-      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
-    ))
-
-    ;; abs(x) * abs(y)   ==>   abs(x * y)
-    (drop (f64.mul
-      (f64.abs (local.get $x0))
-      (f64.abs (local.get $x1))
-    ))
-    (drop (f32.mul
-      (f32.abs (local.get $y1))
-      (f32.abs (local.get $y0))
-    ))
-
-    (drop (f64.mul
-      (f64.abs (local.get $x0))
-      (f64.abs (f64.const 0)) ;; skip
-    ))
-    (drop (f32.mul
-      (f32.abs (f32.const 0)) ;; skip
-      (f32.abs (local.get $y0))
-    ))
-    (drop (f64.mul
-      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
-      (f64.abs (f64.add (local.get $x0) (local.get $x0)))
-    ))
-
+  (func $simplify_rounding_after_conversions_i32_to_f32 (param $x i32)
+    (drop (f32.ceil (f32.convert_i32_u (local.get $x))))
+    (drop (f32.ceil (f32.convert_i32_s (local.get $x))))
 
-    ;; abs(-x)   ==>   abs(x)
-    (drop (f64.abs
-      (f64.neg (local.get $x0))
-    ))
-    (drop (f32.abs
-      (f32.neg (local.get $y0))
-    ))
+    (drop (f32.floor (f32.convert_i32_u (local.get $x))))
+    (drop (f32.floor (f32.convert_i32_s (local.get $x))))
 
-    ;; abs(0 - x)   ==>   skip for non-fast math
-    (drop (f64.abs
-      (f64.sub
-        (f64.const 0)
-        (local.get $x0)
-      )
-    ))
-    (drop (f32.abs
-      (f32.sub
-        (f32.const 0)
-        (local.get $y0)
-      )
-    ))
+    (drop (f32.trunc (f32.convert_i32_u (local.get $x))))
+    (drop (f32.trunc (f32.convert_i32_s (local.get $x))))
 
-    ;; abs(x) / abs(x)   ==>   x / x
-    (drop (f64.div
-      (f64.abs (local.get $x0))
-      (f64.abs (local.get $x0))
-    ))
-    (drop (f32.div
-      (f32.abs (local.get $y0))
-      (f32.abs (local.get $y0))
-    ))
-    (drop (f64.div
-      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
-      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
-    ))
+    (drop (f32.nearest (f32.convert_i32_u (local.get $x))))
+    (drop (f32.nearest (f32.convert_i32_s (local.get $x))))
+  )
 
-    ;; abs(x) / abs(y)   ==>   abs(x / y)
-    (drop (f64.div
-      (f64.abs (local.get $x0))
-      (f64.abs (local.get $x1))
-    ))
-    (drop (f32.div
-      (f32.abs (local.get $y1))
-      (f32.abs (local.get $y0))
-    ))
+  ;; CHECK:      (func $simplify_rounding_after_conversions_i64_to_f64 (param $x i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f64.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify_rounding_after_conversions_i64_to_f64 (param $x i64)
+    (drop (f64.ceil (f64.convert_i64_u (local.get $x))))
+    (drop (f64.ceil (f64.convert_i64_s (local.get $x))))
 
-    ;; abs(x * x)   ==>   x * x
-    (drop (f64.abs
-      (f64.mul
-        (local.get $x0)
-        (local.get $x0)
-      )
-    ))
-    (drop (f32.abs
-      (f32.mul
-        (local.get $y0)
-        (local.get $y0)
-      )
-    ))
+    (drop (f64.floor (f64.convert_i64_u (local.get $x))))
+    (drop (f64.floor (f64.convert_i64_s (local.get $x))))
 
-    ;; abs(x / x)   ==>   x / x
-    (drop (f64.abs
-      (f64.div
-        (local.get $x0)
-        (local.get $x0)
-      )
-    ))
-    (drop (f32.abs
-      (f32.div
-        (local.get $y0)
-        (local.get $y0)
-      )
-    ))
+    (drop (f64.trunc (f64.convert_i64_u (local.get $x))))
+    (drop (f64.trunc (f64.convert_i64_s (local.get $x))))
 
-    (drop (f64.div
-      (f64.abs (local.get $x0))
-      (f64.abs (f64.const 0)) ;; skip
-    ))
-    (drop (f32.div
-      (f32.abs (f32.const 0)) ;; skip
-      (f32.abs (local.get $y0))
-    ))
-    (drop (f64.div
-      (f64.abs (f64.add (local.get $x0) (local.get $x1)))
-      (f64.abs (f64.add (local.get $x0) (local.get $x0)))
-    ))
+    (drop (f64.nearest (f64.convert_i64_u (local.get $x))))
+    (drop (f64.nearest (f64.convert_i64_s (local.get $x))))
   )
-  ;; CHECK:      (func $ternary (param $x i32) (param $y i32)
+
+  ;; CHECK:      (func $simplify_rounding_after_conversions_i64_to_f32 (param $x i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (select
-  ;; CHECK-NEXT:     (i32.const 1)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (select
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (select
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i32.const 1)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (select
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (if (result i32)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (f32.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.convert_i64_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (f32.convert_i64_s
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary (param $x i32) (param $y i32)
-    (drop
-      (select
-        (i32.const 0)
-        (i32.eqz
-          (local.get $y)
-        )
-        (local.get $x)
-      )
-    )
-    (drop
-      (select
-        (i32.const 1)
-        (i32.eqz
-          (local.get $y)
-        )
-        (local.get $x)
-      )
-    )
-    (drop
-      (select
-        (i32.eqz
-          (local.get $y)
-        )
-        (i32.const 0)
-        (local.get $x)
-      )
-    )
-    (drop
-      (select
-        (i32.eqz
-          (local.get $y)
-        )
-        (i32.const 1)
-        (local.get $x)
-      )
-    )
-    ;; if works too
-    (drop
-      (if (result i32)
-        (local.get $x)
-        (i32.eqz
-          (local.get $y)
-        )
-        (i32.const 1)
-      )
-    )
+  (func $simplify_rounding_after_conversions_i64_to_f32 (param $x i64)
+    (drop (f32.ceil (f32.convert_i64_u (local.get $x))))
+    (drop (f32.ceil (f32.convert_i64_s (local.get $x))))
+
+    (drop (f32.floor (f32.convert_i64_u (local.get $x))))
+    (drop (f32.floor (f32.convert_i64_s (local.get $x))))
+
+    (drop (f32.trunc (f32.convert_i64_u (local.get $x))))
+    (drop (f32.trunc (f32.convert_i64_s (local.get $x))))
+
+    (drop (f32.nearest (f32.convert_i64_u (local.get $x))))
+    (drop (f32.nearest (f32.convert_i64_s (local.get $x))))
   )
-  ;; CHECK:      (func $ternary-i64-0 (param $x i32) (param $y i64)
+
+  ;; u64(i32.load(_8|_16)(_u|_s)(x))  =>  i64.load(_8|_16|_32)(_u|_s)(x)
+  ;; except:
+  ;;   i64.extend_i32_u(i32.load8_s(x)) and
+  ;;   i64.extend_i32_u(i32.load16_s(x))
+
+  ;; CHECK:      (func $combine_load_and_extend_u (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (if (result i64)
+  ;; CHECK-NEXT:   (i64.load8_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.load16_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.load32_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i32.load8_s
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i64.const 1)
-  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $ternary-i64-0 (param $x i32) (param $y i64)
-    (drop
-      (if (result i32)
-        (local.get $x)
-        (i32.const 0)
-        (i64.eqz
-          (local.get $y)
-        )
-      )
-    )
-  )
-  ;; CHECK:      (func $ternary-i64-1 (param $x i32) (param $y i64)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.eqz
-  ;; CHECK-NEXT:    (if (result i64)
+  ;; CHECK-NEXT:   (i64.extend_i32_u
+  ;; CHECK-NEXT:    (i32.load16_s
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (i64.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-i64-1 (param $x i32) (param $y i64)
-    (drop
-      (if (result i32)
-        (local.get $x)
-        (i64.eqz
-          (local.get $y)
-        )
-        (i32.const 1)
-      )
-    )
+  (func $combine_load_and_extend_u (param $x i32)
+    (drop (i64.extend_i32_u (i32.load8_u (local.get $x))))
+    (drop (i64.extend_i32_u (i32.load16_u (local.get $x))))
+    (drop (i64.extend_i32_u (i32.load (local.get $x))))
+
+    ;; skips
+    (drop (i64.extend_i32_u (i32.load8_s (local.get $x))))
+    (drop (i64.extend_i32_u (i32.load16_s (local.get $x))))
   )
-  ;; CHECK:      (func $ternary-no (param $x i32) (param $y i32)
+
+  ;; i64(i32.load(_8|_16)(_u|_s)(x))  =>  i64.load(_8|_16|_32)(_u|_s)(x)
+
+  ;; CHECK:      (func $combine_load_and_extend_s (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.load8_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.load16_u
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.load8_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.load16_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.const 2)
-  ;; CHECK-NEXT:    (i32.eqz
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   (i64.load32_s
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-no (param $x i32) (param $y i32)
-    (drop
-      (select
-        (i32.const 2) ;; only 0 and 1 work
-        (i32.eqz
-          (local.get $y)
+  (func $combine_load_and_extend_s (param $x i32)
+    (drop (i64.extend_i32_s (i32.load8_u (local.get $x))))
+    (drop (i64.extend_i32_s (i32.load16_u (local.get $x))))
+    (drop (i64.extend_i32_s (i32.load8_s (local.get $x))))
+    (drop (i64.extend_i32_s (i32.load16_s (local.get $x))))
+    (drop (i64.extend_i32_s (i32.load (local.get $x))))
+  )
+
+  ;; CHECK:      (func $wrap-i64-to-i32-add (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.add
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.const 8)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $wrap-i64-to-i32-add (param $x i32) (result i32)
+    ;; Rather than extend to 64 and add there, we can do all of this in i32.
+    (i32.wrap_i64
+      (i64.add
+        (i64.extend_i32_u
+          (local.get $x)
         )
-        (local.get $x)
+        (i64.const 8)
       )
     )
   )
-  ;; CHECK:      (func $ternary-no-unreachable-1 (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (if (result i32)
+
+  ;; CHECK:      (func $wrap-i64-to-i32-sub (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.sub
+  ;; CHECK-NEXT:   (i32.const 8)
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-no-unreachable-1 (param $x i32) (result i32)
-    (if (result i32)
-      (local.get $x)
-      ;; one arm is an eqz, the other is 0 or 1, so we can put an eqz on the
-      ;; outside in theory, but we'd need to be careful with the unreachable
-      ;; type here. ignore this case, as DCE is the proper optimization anyhow.
-      (i32.eqz
-        (unreachable)
+  (func $wrap-i64-to-i32-sub (param $x i32) (result i32)
+    (i32.wrap_i64
+      (i64.sub
+        (i64.const 8)
+        (i64.extend_i32_u
+          (local.get $x)
+        )
       )
-      (i32.const 0)
     )
   )
-  ;; CHECK:      (func $ternary-no-unreachable-2 (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (if (result i32)
+
+  ;; CHECK:      (func $wrap-i64-to-i32-mul (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (unreachable)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 42)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-no-unreachable-2 (param $x i32) (result i32)
-    (if (result i32)
-      (local.get $x)
-      ;; as before, but flipped
-      (i32.const 0)
-      (i32.eqz
-        (unreachable)
+  (func $wrap-i64-to-i32-mul (param $x i32) (result i32)
+    (i32.wrap_i64
+      (i64.mul
+        (i64.extend_i32_u
+          (local.get $x)
+        )
+        (i64.const 42)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms (param $x i32) (param $y i32) (param $z i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (select
-  ;; CHECK-NEXT:     (local.get $x)
+
+  ;; CHECK:      (func $wrap-i64-to-i32-many (param $x i32) (param $y i32) (result i32)
+  ;; CHECK-NEXT:  (i32.mul
+  ;; CHECK-NEXT:   (i32.add
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.const -1)
   ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (local.get $z)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 42)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms (param $x i32) (param $y i32) (param $z i32)
-    (drop
-      (select
-        (i32.eqz (local.get $x))
-        (i32.eqz (local.get $y))
-        (local.get $z)
+  (func $wrap-i64-to-i32-many (param $x i32) (param $y i32) (result i32)
+    ;; Multiple operations that all together can be turned into i32.
+    (i32.wrap_i64
+      (i64.mul
+        (i64.add
+          (i64.extend_i32_u
+            (local.get $x)
+          )
+          (i64.sub
+            (i64.const -1)
+            (i64.extend_i32_u
+              (local.get $y)
+            )
+          )
+        )
+        (i64.const 42)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-if (param $x i32) (param $y i32) (param $z i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (if (result i32)
-  ;; CHECK-NEXT:     (local.get $z)
+
+  ;; CHECK:      (func $wrap-i64-to-i32-div-no (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.wrap_i64
+  ;; CHECK-NEXT:   (i64.div_u
+  ;; CHECK-NEXT:    (i64.extend_i32_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 42)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-if (param $x i32) (param $y i32) (param $z i32)
-    (drop
-      (if (result i32)
-        (local.get $z)
-        (i32.eqz (local.get $x))
-        (i32.eqz (local.get $y))
+  (func $wrap-i64-to-i32-div-no (param $x i32) (result i32)
+    ;; We *cannot* optimize here, as division cares about i32/i64 differences.
+    (i32.wrap_i64
+      (i64.div_s
+        (i64.extend_i32_u
+          (local.get $x)
+        )
+        (i64.const 42)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.demote_f64
-  ;; CHECK-NEXT:    (f64.floor
-  ;; CHECK-NEXT:     (if (result f64)
-  ;; CHECK-NEXT:      (local.get $z)
-  ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:      (local.get $y)
-  ;; CHECK-NEXT:     )
+
+  ;; CHECK:      (func $wrap-i64-to-i32-tee-no (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (local $y i64)
+  ;; CHECK-NEXT:  (i32.wrap_i64
+  ;; CHECK-NEXT:   (i64.add
+  ;; CHECK-NEXT:    (local.tee $y
+  ;; CHECK-NEXT:     (i64.const 42)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.extend_i32_u
+  ;; CHECK-NEXT:     (local.get $x)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32)
-    (drop
-      ;; the if's type begins as f32, but after moving code out it will be
-      ;; f64
-      (if (result f32)
-        (local.get $z)
-        (f32.demote_f64 (f64.floor (local.get $x)))
-        (f32.demote_f64 (f64.floor (local.get $y)))
+  (func $wrap-i64-to-i32-tee-no (param $x i32) (result i32)
+    ;; The local.tee stops us from optimizing atm. TODO
+    (local $y i64)
+    (i32.wrap_i64
+      (i64.add
+        (i64.extend_i32_u
+          (local.get $x)
+        )
+        (local.tee $y
+          (i64.const 42)
+        )
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-more (param $x f32) (param $y f32) (param $z i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.floor
-  ;; CHECK-NEXT:    (f32.neg
-  ;; CHECK-NEXT:     (select
-  ;; CHECK-NEXT:      (local.get $x)
-  ;; CHECK-NEXT:      (local.get $y)
-  ;; CHECK-NEXT:      (local.get $z)
-  ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:    )
+
+  ;; CHECK:      (func $wrap-i64-to-i32-local-no (param $x i64) (result i32)
+  ;; CHECK-NEXT:  (i32.wrap_i64
+  ;; CHECK-NEXT:   (i64.div_s
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i64.const 42)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-more (param $x f32) (param $y f32) (param $z i32)
-    (drop
-      (select
-        (f32.floor (f32.neg (local.get $x)))
-        (f32.floor (f32.neg (local.get $y)))
-        (local.get $z)
+  (func $wrap-i64-to-i32-local-no (param $x i64) (result i32)
+    ;; We do not optimize here for now as an input ($x) is an i64. TODO
+    (i32.wrap_i64
+      (i64.div_s
+        (local.get $x)
+        (i64.const 42)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-morer (param $x f32) (param $y f32) (param $z i32)
+
+  ;; CHECK:      (func $gt_u-added-constant (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.abs
-  ;; CHECK-NEXT:    (f32.floor
-  ;; CHECK-NEXT:     (f32.neg
-  ;; CHECK-NEXT:      (select
-  ;; CHECK-NEXT:       (local.get $x)
-  ;; CHECK-NEXT:       (local.get $y)
-  ;; CHECK-NEXT:       (local.get $z)
-  ;; CHECK-NEXT:      )
-  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:   (i32.gt_u
+  ;; CHECK-NEXT:    (i32.shr_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-morer (param $x f32) (param $y f32) (param $z i32)
-    (drop
-      (select
-        (f32.abs (f32.floor (f32.neg (local.get $x))))
-        (f32.abs (f32.floor (f32.neg (local.get $y))))
-        (local.get $z)
-      )
-    )
-  )
-  ;; CHECK:      (func $ternary-identical-arms-and-type-is-none (param $x i32) (param $y i32) (param $z i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.eqz
-  ;; CHECK-NEXT:    (if (result i32)
-  ;; CHECK-NEXT:     (local.get $z)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (i32.shr_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-and-type-is-none (param $x i32) (param $y i32) (param $z i32)
-    (if
-      (local.get $z)
-      (drop (i32.eqz (local.get $x)))
-      (drop (i32.eqz (local.get $y)))
-    )
-  )
-  ;; CHECK:      (func $ternary-identical-arms-and-type-is-none-child-types-mismatch (param $x i32) (param $y i32) (param $z i32)
-  ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (local.get $z)
-  ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (f64.const 2.34)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-and-type-is-none-child-types-mismatch (param $x i32) (param $y i32) (param $z i32)
-    (if
-      (local.get $z)
-      ;; the drop cannot be hoisted out, since the children's type mismatch
-      ;; would not allow us to give a proper type to the if.
-      (drop (i32.const 1))
-      (drop (f64.const 2.34))
+  (func $gt_u-added-constant (param $x i32)
+    ;; x + C1 > C2  =>  x > (C2-C1), iff x+C1 and C2-C1 don't over/underflow
+    (drop
+      (i32.gt_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
+        )
+        (i32.const 11)
+      )
+    )
+    ;; We can optimize even if the constants are equal.
+    (drop
+      (i32.gt_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
+        )
+        (i32.const 5)
+      )
+    )
+    ;; x + C1 > C2  =>  x + (C1-C2) > 0, iff x+C1 and C1-C2 don't over/underflow
+    ;; After doing that, further optimizations are possible here.
+    (drop
+      (i32.gt_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 6)
+        )
+        (i32.const 5)
+      )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-but-block (param $x i32) (param $y i32) (param $z i32)
+
+  ;; CHECK:      (func $gt_u-added-constant-no (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (block $block (result i32)
-  ;; CHECK-NEXT:     (i32.eqz
+  ;; CHECK-NEXT:   (i32.gt_u
+  ;; CHECK-NEXT:    (i32.add
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 5)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 11)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.gt_u
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.shr_u
   ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (i32.const 1)
   ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const -2147483648)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (block $block24 (result i32)
-  ;; CHECK-NEXT:     (i32.eqz
-  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:    (i32.const 11)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.gt_u
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.shr_u
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (i32.const 1)
   ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (i32.const 11)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-but-block (param $x i32) (param $y i32) (param $z i32)
+  (func $gt_u-added-constant-no (param $x i32)
+    ;; As above, but without the shr_u, A is big enough for a possible overflow,
+    ;; and we cannot optimize.
     (drop
-      (select
-        ;; identical arms, but they are control flow structures
-        (block (result i32)
-          (i32.eqz (local.get $x))
+      (i32.gt_u
+        (i32.add
+          (local.get $x)
+          (i32.const 5)
         )
-        (block (result i32)
-          (i32.eqz (local.get $y))
+        (i32.const 11)
+      )
+    )
+    ;; With the added constant too big, it might overflow, and we cannot
+    ;; optimize.
+    (drop
+      (i32.gt_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 0x80000000)
         )
-        (local.get $z)
+        (i32.const 11)
+      )
+    )
+    (drop
+      (i32.gt_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 0xffffffff)
+        )
+        (i32.const 11)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-but-binary (param $x i32) (param $y i32) (param $z i32)
+
+  ;; CHECK:      (func $ge_u-added-constant (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (i32.add
-  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.shr_u
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i32.add
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $z)
+  ;; CHECK-NEXT:    (i32.const 6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-but-binary (param $x i32) (param $y i32) (param $z i32)
+  (func $ge_u-added-constant (param $x i32)
+    ;; As above, but with ge rather than gt. We can optimize here.
     (drop
-      (select
-        ;; identical arms, but they are binaries, not unaries
+      (i32.ge_u
         (i32.add
-          (local.get $x)
-          (local.get $x)
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
+        )
+        (i32.const 11)
+      )
+    )
+    (drop
+      (i32.ge_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
         )
+        (i32.const 5)
+      )
+    )
+    (drop
+      (i32.ge_u
         (i32.add
-          (local.get $y)
-          (local.get $y)
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 6)
         )
-        (local.get $z)
+        (i32.const 5)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-br_if-same (param $x i32) (param $y i32) (param $z i32)
-  ;; CHECK-NEXT:  (block $block
-  ;; CHECK-NEXT:   (br_if $block
-  ;; CHECK-NEXT:    (if (result i32)
-  ;; CHECK-NEXT:     (local.get $z)
+
+  ;; CHECK:      (func $ge_u-added-constant-no (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.add
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 5)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 11)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-br_if-same (param $x i32) (param $y i32) (param $z i32)
-    (block $block
-      (if
-        (local.get $z)
-        ;; two br_ifs with the same target are shallowly identical
-        (br_if $block
-          (local.get $x)
-        )
-        (br_if $block
-          (local.get $y)
-        )
-      )
-    )
-  )
-  ;; CHECK:      (func $ternary-identical-arms-br_if-different (param $x i32) (param $y i32) (param $z i32)
-  ;; CHECK-NEXT:  (block $block1
-  ;; CHECK-NEXT:   (block $block2
-  ;; CHECK-NEXT:    (if
-  ;; CHECK-NEXT:     (local.get $z)
-  ;; CHECK-NEXT:     (br_if $block1
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.shr_u
   ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (i32.const 1)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (br_if $block2
-  ;; CHECK-NEXT:      (local.get $y)
+  ;; CHECK-NEXT:     (i32.const -2147483648)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 11)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.sub
+  ;; CHECK-NEXT:     (i32.shr_u
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (i32.const 1)
   ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 11)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-br_if-different (param $x i32) (param $y i32) (param $z i32)
-    (block $block1
-      (block $block2
-        (if
-          (local.get $z)
-          ;; two br_ifs with different targets are not shallowly identical
-          (br_if $block1
+  (func $ge_u-added-constant-no (param $x i32)
+    ;; As above, but with ge rather than gt. We cannot optimize here.
+    (drop
+      (i32.ge_u
+        (i32.add
+          (local.get $x)
+          (i32.const 5)
+        )
+        (i32.const 11)
+      )
+    )
+    (drop
+      (i32.ge_u
+        (i32.add
+          (i32.shr_u
             (local.get $x)
+            (i32.const 1)
           )
-          (br_if $block2
-            (local.get $y)
-          )
+          (i32.const 0x80000000)
         )
+        (i32.const 11)
       )
     )
-  )
-  ;; CHECK:      (func $ternary-identical-arms-return (param $x i32) (param $y i32) (param $z i32) (result i32)
-  ;; CHECK-NEXT:  (block $block
-  ;; CHECK-NEXT:   (return
-  ;; CHECK-NEXT:    (if (result i32)
-  ;; CHECK-NEXT:     (local.get $z)
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-return (param $x i32) (param $y i32) (param $z i32) (result i32)
-    (block $block
-      (if
-        (local.get $z)
-        (return
-          (local.get $x)
-        )
-        (return
-          (local.get $y)
+    (drop
+      (i32.ge_u
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 0xffffffff)
         )
+        (i32.const 11)
       )
     )
   )
-  ;; CHECK:      (func $ternary-identical-arms-return-select (param $x i32) (param $y i32) (param $z i32) (result i32)
-  ;; CHECK-NEXT:  (block $block
-  ;; CHECK-NEXT:   (select
-  ;; CHECK-NEXT:    (return
+
+  ;; CHECK:      (func $eq-added-constant (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.shr_u
   ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (return
-  ;; CHECK-NEXT:     (local.get $y)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-return-select (param $x i32) (param $y i32) (param $z i32) (result i32)
-    (block $block
-      ;; we cannot optimize a select currently as the return has side effects
-      (select
-        (return
-          (local.get $x)
-        )
-        (return
-          (local.get $y)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 6)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $eq-added-constant (param $x i32)
+    ;; As above, but with eq rather than gt. We can optimize here.
+    (drop
+      (i32.eq
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
         )
-        (local.get $z)
+        (i32.const 11)
       )
     )
   )
-  ;; CHECK:      (func $send-i32 (param $0 i32)
-  ;; CHECK-NEXT:  (nop)
-  ;; CHECK-NEXT: )
-  (func $send-i32 (param i32))
-  ;; CHECK:      (func $ternary-identical-arms-call (param $x i32) (param $y i32) (param $z i32)
-  ;; CHECK-NEXT:  (call $send-i32
-  ;; CHECK-NEXT:   (if (result i32)
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:    (local.get $y)
+
+  ;; CHECK:      (func $ne-added-constant (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (i32.shr_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $ternary-identical-arms-call (param $x i32) (param $y i32) (param $z i32)
-    (if
-      (local.get $z)
-      (call $send-i32
-        (local.get $x)
-      )
-      (call $send-i32
-        (local.get $y)
+  (func $ne-added-constant (param $x i32)
+    ;; As above, but with ne rather than gt. We can optimize here.
+    (drop
+      (i32.ne
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
+        )
+        (i32.const 11)
       )
     )
   )
-  ;; CHECK:      (func $if-dont-change-to-unreachable (param $x i32) (param $y i32) (param $z i32) (result i32)
-  ;; CHECK-NEXT:  (if (result i32)
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (return
-  ;; CHECK-NEXT:    (local.get $y)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (return
-  ;; CHECK-NEXT:    (local.get $z)
+
+  ;; CHECK:      (func $lt-added-constant (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.lt_u
+  ;; CHECK-NEXT:    (i32.shr_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 6)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $if-dont-change-to-unreachable (param $x i32) (param $y i32) (param $z i32) (result i32)
-    ;; if we move the returns outside, we'd become unreachable; avoid that.
-    (if (result i32)
-      (local.get $x)
-      (return
-        (local.get $y)
-      )
-      (return
-        (local.get $z)
+  (func $lt-added-constant (param $x i32)
+    ;; As above, but with lt_s rather than gt_u. We can optimize here.
+    (drop
+      (i32.lt_s
+        (i32.add
+          (i32.shr_u
+            (local.get $x)
+            (i32.const 1)
+          )
+          (i32.const 5)
+        )
+        (i32.const 11)
       )
     )
   )
 
-  ;; f32.reinterpret_i32(i32.load(x))  =>  f32.load(x)
-  ;; f64.reinterpret_i64(i64.load(x))  =>  f64.load(x)
-  ;; i32.reinterpret_f32(f32.load(x))  =>  i32.load(x)
-  ;; i64.reinterpret_f64(f64.load(x))  =>  i64.load(x)
-
-  ;; CHECK:      (func $simplify_reinterpret_and_load (param $x i32)
+  ;; CHECK:      (func $too-few-bits (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.load
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.load
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.load
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f32.reinterpret_i32
-  ;; CHECK-NEXT:    (i32.load8_s
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (f64.reinterpret_i64
-  ;; CHECK-NEXT:    (i64.load32_u
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $simplify_reinterpret_and_load (param $x i32)
-    (drop (f32.reinterpret_i32 (i32.load (local.get $x))))
-    (drop (f64.reinterpret_i64 (i64.load (local.get $x))))
-    (drop (i32.reinterpret_f32 (f32.load (local.get $x))))
-    (drop (i64.reinterpret_f64 (f64.load (local.get $x))))
-    (drop (f32.reinterpret_i32 (i32.load8_s (local.get $x))))     ;; skip
-    (drop (f64.reinterpret_i64 (i64.load32_u (local.get $x))))    ;; skip
-  )
-
-  ;; f32.store(y, f32.reinterpret_i32(x))  =>  i32.store(y, x)
-  ;; f64.store(y, f64.reinterpret_i64(x))  =>  i64.store(y, x)
-  ;; i32.store(y, i32.reinterpret_f32(x))  =>  f32.store(y, x)
-  ;; i64.store(y, i64.reinterpret_f64(x))  =>  f64.store(y, x)
-
-  ;; CHECK:      (func $simplify_store_and_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
-  ;; CHECK-NEXT:  (i32.store
-  ;; CHECK-NEXT:   (i32.const 8)
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (i64.store
-  ;; CHECK-NEXT:   (i32.const 16)
-  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (f32.store
-  ;; CHECK-NEXT:   (i32.const 24)
-  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (f64.store
-  ;; CHECK-NEXT:   (i32.const 32)
-  ;; CHECK-NEXT:   (local.get $w)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (i32.store8
-  ;; CHECK-NEXT:   (i32.const 40)
-  ;; CHECK-NEXT:   (i32.reinterpret_f32
-  ;; CHECK-NEXT:    (local.get $z)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (i64.store32
-  ;; CHECK-NEXT:   (i32.const 44)
-  ;; CHECK-NEXT:   (i64.reinterpret_f64
-  ;; CHECK-NEXT:    (local.get $w)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $simplify_store_and_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
-    (f32.store (i32.const 8) (f32.reinterpret_i32 (local.get $x)))
-    (f64.store (i32.const 16) (f64.reinterpret_i64 (local.get $y)))
-    (i32.store (i32.const 24) (i32.reinterpret_f32 (local.get $z)))
-    (i64.store (i32.const 32) (i64.reinterpret_f64 (local.get $w)))
-    (i32.store8 (i32.const 40) (i32.reinterpret_f32 (local.get $z)))       ;; skip
-    (i64.store32 (i32.const 44) (i64.reinterpret_f64 (local.get $w)))      ;; skip
+  (func $too-few-bits (param $x i32)
+    ;; Comparison of something with at most 8 bits to something with more than
+    ;; 8. These must all be false.
+    (drop
+      (i32.eq
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.gt_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.gt_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.ge_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.ge_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    ;; These are all true.
+    (drop
+      (i32.ne
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.lt_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.lt_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.le_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
+    (drop
+      (i32.le_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 256)
+      )
+    )
   )
 
-  ;; i32.reinterpret_f32(f32.reinterpret_i32(x))  =>  x
-  ;; i64.reinterpret_f64(f64.reinterpret_i64(x))  =>  x
-  ;; f32.reinterpret_i32(i32.reinterpret_f32(x))  =>  x
-  ;; f64.reinterpret_i64(i64.reinterpret_f64(x))  =>  x
-
-  ;; CHECK:      (func $eliminate_reinterpret_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+  ;; CHECK:      (func $too-few-bits-no (param $x i32)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eq
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (i32.ne
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $y)
+  ;; CHECK-NEXT:   (i32.lt_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $z)
+  ;; CHECK-NEXT:   (i32.lt_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (local.get $w)
+  ;; CHECK-NEXT:   (i32.le_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $eliminate_reinterpret_reinterpret (param $x i32) (param $y i64) (param $z f32) (param $w f64)
-    (drop (i32.reinterpret_f32 (f32.reinterpret_i32 (local.get $x))))
-    (drop (i64.reinterpret_f64 (f64.reinterpret_i64 (local.get $y))))
-    (drop (f32.reinterpret_i32 (i32.reinterpret_f32 (local.get $z))))
-    (drop (f64.reinterpret_i64 (i64.reinterpret_f64 (local.get $w))))
-  )
-
-  ;; u64(i32.load(_8|_16)(_u|_s)(x))  =>  i64.load(_8|_16|_32)(_u|_s)(x)
-  ;; except:
-  ;;   i64.extend_i32_u(i32.load8_s(x)) and
-  ;;   i64.extend_i32_u(i32.load16_s(x))
-
-  ;; CHECK:      (func $combine_load_and_extend_u (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load8_u
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (i32.le_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load16_u
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (i32.gt_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load32_u
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (i32.gt_u
+  ;; CHECK-NEXT:    (i32.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i32.load8_s
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.and
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend_i32_u
-  ;; CHECK-NEXT:    (i32.load16_s
+  ;; CHECK-NEXT:   (i32.ge_u
+  ;; CHECK-NEXT:    (i32.and
   ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i32.const 255)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $combine_load_and_extend_u (param $x i32)
-    (drop (i64.extend_i32_u (i32.load8_u (local.get $x))))
-    (drop (i64.extend_i32_u (i32.load16_u (local.get $x))))
-    (drop (i64.extend_i32_u (i32.load (local.get $x))))
-
-    ;; skips
-    (drop (i64.extend_i32_u (i32.load8_s (local.get $x))))
-    (drop (i64.extend_i32_u (i32.load16_s (local.get $x))))
+  (func $too-few-bits-no (param $x i32)
+    ;; Identical to the above, but the constant is changed from 256 to 255. We
+    ;; cannot optimize here: the number of bits is the same and we can't infer
+    ;; anything.
+    (drop
+      (i32.eq
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.ne
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.lt_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.lt_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.le_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.le_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.gt_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.gt_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.ge_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
+    (drop
+      (i32.ge_u
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 255)
+      )
+    )
   )
 
-  ;; i64(i32.load(_8|_16)(_u|_s)(x))  =>  i64.load(_8|_16|_32)(_u|_s)(x)
-
-  ;; CHECK:      (func $combine_load_and_extend_s (param $x i32)
+  ;; CHECK:      (func $too-few-bits-signed (param $x i32)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load8_u
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load16_u
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load8_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load16_s
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.ne
   ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:    (i32.const -2147483648)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $too-few-bits-signed (param $x i32)
+    ;; As above, but now using only signed operations and the constant on the
+    ;; right has the sign bit set. The left side is non-negative, which lets us
+    ;; infer the results here.
+    ;; These are all false:
+    (drop
+      (i32.lt_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 0x80000000) ;; -2147483648
+      )
+    )
+    (drop
+      (i32.le_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 0x80000000) ;; -2147483648
+      )
+    )
+    ;; These are all true:
+    (drop
+      (i32.gt_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 0x80000000) ;; -2147483648
+      )
+    )
+    (drop
+      (i32.ge_s
+        (i32.and
+          (local.get $x)
+          (i32.const 255)
+        )
+        (i32.const 0x80000000) ;; -2147483648
+      )
+    )
+    ;; This cannot be inferred, as the left has too many possible bits (so it
+    ;; may have the sign bit set).
+    (drop
+      (i32.gt_s
+        (local.get $x)
+        (i32.const 0x80000000) ;; -2147483648
+      )
+    )
+  )
+
+  ;; CHECK:      (func $too-few-bits-i64 (param $x i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.const 0)
+  ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.load32_s
-  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   (i64.eq
+  ;; CHECK-NEXT:    (i64.and
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:     (i64.const 255)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i64.const 255)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $combine_load_and_extend_s (param $x i32)
-    (drop (i64.extend_i32_s (i32.load8_u (local.get $x))))
-    (drop (i64.extend_i32_s (i32.load16_u (local.get $x))))
-    (drop (i64.extend_i32_s (i32.load8_s (local.get $x))))
-    (drop (i64.extend_i32_s (i32.load16_s (local.get $x))))
-    (drop (i64.extend_i32_s (i32.load (local.get $x))))
+  (func $too-few-bits-i64 (param $x i64)
+    ;; As above, but with i64 values. We can infer 0 here.
+    (drop
+      (i64.eq
+        (i64.and
+          (local.get $x)
+          (i64.const 255)
+        )
+        (i64.const 256)
+      )
+    )
+    ;; The constant is now 255 and we cannot optimize here.
+    (drop
+      (i64.eq
+        (i64.and
+          (local.get $x)
+          (i64.const 255)
+        )
+        (i64.const 255)
+      )
+    )
   )
 )
diff --git a/test/lit/passes/optimize-instructions-nontrapping-float-to-int.wast b/test/lit/passes/optimize-instructions-nontrapping-float-to-int.wast
new file mode 100644
index 0000000..d3e759e
--- /dev/null
+++ b/test/lit/passes/optimize-instructions-nontrapping-float-to-int.wast
@@ -0,0 +1,77 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --optimize-instructions --enable-nontrapping-float-to-int -S -o - | filecheck %s
+
+(module
+  (memory 0)
+
+  ;; CHECK:      (func $simplify_int_float_sat_conversion_roundtrips (param $x i32) (param $y i64)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.trunc_sat_f64_s
+  ;; CHECK-NEXT:    (f64.convert_i32_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.trunc_sat_f64_u
+  ;; CHECK-NEXT:    (f64.convert_i32_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.trunc_sat_f32_u
+  ;; CHECK-NEXT:    (f32.convert_i32_u
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.trunc_sat_f64_u
+  ;; CHECK-NEXT:    (f64.convert_i64_u
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.trunc_sat_f64_s
+  ;; CHECK-NEXT:    (f64.convert_i64_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.trunc_sat_f32_u
+  ;; CHECK-NEXT:    (f32.convert_i32_s
+  ;; CHECK-NEXT:     (local.get $x)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i64.trunc_sat_f32_s
+  ;; CHECK-NEXT:    (f32.convert_i64_s
+  ;; CHECK-NEXT:     (local.get $y)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $simplify_int_float_sat_conversion_roundtrips (param $x i32) (param $y i64)
+    (drop (i32.trunc_sat_f64_u (f64.convert_i32_u (local.get $x))))
+    (drop (i32.trunc_sat_f64_s (f64.convert_i32_s (local.get $x))))
+
+    ;; skips
+    (drop (i32.trunc_sat_f64_s (f64.convert_i32_u (local.get $x))))
+    (drop (i32.trunc_sat_f64_u (f64.convert_i32_s (local.get $x))))
+    (drop (i32.trunc_sat_f32_u (f32.convert_i32_u (local.get $x))))
+    (drop (i32.trunc_sat_f64_u (f64.convert_i64_u (local.get $y))))
+    (drop (i64.trunc_sat_f64_s (f64.convert_i64_s (local.get $y))))
+    (drop (i64.trunc_sat_f32_u (f32.convert_i32_s (local.get $x))))
+    (drop (i64.trunc_sat_f32_s (f32.convert_i64_s (local.get $y))))
+  )
+)
diff --git a/test/lit/passes/optimize-instructions-sign-ext.wast b/test/lit/passes/optimize-instructions-sign-ext.wast
deleted file mode 100644
index 0b33967..0000000
--- a/test/lit/passes/optimize-instructions-sign-ext.wast
+++ /dev/null
@@ -1,65 +0,0 @@
-;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --optimize-instructions --enable-sign-ext -S -o - | filecheck %s
-
-(module
-  ;; CHECK:      (func $duplicate-elimination (param $x i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.extend8_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i32.extend16_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $duplicate-elimination (param $x i32)
-    (drop (i32.extend8_s (i32.extend8_s (local.get $x))))
-    (drop (i32.extend16_s (i32.extend16_s (local.get $x))))
-  )
-
-  ;; i64(x) << 56 >> 56   ==>   i64.extend8_s(x)
-  ;; i64(x) << 48 >> 48   ==>   i64.extend16_s(x)
-  ;; i64(x) << 32 >> 32   ==>   i64.extend32_s(x)
-  ;; i64.extend_i32_s(i32.wrap_i64(x))   ==>   i64.extend32_s(x)
-
-  ;; CHECK:      (func $i64-sign-extentions (param $x i64)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend8_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend16_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend32_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.shr_s
-  ;; CHECK-NEXT:    (i64.shl
-  ;; CHECK-NEXT:     (local.get $x)
-  ;; CHECK-NEXT:     (i64.const 16)
-  ;; CHECK-NEXT:    )
-  ;; CHECK-NEXT:    (i64.const 16)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (i64.extend32_s
-  ;; CHECK-NEXT:    (local.get $x)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $i64-sign-extentions (param $x i64)
-    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 56)) (i64.const 56)))
-    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 48)) (i64.const 48)))
-    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32)))
-    (drop (i64.shr_s (i64.shl (local.get $x) (i64.const 16)) (i64.const 16))) ;; skip
-    (drop (i64.extend_i32_s (i32.wrap_i64 (local.get $x))))
-  )
-)
diff --git a/test/lit/passes/optimize-instructions-typed-function-references.wast b/test/lit/passes/optimize-instructions-typed-function-references.wast
deleted file mode 100644
index 418195b..0000000
--- a/test/lit/passes/optimize-instructions-typed-function-references.wast
+++ /dev/null
@@ -1,17 +0,0 @@
-;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --optimize-instructions --enable-reference-types \
-;; RUN:   --enable-typed-function-references -S -o - | filecheck %s
-
-(module
-  ;; CHECK:      (type $i32-i32 (func (param i32) (result i32)))
-  (type $i32-i32 (func (param i32) (result i32)))
-  ;; this function has a reference parameter. we analyze parameters, and should
-  ;; not be confused by a type that has no bit size, in particular. this test
-  ;; just verifies that we do not crash on that.
-  ;; CHECK:      (func $call_from-param (param $f (ref null $i32-i32)) (result i32)
-  ;; CHECK-NEXT:  (unreachable)
-  ;; CHECK-NEXT: )
-  (func $call_from-param (param $f (ref null $i32-i32)) (result i32)
-    (unreachable)
-  )
-)
diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast
index 61de7e5..4675d0a 100644
--- a/test/lit/passes/poppify.wast
+++ b/test/lit/passes/poppify.wast
@@ -60,10 +60,8 @@
   )
 
   ;; CHECK:      (func $block (result i32)
-  ;; CHECK-NEXT:  (block $block (result i32)
-  ;; CHECK-NEXT:   (nop)
-  ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (i32.const 0)
   ;; CHECK-NEXT: )
   (func $block (result i32)
     (block i32
@@ -82,6 +80,19 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $nested (result i32)
+    (block $block i32
+      (block $block0 i32
+        (block $block1 i32
+          (i32.const 0)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $nested-nonames (result i32)
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT: )
+  (func $nested-nonames (result i32)
     (block i32
       (block i32
         (block i32
@@ -93,10 +104,10 @@
 
   ;; CHECK:      (func $child-blocks (result i32)
   ;; CHECK-NEXT:  (block $block (result i32)
-  ;; CHECK-NEXT:   (block $block2 (result i32)
+  ;; CHECK-NEXT:   (block $block0 (result i32)
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block3 (result i32)
+  ;; CHECK-NEXT:   (block $block1 (result i32)
   ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i32.add
@@ -106,6 +117,27 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $child-blocks (result i32)
+    (block $block (result i32)
+      (i32.add
+        (block $block0 (result i32)
+          (i32.const 0)
+        )
+        (block $block1 (result i32)
+          (i32.const 1)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $child-blocks-nonames (result i32)
+  ;; CHECK-NEXT:  (i32.const 0)
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT:  (i32.add
+  ;; CHECK-NEXT:   (pop i32)
+  ;; CHECK-NEXT:   (pop i32)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $child-blocks-nonames (result i32)
     (block (result i32)
       (i32.add
         (block (result i32)
diff --git a/test/lit/passes/post-emscripten.wast b/test/lit/passes/post-emscripten.wast
new file mode 100644
index 0000000..fc61e88
--- /dev/null
+++ b/test/lit/passes/post-emscripten.wast
@@ -0,0 +1,44 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s --post-emscripten -S -o - | filecheck %s
+
+;; Checks that the start/stop exports are removed and that the data they
+;; refer to is either zero'd out, or the segment emptied.
+
+(module
+ ;; CHECK:      (global $em_asm_start i32 (i32.const 1000))
+ (global $em_asm_start i32 (i32.const 1000))
+ ;; CHECK:      (global $em_asm_stop i32 (i32.const 1011))
+ (global $em_asm_stop i32 (i32.const 1011))
+ ;; CHECK:      (global $em_js_start i32 (i32.const 2006))
+ (global $em_js_start i32 (i32.const 2006))
+ ;; CHECK:      (global $em_js_stop i32 (i32.const 2015))
+ (global $em_js_stop i32 (i32.const 2015))
+ ;; CHECK:      (global $em_lib_deps_start i32 (i32.const 3000))
+ (global $em_lib_deps_start i32 (i32.const 3000))
+ ;; CHECK:      (global $em_lib_deps_stop i32 (i32.const 3009))
+ (global $em_lib_deps_stop i32 (i32.const 3009))
+ ;; CHECK:      (global $foo_start i32 (i32.const 4000))
+ (global $foo_start i32 (i32.const 4000))
+ ;; CHECK:      (global $foo_stop i32 (i32.const 4015))
+ (global $foo_stop i32 (i32.const 4015))
+ (memory 10 10)
+ ;; CHECK:      (memory $0 10 10)
+
+ ;; CHECK:      (data $data1 (i32.const 1000) "")
+ (data $data1 (i32.const 1000) "hello world")
+ ;; CHECK:      (data $data2 (i32.const 2000) "hello \00\00\00\00\00\00\00\00\00 world")
+ (data $data2 (i32.const 2000) "hello DELETE ME world")
+ ;; CHECK:      (data $data3 (i32.const 3000) "")
+ (data $data3 (i32.const 3000) "some deps")
+ (export "__start_em_asm" (global $em_asm_start))
+ (export "__stop_em_asm" (global $em_asm_stop))
+ (export "__start_em_js" (global $em_js_start))
+ (export "__stop_em_js" (global $em_js_stop))
+ (export "__start_em_lib_deps" (global $em_lib_deps_start))
+ (export "__stop_em_lib_deps" (global $em_lib_deps_stop))
+ ;; CHECK:      (export "__start_foo" (global $foo_start))
+ (export "__start_foo" (global $foo_start))
+ ;; CHECK:      (export "__stop_foo" (global $foo_stop))
+ (export "__stop_foo" (global $foo_stop))
+)
+
diff --git a/test/lit/passes/precompute-gc-immutable.wast b/test/lit/passes/precompute-gc-immutable.wast
index 0fd4e82..ebb26bf 100644
--- a/test/lit/passes/precompute-gc-immutable.wast
+++ b/test/lit/passes/precompute-gc-immutable.wast
@@ -94,6 +94,7 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (call $helper
@@ -136,8 +137,11 @@
   ;; CHECK:      (func $local-null (type $none_=>_none)
   ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
   ;; CHECK-NEXT:  (call $helper
-  ;; CHECK-NEXT:   (struct.get $struct-imm 0
-  ;; CHECK-NEXT:    (ref.null $struct-imm)
+  ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -248,7 +252,7 @@
   ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (local.set $ref-imm
   ;; CHECK-NEXT:     (struct.new $struct-imm
   ;; CHECK-NEXT:      (i32.const 1)
@@ -258,7 +262,7 @@
   ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block0
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (local.set $ref-imm
   ;; CHECK-NEXT:     (struct.new $struct-imm
   ;; CHECK-NEXT:      (i32.const 2)
@@ -664,10 +668,9 @@
   ;; Create an immutable vtable in an immutable global, but using an array
   ;; instead of a struct.
 
-  ;; CHECK:      (type $object (struct_subtype (field (ref $vtable)) data))
-
   ;; CHECK:      (type $vtable (array_subtype funcref data))
   (type $vtable (array_subtype funcref data))
+  ;; CHECK:      (type $object (struct_subtype (field (ref $vtable)) data))
   (type $object (struct_subtype (ref $vtable) data))
 
   ;; CHECK:      (global $vtable (ref $vtable) (array.init_static $vtable
@@ -736,7 +739,7 @@
   ;; data that is filled with vtables of different types. On usage, we do a
   ;; cast of the vtable type.
 
-  ;; CHECK:      (type $itable (array_subtype (ref null data) data))
+  ;; CHECK:      (type $itable (array_subtype dataref data))
   (type $itable (array_subtype (ref null data) data))
 
   ;; CHECK:      (type $object (struct_subtype (field (ref $itable)) data))
diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast
index 14347bd..8961586 100644
--- a/test/lit/passes/precompute-gc.wast
+++ b/test/lit/passes/precompute-gc.wast
@@ -15,12 +15,14 @@
 
  ;; two incompatible struct types
  (type $A (struct (field (mut f32))))
+ ;; CHECK:      (type $func-return-i32 (func (result i32)))
+
  ;; CHECK:      (type $B (struct (field (mut f64))))
+ ;; NOMNL:      (type $func-return-i32 (func_subtype (result i32) func))
+
  ;; NOMNL:      (type $B (struct_subtype (field (mut f64)) data))
  (type $B (struct (field (mut f64))))
 
- ;; CHECK:      (type $func-return-i32 (func (result i32)))
- ;; NOMNL:      (type $func-return-i32 (func_subtype (result i32) func))
  (type $func-return-i32 (func (result i32)))
 
  ;; CHECK:      (import "fuzzing-support" "log-i32" (func $log (param i32)))
@@ -30,11 +32,11 @@
  ;; CHECK:      (func $test-fallthrough (result i32)
  ;; CHECK-NEXT:  (local $x funcref)
  ;; CHECK-NEXT:  (local.set $x
- ;; CHECK-NEXT:   (block (result funcref)
+ ;; CHECK-NEXT:   (block (result nullfuncref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $test-fallthrough)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (ref.null func)
+ ;; CHECK-NEXT:    (ref.null nofunc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 1)
@@ -42,11 +44,11 @@
  ;; NOMNL:      (func $test-fallthrough (type $func-return-i32) (result i32)
  ;; NOMNL-NEXT:  (local $x funcref)
  ;; NOMNL-NEXT:  (local.set $x
- ;; NOMNL-NEXT:   (block (result funcref)
+ ;; NOMNL-NEXT:   (block (result nullfuncref)
  ;; NOMNL-NEXT:    (drop
  ;; NOMNL-NEXT:     (call $test-fallthrough)
  ;; NOMNL-NEXT:    )
- ;; NOMNL-NEXT:    (ref.null func)
+ ;; NOMNL-NEXT:    (ref.null nofunc)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (i32.const 1)
@@ -74,9 +76,8 @@
  ;; CHECK:      (func $load-from-struct
  ;; CHECK-NEXT:  (local $x (ref null $struct))
  ;; CHECK-NEXT:  (local.set $x
- ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+ ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 1)
- ;; CHECK-NEXT:    (rtt.canon $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $log
@@ -85,9 +86,8 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
- ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+ ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 2)
- ;; CHECK-NEXT:    (rtt.canon $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $log
@@ -108,9 +108,8 @@
  ;; NOMNL:      (func $load-from-struct (type $none_=>_none)
  ;; NOMNL-NEXT:  (local $x (ref null $struct))
  ;; NOMNL-NEXT:  (local.set $x
- ;; NOMNL-NEXT:   (struct.new_with_rtt $struct
+ ;; NOMNL-NEXT:   (struct.new $struct
  ;; NOMNL-NEXT:    (i32.const 1)
- ;; NOMNL-NEXT:    (rtt.canon $struct)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $log
@@ -119,9 +118,8 @@
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $x
- ;; NOMNL-NEXT:   (struct.new_with_rtt $struct
+ ;; NOMNL-NEXT:   (struct.new $struct
  ;; NOMNL-NEXT:    (i32.const 2)
- ;; NOMNL-NEXT:    (rtt.canon $struct)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $log
@@ -142,9 +140,8 @@
  (func $load-from-struct
   (local $x (ref null $struct))
   (local.set $x
-   (struct.new_with_rtt $struct
+   (struct.new $struct
     (i32.const 1)
-    (rtt.canon $struct)
    )
   )
   ;; we don't precompute these, as we don't know if the GC data was modified
@@ -154,9 +151,8 @@
   )
   ;; Assign a new struct
   (local.set $x
-   (struct.new_with_rtt $struct
+   (struct.new $struct
     (i32.const 2)
-    (rtt.canon $struct)
    )
   )
   (call $log
@@ -176,15 +172,13 @@
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $i)
  ;; CHECK-NEXT:   (local.set $x
- ;; CHECK-NEXT:    (struct.new_with_rtt $struct
+ ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (i32.const 1)
- ;; CHECK-NEXT:     (rtt.canon $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $x
- ;; CHECK-NEXT:    (struct.new_with_rtt $struct
+ ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (i32.const 2)
- ;; CHECK-NEXT:     (rtt.canon $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -199,15 +193,13 @@
  ;; NOMNL-NEXT:  (if
  ;; NOMNL-NEXT:   (local.get $i)
  ;; NOMNL-NEXT:   (local.set $x
- ;; NOMNL-NEXT:    (struct.new_with_rtt $struct
+ ;; NOMNL-NEXT:    (struct.new $struct
  ;; NOMNL-NEXT:     (i32.const 1)
- ;; NOMNL-NEXT:     (rtt.canon $struct)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (local.set $x
- ;; NOMNL-NEXT:    (struct.new_with_rtt $struct
+ ;; NOMNL-NEXT:    (struct.new $struct
  ;; NOMNL-NEXT:     (i32.const 2)
- ;; NOMNL-NEXT:     (rtt.canon $struct)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
@@ -223,15 +215,13 @@
   (if
    (local.get $i)
    (local.set $x
-    (struct.new_with_rtt $struct
+    (struct.new $struct
      (i32.const 1)
-     (rtt.canon $struct)
     )
    )
    (local.set $x
-    (struct.new_with_rtt $struct
+    (struct.new $struct
      (i32.const 2)
-     (rtt.canon $struct)
     )
    )
   )
@@ -277,9 +267,8 @@
  ;; CHECK:      (func $load-from-struct-bad-escape
  ;; CHECK-NEXT:  (local $x (ref null $struct))
  ;; CHECK-NEXT:  (local.set $x
- ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+ ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 1)
- ;; CHECK-NEXT:    (rtt.canon $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $modify-gc-heap
@@ -294,9 +283,8 @@
  ;; NOMNL:      (func $load-from-struct-bad-escape (type $none_=>_none)
  ;; NOMNL-NEXT:  (local $x (ref null $struct))
  ;; NOMNL-NEXT:  (local.set $x
- ;; NOMNL-NEXT:   (struct.new_with_rtt $struct
+ ;; NOMNL-NEXT:   (struct.new $struct
  ;; NOMNL-NEXT:    (i32.const 1)
- ;; NOMNL-NEXT:    (rtt.canon $struct)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $modify-gc-heap
@@ -311,9 +299,8 @@
  (func $load-from-struct-bad-escape (export "test")
   (local $x (ref null $struct))
   (local.set $x
-   (struct.new_with_rtt $struct
+   (struct.new $struct
     (i32.const 1)
-    (rtt.canon $struct)
    )
   )
   (call $modify-gc-heap
@@ -355,13 +342,13 @@
  ;; CHECK-NEXT:  (call $log
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $x)
- ;; CHECK-NEXT:    (ref.null $struct)
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $log
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $x)
- ;; CHECK-NEXT:    (ref.null $struct)
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $log
@@ -380,13 +367,13 @@
  ;; NOMNL-NEXT:  (call $log
  ;; NOMNL-NEXT:   (ref.eq
  ;; NOMNL-NEXT:    (local.get $x)
- ;; NOMNL-NEXT:    (ref.null $struct)
+ ;; NOMNL-NEXT:    (ref.null none)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $log
  ;; NOMNL-NEXT:   (ref.eq
  ;; NOMNL-NEXT:    (local.get $x)
- ;; NOMNL-NEXT:    (ref.null $struct)
+ ;; NOMNL-NEXT:    (ref.null none)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (call $log
@@ -431,9 +418,8 @@
  ;; CHECK-NEXT:  (local $y (ref null $struct))
  ;; CHECK-NEXT:  (local $tempresult i32)
  ;; CHECK-NEXT:  (local.set $x
- ;; CHECK-NEXT:   (struct.new_with_rtt $struct
+ ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 1)
- ;; CHECK-NEXT:    (rtt.canon $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $y
@@ -449,9 +435,8 @@
  ;; NOMNL-NEXT:  (local $y (ref null $struct))
  ;; NOMNL-NEXT:  (local $tempresult i32)
  ;; NOMNL-NEXT:  (local.set $x
- ;; NOMNL-NEXT:   (struct.new_with_rtt $struct
+ ;; NOMNL-NEXT:   (struct.new $struct
  ;; NOMNL-NEXT:    (i32.const 1)
- ;; NOMNL-NEXT:    (rtt.canon $struct)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $y
@@ -467,9 +452,8 @@
   (local $y (ref null $struct))
   (local $tempresult i32)
   (local.set $x
-   (struct.new_with_rtt $struct
+   (struct.new $struct
     (i32.const 1)
-    (rtt.canon $struct)
    )
   )
   (local.set $y
@@ -492,9 +476,7 @@
  ;; CHECK-NEXT:  (local.set $tempresult
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.tee $tempref
- ;; CHECK-NEXT:     (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:      (rtt.canon $empty)
- ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (struct.new_default $empty)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $tempref)
  ;; CHECK-NEXT:   )
@@ -507,9 +489,7 @@
  ;; NOMNL-NEXT:  (local.set $tempresult
  ;; NOMNL-NEXT:   (ref.eq
  ;; NOMNL-NEXT:    (local.tee $tempref
- ;; NOMNL-NEXT:     (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:      (rtt.canon $empty)
- ;; NOMNL-NEXT:     )
+ ;; NOMNL-NEXT:     (struct.new_default $empty)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:    (local.get $tempref)
  ;; NOMNL-NEXT:   )
@@ -524,9 +504,7 @@
    (ref.eq
     ;; allocate one struct
     (local.tee $tempref
-     (struct.new_with_rtt $empty
-      (rtt.canon $empty)
-     )
+     (struct.new $empty)
     )
     (local.get $tempref)
    )
@@ -561,12 +539,8 @@
   (local.set $tempresult
    ;; allocate two different structs
    (ref.eq
-    (struct.new_with_rtt $empty
-     (rtt.canon $empty)
-    )
-    (struct.new_with_rtt $empty
-     (rtt.canon $empty)
-    )
+    (struct.new $empty)
+    (struct.new $empty)
    )
   )
   (local.get $tempresult)
@@ -577,9 +551,7 @@
  ;; CHECK-NEXT:  (local $tempref (ref null $empty))
  ;; CHECK-NEXT:  (local.set $tempresult
  ;; CHECK-NEXT:   (ref.eq
- ;; CHECK-NEXT:    (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:     (rtt.canon $empty)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (struct.new_default $empty)
  ;; CHECK-NEXT:    (local.get $input)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
@@ -590,9 +562,7 @@
  ;; NOMNL-NEXT:  (local $tempref (ref null $empty))
  ;; NOMNL-NEXT:  (local.set $tempresult
  ;; NOMNL-NEXT:   (ref.eq
- ;; NOMNL-NEXT:    (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:     (rtt.canon $empty)
- ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (struct.new_default $empty)
  ;; NOMNL-NEXT:    (local.get $input)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
@@ -605,9 +575,7 @@
    ;; allocate a struct and compare it to a param, which we know nothing about,
    ;; so we can infer nothing here at all.
    (ref.eq
-    (struct.new_with_rtt $empty
-     (rtt.canon $empty)
-    )
+    (struct.new $empty)
     (local.get $input)
    )
   )
@@ -684,9 +652,7 @@
  ;; CHECK-NEXT:  (local $tempref (ref null $empty))
  ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
  ;; CHECK-NEXT:  (local.set $tempref
- ;; CHECK-NEXT:   (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:    (rtt.canon $empty)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (struct.new_default $empty)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $stashedref
  ;; CHECK-NEXT:   (local.get $tempref)
@@ -696,9 +662,7 @@
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $tempref
- ;; CHECK-NEXT:    (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:     (rtt.canon $empty)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (struct.new_default $empty)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $tempresult
@@ -714,9 +678,7 @@
  ;; NOMNL-NEXT:  (local $tempref (ref null $empty))
  ;; NOMNL-NEXT:  (local $stashedref (ref null $empty))
  ;; NOMNL-NEXT:  (local.set $tempref
- ;; NOMNL-NEXT:   (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:    (rtt.canon $empty)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (struct.new_default $empty)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $stashedref
  ;; NOMNL-NEXT:   (local.get $tempref)
@@ -726,9 +688,7 @@
  ;; NOMNL-NEXT:    (i32.const 0)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (local.set $tempref
- ;; NOMNL-NEXT:    (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:     (rtt.canon $empty)
- ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (struct.new_default $empty)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $tempresult
@@ -744,9 +704,7 @@
   (local $tempref (ref null $empty))
   (local $stashedref (ref null $empty))
   (local.set $tempref
-   (struct.new_with_rtt $empty
-    (rtt.canon $empty)
-   )
+   (struct.new $empty)
   )
   (local.set $stashedref
    (local.get $tempref)
@@ -758,9 +716,7 @@
     (i32.const 0)
    )
    (local.set $tempref
-    (struct.new_with_rtt $empty
-     (rtt.canon $empty)
-    )
+    (struct.new $empty)
    )
   )
   (local.set $tempresult
@@ -777,9 +733,7 @@
  ;; CHECK-NEXT:  (local $tempref (ref null $empty))
  ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
  ;; CHECK-NEXT:  (local.set $tempref
- ;; CHECK-NEXT:   (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:    (rtt.canon $empty)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (struct.new_default $empty)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $stashedref
  ;; CHECK-NEXT:   (local.get $tempref)
@@ -792,9 +746,7 @@
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $tempref
- ;; CHECK-NEXT:    (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:     (rtt.canon $empty)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (struct.new_default $empty)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (br_if $loop
  ;; CHECK-NEXT:    (call $helper
@@ -808,9 +760,7 @@
  ;; NOMNL-NEXT:  (local $tempref (ref null $empty))
  ;; NOMNL-NEXT:  (local $stashedref (ref null $empty))
  ;; NOMNL-NEXT:  (local.set $tempref
- ;; NOMNL-NEXT:   (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:    (rtt.canon $empty)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (struct.new_default $empty)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $stashedref
  ;; NOMNL-NEXT:   (local.get $tempref)
@@ -823,9 +773,7 @@
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (local.set $tempref
- ;; NOMNL-NEXT:    (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:     (rtt.canon $empty)
- ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (struct.new_default $empty)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (br_if $loop
  ;; NOMNL-NEXT:    (call $helper
@@ -839,9 +787,7 @@
   (local $tempref (ref null $empty))
   (local $stashedref (ref null $empty))
   (local.set $tempref
-   (struct.new_with_rtt $empty
-    (rtt.canon $empty)
-   )
+   (struct.new $empty)
   )
   (local.set $stashedref
    (local.get $tempref)
@@ -856,9 +802,7 @@
     )
    )
    (local.set $tempref
-    (struct.new_with_rtt $empty
-     (rtt.canon $empty)
-    )
+    (struct.new $empty)
    )
    (br_if $loop
     (call $helper
@@ -873,9 +817,7 @@
  ;; CHECK-NEXT:  (local $tempref (ref null $empty))
  ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
  ;; CHECK-NEXT:  (local.set $tempref
- ;; CHECK-NEXT:   (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:    (rtt.canon $empty)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (struct.new_default $empty)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $stashedref
  ;; CHECK-NEXT:   (local.get $tempref)
@@ -896,9 +838,7 @@
  ;; NOMNL-NEXT:  (local $tempref (ref null $empty))
  ;; NOMNL-NEXT:  (local $stashedref (ref null $empty))
  ;; NOMNL-NEXT:  (local.set $tempref
- ;; NOMNL-NEXT:   (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:    (rtt.canon $empty)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (struct.new_default $empty)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $stashedref
  ;; NOMNL-NEXT:   (local.get $tempref)
@@ -921,9 +861,7 @@
   ;; As above, but remove the new in the loop, so that each loop iteration does
   ;; in fact have the ref locals identical, and we can precompute a 1.
   (local.set $tempref
-   (struct.new_with_rtt $empty
-    (rtt.canon $empty)
-   )
+   (struct.new $empty)
   )
   (local.set $stashedref
    (local.get $tempref)
@@ -949,9 +887,7 @@
  ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
  ;; CHECK-NEXT:  (loop $loop
  ;; CHECK-NEXT:   (local.set $tempref
- ;; CHECK-NEXT:    (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:     (rtt.canon $empty)
- ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (struct.new_default $empty)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $stashedref
  ;; CHECK-NEXT:    (local.get $tempref)
@@ -972,9 +908,7 @@
  ;; NOMNL-NEXT:  (local $stashedref (ref null $empty))
  ;; NOMNL-NEXT:  (loop $loop
  ;; NOMNL-NEXT:   (local.set $tempref
- ;; NOMNL-NEXT:    (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:     (rtt.canon $empty)
- ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (struct.new_default $empty)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (local.set $stashedref
  ;; NOMNL-NEXT:    (local.get $tempref)
@@ -997,9 +931,7 @@
    ;; Another example of a loop where we can optimize. Here the new is inside
    ;; the loop.
    (local.set $tempref
-    (struct.new_with_rtt $empty
-     (rtt.canon $empty)
-    )
+    (struct.new $empty)
    )
    (local.set $stashedref
     (local.get $tempref)
@@ -1028,9 +960,7 @@
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $tempref
- ;; CHECK-NEXT:     (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:      (rtt.canon $empty)
- ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (struct.new_default $empty)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $stashedref
@@ -1059,9 +989,7 @@
  ;; NOMNL-NEXT:     (i32.const 0)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:    (local.set $tempref
- ;; NOMNL-NEXT:     (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:      (rtt.canon $empty)
- ;; NOMNL-NEXT:     )
+ ;; NOMNL-NEXT:     (struct.new_default $empty)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:   (local.set $stashedref
@@ -1094,9 +1022,7 @@
      (i32.const 0)
     )
     (local.set $tempref
-     (struct.new_with_rtt $empty
-      (rtt.canon $empty)
-     )
+     (struct.new $empty)
     )
    )
    (local.set $stashedref
@@ -1129,22 +1055,28 @@
  ;; CHECK:      (func $odd-cast-and-get
  ;; CHECK-NEXT:  (local $temp (ref null $B))
  ;; CHECK-NEXT:  (local.set $temp
- ;; CHECK-NEXT:   (ref.null $B)
+ ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (struct.get $B 0
- ;; CHECK-NEXT:    (ref.null $B)
+ ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
+ ;; CHECK-NEXT:    (drop
+ ;; CHECK-NEXT:     (ref.null none)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $odd-cast-and-get (type $none_=>_none)
  ;; NOMNL-NEXT:  (local $temp (ref null $B))
  ;; NOMNL-NEXT:  (local.set $temp
- ;; NOMNL-NEXT:   (ref.null $B)
+ ;; NOMNL-NEXT:   (ref.null none)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (drop
- ;; NOMNL-NEXT:   (struct.get $B 0
- ;; NOMNL-NEXT:    (ref.null $B)
+ ;; NOMNL-NEXT:   (block ;; (replaces something unreachable we can't emit)
+ ;; NOMNL-NEXT:    (drop
+ ;; NOMNL-NEXT:     (ref.null none)
+ ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (unreachable)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
@@ -1155,9 +1087,8 @@
   ;; ref.cast instruction has, that is, the value is a null of type $B). So this
   ;; is an odd cast that "works".
   (local.set $temp
-   (ref.cast
+   (ref.cast_static $B
     (ref.null $A)
-    (rtt.canon $B)
    )
   )
   (drop
@@ -1173,13 +1104,16 @@
  ;; CHECK-NEXT:  (local $temp ((ref null $B) i32))
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (tuple.make
- ;; CHECK-NEXT:    (ref.null $B)
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (struct.get $B 0
- ;; CHECK-NEXT:    (ref.null $B)
+ ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
+ ;; CHECK-NEXT:    (drop
+ ;; CHECK-NEXT:     (ref.null none)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
@@ -1187,13 +1121,16 @@
  ;; NOMNL-NEXT:  (local $temp ((ref null $B) i32))
  ;; NOMNL-NEXT:  (local.set $temp
  ;; NOMNL-NEXT:   (tuple.make
- ;; NOMNL-NEXT:    (ref.null $B)
+ ;; NOMNL-NEXT:    (ref.null none)
  ;; NOMNL-NEXT:    (i32.const 10)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (drop
- ;; NOMNL-NEXT:   (struct.get $B 0
- ;; NOMNL-NEXT:    (ref.null $B)
+ ;; NOMNL-NEXT:   (block ;; (replaces something unreachable we can't emit)
+ ;; NOMNL-NEXT:    (drop
+ ;; NOMNL-NEXT:     (ref.null none)
+ ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:    (unreachable)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
@@ -1202,9 +1139,8 @@
   ;; As above, but with a tuple.
   (local.set $temp
    (tuple.make
-    (ref.cast
+    (ref.cast_static $B
      (ref.null $A)
-     (rtt.canon $B)
     )
     (i32.const 10)
    )
@@ -1230,26 +1166,24 @@
 
  ;; CHECK:      (func $odd-cast-and-get-non-null (param $temp (ref $func-return-i32))
  ;; CHECK-NEXT:  (local.set $temp
- ;; CHECK-NEXT:   (ref.cast
+ ;; CHECK-NEXT:   (ref.cast_static $func-return-i32
  ;; CHECK-NEXT:    (ref.func $receive-f64)
- ;; CHECK-NEXT:    (rtt.canon $func-return-i32)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (call_ref
+ ;; CHECK-NEXT:   (call_ref $func-return-i32
  ;; CHECK-NEXT:    (local.get $temp)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $odd-cast-and-get-non-null (type $ref|$func-return-i32|_=>_none) (param $temp (ref $func-return-i32))
  ;; NOMNL-NEXT:  (local.set $temp
- ;; NOMNL-NEXT:   (ref.cast
+ ;; NOMNL-NEXT:   (ref.cast_static $func-return-i32
  ;; NOMNL-NEXT:    (ref.func $receive-f64)
- ;; NOMNL-NEXT:    (rtt.canon $func-return-i32)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (drop
- ;; NOMNL-NEXT:   (call_ref
+ ;; NOMNL-NEXT:   (call_ref $func-return-i32
  ;; NOMNL-NEXT:    (local.get $temp)
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
@@ -1257,65 +1191,19 @@
  (func $odd-cast-and-get-non-null (param $temp (ref $func-return-i32))
   ;; Try to cast a function to an incompatible type.
   (local.set $temp
-   (ref.cast
+   (ref.cast_static $func-return-i32
     (ref.func $receive-f64)
-    (rtt.canon $func-return-i32)
    )
   )
   (drop
    ;; Read from the local, checking whether precompute set a value there (it
    ;; should not, as the cast fails).
-   (call_ref
+   (call_ref $func-return-i32
     (local.get $temp)
    )
   )
  )
 
- ;; Regression test checking that breaking RTTs are interpreted correctly.
- ;; CHECK:      (func $cast-breaking-rtt
- ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (ref.cast
- ;; CHECK-NEXT:    (ref.cast
- ;; CHECK-NEXT:     (struct.new_default $struct)
- ;; CHECK-NEXT:     (call $unreachable-rtt)
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (call $unreachable-rtt)
- ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:  )
- ;; CHECK-NEXT: )
- ;; NOMNL:      (func $cast-breaking-rtt (type $none_=>_none)
- ;; NOMNL-NEXT:  (drop
- ;; NOMNL-NEXT:   (ref.cast
- ;; NOMNL-NEXT:    (ref.cast
- ;; NOMNL-NEXT:     (struct.new_default $struct)
- ;; NOMNL-NEXT:     (call $unreachable-rtt)
- ;; NOMNL-NEXT:    )
- ;; NOMNL-NEXT:    (call $unreachable-rtt)
- ;; NOMNL-NEXT:   )
- ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT: )
- (func $cast-breaking-rtt
-  (drop
-   (ref.cast
-    (ref.cast
-     (struct.new_default $struct)
-     (call $unreachable-rtt)
-    )
-    (call $unreachable-rtt)
-   )
-  )
- )
-
- ;; CHECK:      (func $unreachable-rtt (result (rtt $struct))
- ;; CHECK-NEXT:  (unreachable)
- ;; CHECK-NEXT: )
- ;; NOMNL:      (func $unreachable-rtt (type $none_=>_rtt_$struct) (result (rtt $struct))
- ;; NOMNL-NEXT:  (unreachable)
- ;; NOMNL-NEXT: )
- (func $unreachable-rtt (result (rtt $struct))
-  (unreachable)
- )
-
  ;; CHECK:      (func $new_block_unreachable (result anyref)
  ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
  ;; CHECK-NEXT:   (drop
@@ -1323,9 +1211,7 @@
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (drop
- ;; CHECK-NEXT:    (rtt.canon $struct)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $new_block_unreachable (type $none_=>_anyref) (result anyref)
@@ -1335,67 +1221,21 @@
  ;; NOMNL-NEXT:     (unreachable)
  ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:   )
- ;; NOMNL-NEXT:   (drop
- ;; NOMNL-NEXT:    (rtt.canon $struct)
- ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (unreachable)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $new_block_unreachable (result anyref)
-  (struct.new_with_rtt $struct
+  (struct.new $struct
    ;; The value is a block with an unreachable. precompute will get rid of the
    ;; block, after which fuzz-exec should not crash - this is a regression test
    ;; for us being careful in how we execute an unreachable struct.new
    (block $label$1 (result i32)
     (unreachable)
    )
-   (rtt.canon $struct)
-  )
- )
-
- ;; CHECK:      (func $br_on_cast-on-creation-rtt (result (ref $empty))
- ;; CHECK-NEXT:  (block $label (result (ref $empty))
- ;; CHECK-NEXT:   (drop
- ;; CHECK-NEXT:    (br_on_cast $label
- ;; CHECK-NEXT:     (struct.new_default_with_rtt $empty
- ;; CHECK-NEXT:      (rtt.canon $empty)
- ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (rtt.canon $empty)
- ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (unreachable)
- ;; CHECK-NEXT:  )
- ;; CHECK-NEXT: )
- ;; NOMNL:      (func $br_on_cast-on-creation-rtt (type $none_=>_ref|$empty|) (result (ref $empty))
- ;; NOMNL-NEXT:  (block $label (result (ref $empty))
- ;; NOMNL-NEXT:   (drop
- ;; NOMNL-NEXT:    (br_on_cast $label
- ;; NOMNL-NEXT:     (struct.new_default_with_rtt $empty
- ;; NOMNL-NEXT:      (rtt.canon $empty)
- ;; NOMNL-NEXT:     )
- ;; NOMNL-NEXT:     (rtt.canon $empty)
- ;; NOMNL-NEXT:    )
- ;; NOMNL-NEXT:   )
- ;; NOMNL-NEXT:   (unreachable)
- ;; NOMNL-NEXT:  )
- ;; NOMNL-NEXT: )
- (func $br_on_cast-on-creation-rtt (result (ref $empty))
-  (block $label (result (ref $empty))
-   (drop
-    ;; The br_on_cast will read the GC data created from struct.new, which must
-    ;; emit it properly, including with an RTT which it will read from (since
-    ;; this instructions uses an RTT).
-    (br_on_cast $label
-     (struct.new_default_with_rtt $empty
-      (rtt.canon $empty)
-     )
-     (rtt.canon $empty)
-    )
-   )
-   (unreachable)
   )
  )
 
- ;; CHECK:      (func $br_on_cast-on-creation-nortt (result (ref $empty))
+ ;; CHECK:      (func $br_on_cast-on-creation (result (ref $empty))
  ;; CHECK-NEXT:  (block $label (result (ref $empty))
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (br_on_cast_static $label $empty
@@ -1405,7 +1245,7 @@
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $br_on_cast-on-creation-nortt (type $none_=>_ref|$empty|) (result (ref $empty))
+ ;; NOMNL:      (func $br_on_cast-on-creation (type $none_=>_ref|$empty|) (result (ref $empty))
  ;; NOMNL-NEXT:  (block $label (result (ref $empty))
  ;; NOMNL-NEXT:   (drop
  ;; NOMNL-NEXT:    (br_on_cast_static $label $empty
@@ -1415,10 +1255,9 @@
  ;; NOMNL-NEXT:   (unreachable)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
- (func $br_on_cast-on-creation-nortt (result (ref $empty))
+ (func $br_on_cast-on-creation (result (ref $empty))
   (block $label (result (ref $empty))
    (drop
-    ;; As above, but with no RTTs.
     (br_on_cast_static $label $empty
      (struct.new_default $empty)
     )
@@ -1438,7 +1277,7 @@
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $ref
- ;; CHECK-NEXT:   (ref.null $empty)
+ ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $helper
@@ -1470,7 +1309,7 @@
  ;; NOMNL-NEXT:   )
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (local.set $ref
- ;; NOMNL-NEXT:   (ref.null $empty)
+ ;; NOMNL-NEXT:   (ref.null none)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT:  (drop
  ;; NOMNL-NEXT:   (call $helper
@@ -1532,4 +1371,62 @@
    )
   )
  )
+
+ ;; CHECK:      (func $remove-set (result (ref func))
+ ;; CHECK-NEXT:  (local $nn funcref)
+ ;; CHECK-NEXT:  (local $i i32)
+ ;; CHECK-NEXT:  (loop $loop
+ ;; CHECK-NEXT:   (local.set $i
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (br $loop)
+ ;; CHECK-NEXT:   (return
+ ;; CHECK-NEXT:    (ref.as_non_null
+ ;; CHECK-NEXT:     (local.get $nn)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ ;; NOMNL:      (func $remove-set (type $none_=>_ref|func|) (result (ref func))
+ ;; NOMNL-NEXT:  (local $nn funcref)
+ ;; NOMNL-NEXT:  (local $i i32)
+ ;; NOMNL-NEXT:  (loop $loop
+ ;; NOMNL-NEXT:   (local.set $i
+ ;; NOMNL-NEXT:    (i32.const 0)
+ ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (br $loop)
+ ;; NOMNL-NEXT:   (return
+ ;; NOMNL-NEXT:    (ref.as_non_null
+ ;; NOMNL-NEXT:     (local.get $nn)
+ ;; NOMNL-NEXT:    )
+ ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:  )
+ ;; NOMNL-NEXT: )
+ (func $remove-set (result (ref func))
+  (local $nn (ref func))
+  (local $i i32)
+  (loop $loop
+   ;; Add a local.set here in the loop, just so the entire loop is not optimized
+   ;; out.
+   (local.set $i
+    (i32.const 0)
+   )
+   ;; This entire block can be precomputed into an unconditional br. That
+   ;; removes the local.set, which means the local no longer validates since
+   ;; there is a get without a set (the get is never reached, but the validator
+   ;; does not take that into account). Fixups will turn the local nullable to
+   ;; avoid that problem.
+   (block
+    (br_if $loop
+     (i32.const 1)
+    )
+    (local.set $nn
+     (ref.func $remove-set)
+    )
+   )
+   (return
+    (local.get $nn)
+   )
+  )
+ )
 )
diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast
index 458958d..9473b06 100644
--- a/test/lit/passes/remove-unused-brs-gc.wast
+++ b/test/lit/passes/remove-unused-brs-gc.wast
@@ -8,13 +8,15 @@
 
  ;; CHECK:      (func $br_on_non_data-1
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (block $any (result anyref)
+ ;; CHECK-NEXT:   (block $any (result i31ref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (br $any
- ;; CHECK-NEXT:      (ref.func $br_on_non_data-1)
+ ;; CHECK-NEXT:      (i31.new
+ ;; CHECK-NEXT:       (i32.const 0)
+ ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (ref.null any)
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
@@ -22,22 +24,22 @@
   (drop
    (block $any (result anyref)
     (drop
-     ;; A function is not data, and so we should branch.
+     ;; An i31 is not data, and so we should branch.
      (br_on_non_data $any
-      (ref.func $br_on_non_data-1)
+      (i31.new (i32.const 0))
      )
     )
     (ref.null any)
    )
   )
  )
- ;; CHECK:      (func $br_on_non_data-2 (param $data dataref)
+ ;; CHECK:      (func $br_on_non_data-2 (param $data (ref data))
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (block $any (result anyref)
+ ;; CHECK-NEXT:   (block $any (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $data)
  ;; CHECK-NEXT:    )
- ;; CHECK-NEXT:    (ref.null any)
+ ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
@@ -55,10 +57,10 @@
   )
  )
 
- ;; CHECK:      (func $br_on-if (param $0 dataref)
+ ;; CHECK:      (func $br_on-if (param $0 (ref data))
  ;; CHECK-NEXT:  (block $label
  ;; CHECK-NEXT:   (drop
- ;; CHECK-NEXT:    (select (result dataref)
+ ;; CHECK-NEXT:    (select (result (ref data))
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:     (i32.const 0)
@@ -142,7 +144,7 @@
  ;; CHECK-NEXT:  (block $block (result (ref $struct))
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (br_on_cast_static $block $struct
- ;; CHECK-NEXT:     (ref.null $struct)
+ ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
@@ -184,35 +186,110 @@
   )
  )
 
- ;; CHECK:      (func $br_on_cast_dynamic (result (ref $struct))
- ;; CHECK-NEXT:  (local $temp (ref null $struct))
- ;; CHECK-NEXT:  (block $block (result (ref $struct))
- ;; CHECK-NEXT:   (drop
- ;; CHECK-NEXT:    (br_on_cast $block
- ;; CHECK-NEXT:     (struct.new_default_with_rtt $struct
- ;; CHECK-NEXT:      (rtt.canon $struct)
+ ;; CHECK:      (func $casts-are-costly (param $x i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (if (result i32)
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:    (ref.test_static $struct
+ ;; CHECK-NEXT:     (ref.null none)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (if (result anyref)
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (ref.cast_static $struct
+ ;; CHECK-NEXT:     (ref.null none)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (if (result anyref)
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:    (block $something (result anyref)
+ ;; CHECK-NEXT:     (drop
+ ;; CHECK-NEXT:      (br_on_cast_static $something $struct
+ ;; CHECK-NEXT:       (ref.null none)
+ ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
- ;; CHECK-NEXT:     (rtt.canon $struct)
+ ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (select (result anyref)
+ ;; CHECK-NEXT:    (block (result anyref)
+ ;; CHECK-NEXT:     (block $nothing
+ ;; CHECK-NEXT:      (drop
+ ;; CHECK-NEXT:       (br_on_null $nothing
+ ;; CHECK-NEXT:        (ref.null none)
+ ;; CHECK-NEXT:       )
+ ;; CHECK-NEXT:      )
+ ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (ref.null none)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
- ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
- (func $br_on_cast_dynamic (result (ref $struct))
-  (local $temp (ref null $struct))
-  (block $block (result (ref $struct))
-   (drop
-    ;; This dynamic cast happens to be optimizable since we see both sides use
-    ;; rtt.canon, but we do not inspect things that closely, and leave such
-    ;; dynamic casts to runtime.
-    (br_on_cast $block
-     (struct.new_with_rtt $struct
-       (rtt.canon $struct)
+ (func $casts-are-costly (param $x i32)
+  ;; We never turn an if into a select if an arm has a cast of any kind, as
+  ;; those things involve branches internally, so we'd be adding more than we
+  ;; save.
+  (drop
+   (if (result i32)
+    (local.get $x)
+    (ref.test_static $struct
+     (ref.null any)
+    )
+    (i32.const 0)
+   )
+  )
+  (drop
+   (if (result anyref)
+    (local.get $x)
+    (ref.null any)
+    (ref.cast_static $struct
+     (ref.null any)
+    )
+   )
+  )
+  (drop
+   (if (result anyref)
+    (local.get $x)
+    (block (result anyref)
+     (block $something (result anyref)
+      (drop
+       (br_on_cast_static $something $struct
+        (ref.null $struct)
+       )
+      )
+      (ref.null any)
      )
-     (rtt.canon $struct)
     )
+    (ref.null any)
+   )
+  )
+  ;; However, null checks are fairly fast, and we will emit a select here.
+  (drop
+   (if (result anyref)
+    (local.get $x)
+    (block (result anyref)
+     (block $nothing
+      (drop
+       (br_on_null $nothing
+        (ref.null $struct)
+       )
+      )
+     )
+     (ref.null any)
+    )
+    (ref.null any)
    )
-   (unreachable)
   )
  )
 )
diff --git a/test/lit/passes/remove-unused-brs.wast b/test/lit/passes/remove-unused-brs.wast
index 3c5d499..7db4bdc 100644
--- a/test/lit/passes/remove-unused-brs.wast
+++ b/test/lit/passes/remove-unused-brs.wast
@@ -4,16 +4,11 @@
 
 
 (module
-  ;; CHECK:      (type $none_=>_i32 (func (result i32)))
-  (type $none_=>_i32 (func (result i32)))
-  ;; CHECK:      (type $i32_=>_none (func (param i32)))
-  (type $i32_=>_none (func (param i32)))
-
   ;; Regression test in which we need to calculate a proper LUB.
   ;; CHECK:      (func $selectify-fresh-lub (param $x i32) (result anyref)
-  ;; CHECK-NEXT:  (select (result funcref)
-  ;; CHECK-NEXT:   (ref.null $none_=>_i32)
-  ;; CHECK-NEXT:   (ref.null $i32_=>_none)
+  ;; CHECK-NEXT:  (select (result nullref)
+  ;; CHECK-NEXT:   (ref.null none)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:   (local.get $x)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -21,10 +16,10 @@
     (if
       (local.get $x)
       (return
-        (ref.null $none_=>_i32)
+        (ref.null i31)
       )
       (return
-        (ref.null $i32_=>_none)
+        (ref.null data)
       )
     )
   )
@@ -109,7 +104,7 @@
 
   ;; CHECK:      (func $restructure-br_if-condition-reorderable (param $x i32) (result i32)
   ;; CHECK-NEXT:  (if (result i32)
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (call $nothing)
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
@@ -142,7 +137,7 @@
 
   ;; CHECK:      (func $restructure-br_if-value-effectful (param $x i32) (result i32)
   ;; CHECK-NEXT:  (select
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (call $nothing)
   ;; CHECK-NEXT:    (i32.const 100)
   ;; CHECK-NEXT:   )
@@ -153,7 +148,7 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (i32.const 300)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block0 (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (call $nothing)
   ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
@@ -186,11 +181,11 @@
   ;; CHECK-NEXT:  (block $x (result i32)
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (br_if $x
-  ;; CHECK-NEXT:     (block $block (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (call $nothing)
   ;; CHECK-NEXT:      (i32.const 100)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (block $block1 (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (call $nothing)
   ;; CHECK-NEXT:      (local.get $x)
   ;; CHECK-NEXT:     )
@@ -231,11 +226,11 @@
   ;; CHECK-NEXT:  (block $x (result i32)
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (br_if $x
-  ;; CHECK-NEXT:     (block $block (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (call $nothing)
   ;; CHECK-NEXT:      (i32.const 100)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (block $block2 (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (call $nothing)
   ;; CHECK-NEXT:      (local.get $x)
   ;; CHECK-NEXT:     )
@@ -270,7 +265,7 @@
   ;; CHECK-NEXT:  (block $x (result i32)
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (br_if $x
-  ;; CHECK-NEXT:     (block $block (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (call $nothing)
   ;; CHECK-NEXT:      (i32.const 100)
   ;; CHECK-NEXT:     )
@@ -303,7 +298,7 @@
   ;; CHECK-NEXT:  (block $x (result i32)
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (br_if $x
-  ;; CHECK-NEXT:     (block $block (result i32)
+  ;; CHECK-NEXT:     (block (result i32)
   ;; CHECK-NEXT:      (call $nothing)
   ;; CHECK-NEXT:      (i32.const 100)
   ;; CHECK-NEXT:     )
diff --git a/test/lit/passes/remove-unused-module-elements-refs.wast b/test/lit/passes/remove-unused-module-elements-refs.wast
index 6d408eb..2c9df16 100644
--- a/test/lit/passes/remove-unused-module-elements-refs.wast
+++ b/test/lit/passes/remove-unused-module-elements-refs.wast
@@ -4,6 +4,8 @@
 (module
   ;; CHECK:      (type $A (func_subtype func))
   (type $A (func))
+  ;; CHECK:      (type $ref?|$A|_=>_none (func_subtype (param (ref null $A)) func))
+
   ;; CHECK:      (type $B (func_subtype func))
   (type $B (func))
 
@@ -11,21 +13,24 @@
 
   ;; CHECK:      (export "foo" (func $foo))
 
-  ;; CHECK:      (func $foo (type $A)
+  ;; CHECK:      (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-B)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:  (call_ref $A
+  ;; CHECK-NEXT:   (local.get $A)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $foo (export "foo")
+  (func $foo (export "foo") (param $A (ref null $A))
     ;; This export has two RefFuncs, and one CallRef.
     (drop
       (ref.func $target-A)
@@ -33,12 +38,12 @@
     (drop
       (ref.func $target-B)
     )
-    (call_ref
-      (ref.null $A)
+    (call_ref $A
+      (local.get $A)
     )
     ;; Verify that we do not crash on an unreachable call_ref, which has no
     ;; heap type for us to analyze.
-    (call_ref
+    (call_ref $A
       (unreachable)
     )
   )
@@ -80,15 +85,18 @@
   ;; CHECK:      (export "foo" (func $foo))
 
   ;; CHECK:      (func $foo (type $A)
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:  (block ;; (replaces something unreachable we can't emit)
+  ;; CHECK-NEXT:   (drop
+  ;; CHECK-NEXT:    (ref.null nofunc)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $foo (export "foo")
-    (call_ref
+    (call_ref $A
       (ref.null $A)
     )
     (drop
@@ -97,7 +105,7 @@
   )
 
   ;; CHECK:      (func $target-A (type $A)
-  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (unreachable)
   ;; CHECK-NEXT: )
   (func $target-A (type $A)
     ;; This function is reachable.
@@ -114,33 +122,35 @@
   (type $A (func))
   (type $B (func))
 
+  ;; CHECK:      (type $ref?|$A|_=>_none (func_subtype (param (ref null $A)) func))
+
   ;; CHECK:      (elem declare func $target-A-1 $target-A-2)
 
   ;; CHECK:      (export "foo" (func $foo))
 
-  ;; CHECK:      (func $foo (type $A)
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK:      (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+  ;; CHECK-NEXT:  (call_ref $A
+  ;; CHECK-NEXT:   (local.get $A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-A-1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:  (call_ref $A
+  ;; CHECK-NEXT:   (local.get $A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-A-2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $foo (export "foo")
-    (call_ref
-      (ref.null $A)
+  (func $foo (export "foo") (param $A (ref null $A))
+    (call_ref $A
+      (local.get $A)
     )
     (drop
       (ref.func $target-A-1)
     )
-    (call_ref
-      (ref.null $A)
+    (call_ref $A
+      (local.get $A)
     )
     (drop
       (ref.func $target-A-2)
@@ -173,36 +183,38 @@
   (type $A (func))
   (type $B (func))
 
+  ;; CHECK:      (type $ref?|$A|_=>_none (func_subtype (param (ref null $A)) func))
+
   ;; CHECK:      (elem declare func $target-A-1 $target-A-2)
 
   ;; CHECK:      (export "foo" (func $foo))
 
-  ;; CHECK:      (func $foo (type $A)
+  ;; CHECK:      (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-A-1)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:  (call_ref $A
+  ;; CHECK-NEXT:   (local.get $A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-A-2)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:  (call_ref $A
+  ;; CHECK-NEXT:   (local.get $A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $foo (export "foo")
+  (func $foo (export "foo") (param $A (ref null $A))
     (drop
       (ref.func $target-A-1)
     )
-    (call_ref
-      (ref.null $A)
+    (call_ref $A
+      (local.get $A)
     )
     (drop
       (ref.func $target-A-2)
     )
-    (call_ref
-      (ref.null $A)
+    (call_ref $A
+      (local.get $A)
     )
   )
 
@@ -286,6 +298,8 @@
 
   ;; CHECK:      (type $funcref_=>_none (func_subtype (param funcref) func))
 
+  ;; CHECK:      (type $ref?|$A|_=>_none (func_subtype (param (ref null $A)) func))
+
   ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref)))
   (import "binaryen-intrinsics" "call.without.effects"
     (func $call-without-effects (param funcref)))
@@ -298,9 +312,9 @@
 
   ;; CHECK:      (export "foo" (func $foo))
 
-  ;; CHECK:      (func $foo (type $A)
+  ;; CHECK:      (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
   ;; CHECK-NEXT:  (call $call-without-effects
-  ;; CHECK-NEXT:   (ref.null $A)
+  ;; CHECK-NEXT:   (local.get $A)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (ref.func $target-keep)
@@ -309,12 +323,12 @@
   ;; CHECK-NEXT:   (ref.func $target-keep-2)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $foo (export "foo")
+  (func $foo (export "foo") (param $A (ref null $A))
     ;; Call the intrinsic without a RefFunc. All we infer here is the type,
     ;; which means we must assume anything with type $A (and a reference) can be
     ;; called, which will keep alive both $target-keep and $target-keep-2
     (call $call-without-effects
-      (ref.null $A)
+      (local.get $A)
     )
     (drop
       (ref.func $target-keep)
diff --git a/test/lit/passes/reorder-globals.wast b/test/lit/passes/reorder-globals.wast
new file mode 100644
index 0000000..bdfd03d
--- /dev/null
+++ b/test/lit/passes/reorder-globals.wast
@@ -0,0 +1,294 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: foreach %s %t wasm-opt --reorder-globals-always -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --reorder-globals-always --roundtrip -S -o - | filecheck %s
+
+;; Also check roundtripping here, so verify we don't end up emitting invalid
+;; binaries.
+
+;; Global $b has more uses, so it should be sorted first.
+(module
+
+  ;; CHECK:      (global $b i32 (i32.const 20))
+
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+  (global $b i32 (i32.const 20))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+  )
+)
+
+;; As above, but now with global.sets. Again $b should be sorted first.
+(module
+
+  ;; CHECK:      (global $b (mut i32) (i32.const 20))
+
+  ;; CHECK:      (global $a (mut i32) (i32.const 10))
+  (global $a (mut i32) (i32.const 10))
+  (global $b (mut i32) (i32.const 20))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (global.set $b
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $b
+  ;; CHECK-NEXT:   (i32.const 40)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $a)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (global.set $b
+      (i32.const 30)
+    )
+    (global.set $b
+      (i32.const 40)
+    )
+    (drop
+      (global.get $a)
+    )
+  )
+)
+
+;; As above, but flipped so now $a has more, and should remain first.
+(module
+  ;; CHECK:      (global $a (mut i32) (i32.const 10))
+  (global $a (mut i32) (i32.const 10))
+  ;; CHECK:      (global $b (mut i32) (i32.const 20))
+  (global $b (mut i32) (i32.const 20))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (global.set $a
+  ;; CHECK-NEXT:   (i32.const 30)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (global.set $a
+  ;; CHECK-NEXT:   (i32.const 40)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (global.set $a
+      (i32.const 30)
+    )
+    (global.set $a
+      (i32.const 40)
+    )
+    (drop
+      (global.get $b)
+    )
+  )
+)
+
+;; $b has more uses, but it depends on $a and cannot be sorted before it.
+(module
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+  ;; CHECK:      (global $b i32 (global.get $a))
+  (global $b i32 (global.get $a))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+  )
+)
+
+;; $c has more uses, but it depends on $b and $a and cannot be sorted before
+;; them. Likewise $b cannot be before $a.
+(module
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+  ;; CHECK:      (global $b i32 (global.get $a))
+  (global $b i32 (global.get $a))
+  ;; CHECK:      (global $c i32 (global.get $b))
+  (global $c i32 (global.get $b))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+    (drop
+      (global.get $c)
+    )
+    (drop
+      (global.get $c)
+    )
+  )
+)
+
+;; As above, but without dependencies, so now $c is first and then $b.
+(module
+
+
+  ;; CHECK:      (global $c i32 (i32.const 30))
+
+  ;; CHECK:      (global $b i32 (i32.const 20))
+
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+  (global $b i32 (i32.const 20))
+  (global $c i32 (i32.const 30))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+    (drop
+      (global.get $c)
+    )
+    (drop
+      (global.get $c)
+    )
+  )
+)
+
+;; As above, but a mixed case: $b depends on $a but $c has no dependencies. $c
+;; can be first.
+(module
+
+  ;; CHECK:      (global $c i32 (i32.const 30))
+
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+  ;; CHECK:      (global $b i32 (global.get $a))
+  (global $b i32 (global.get $a))
+  (global $c i32 (i32.const 30))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+    (drop
+      (global.get $c)
+    )
+    (drop
+      (global.get $c)
+    )
+  )
+)
+
+;; Another mixed case, now with $c depending on $b. $b can be before $a.
+(module
+
+
+  ;; CHECK:      (global $b i32 (i32.const 20))
+
+  ;; CHECK:      (global $c i32 (global.get $b))
+
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+  (global $b i32 (i32.const 20))
+  (global $c i32 (global.get $b))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $c)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+    (drop
+      (global.get $c)
+    )
+    (drop
+      (global.get $c)
+    )
+  )
+)
+
+;; $b has more uses, but $a is an import and must remain first.
+(module
+  ;; CHECK:      (import "a" "b" (global $a i32))
+  (import "a" "b" (global $a i32))
+  ;; CHECK:      (global $b i32 (i32.const 10))
+  (global $b i32 (i32.const 10))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $b)
+    )
+  )
+)
+
+;; As above, but with a and b's names flipped, to check that the names do not
+;; matter, and we keep imports first.
+(module
+  ;; CHECK:      (import "a" "b" (global $b i32))
+  (import "a" "b" (global $b i32))
+
+  ;; CHECK:      (global $a i32 (i32.const 10))
+  (global $a i32 (i32.const 10))
+
+  ;; CHECK:      (func $uses
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (global.get $a)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $uses
+    (drop
+      (global.get $a)
+    )
+  )
+)
diff --git a/test/lit/passes/roundtrip-gc-types.wast b/test/lit/passes/roundtrip-gc-types.wast
index d14415f..acbfea3 100644
--- a/test/lit/passes/roundtrip-gc-types.wast
+++ b/test/lit/passes/roundtrip-gc-types.wast
@@ -21,16 +21,13 @@
  ;; CHECK:      (type $D (struct (field (ref $C)) (field (ref $A))))
  ;; NOMNL:      (type $D (struct_subtype (field (ref $C)) (field (ref $A)) $A))
  (type $D (struct_subtype (field (ref $C)) (field (ref $A)) $A))
- ;; CHECK:      (global $g0 (rtt 0 $A) (rtt.canon $A))
- ;; NOMNL:      (global $g0 (rtt 0 $A) (rtt.canon $A))
- (global $g0 (rtt 0 $A) (rtt.canon $A))
- ;; CHECK:      (global $g1 (rtt 1 $D) (rtt.sub $D
- ;; CHECK-NEXT:  (global.get $g0)
- ;; CHECK-NEXT: ))
- ;; NOMNL:      (global $g1 (rtt 1 $D) (rtt.sub $D
- ;; NOMNL-NEXT:  (global.get $g0)
- ;; NOMNL-NEXT: ))
- (global $g1 (rtt 1 $D) (rtt.sub $D
-  (global.get $g0)
- ))
+ ;; CHECK:      (func $use-types (param $0 (ref $A)) (param $1 (ref $D))
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ ;; NOMNL:      (func $use-types (type $ref|$A|_ref|$D|_=>_none) (param $0 (ref $A)) (param $1 (ref $D))
+ ;; NOMNL-NEXT:  (nop)
+ ;; NOMNL-NEXT: )
+ (func $use-types (param (ref $A) (ref $D))
+  (nop)
+ )
 )
diff --git a/test/lit/passes/roundtrip-gc.wast b/test/lit/passes/roundtrip-gc.wast
index 2fb5d57..2692b7b 100644
--- a/test/lit/passes/roundtrip-gc.wast
+++ b/test/lit/passes/roundtrip-gc.wast
@@ -8,44 +8,51 @@
  ;; NOMNL:      (export "export" (func $test))
  (export "export" (func $test))
  ;; CHECK:      (func $test
+ ;; CHECK-NEXT:  (local $0 (ref $\7bi32\7d))
  ;; CHECK-NEXT:  (call $help
- ;; CHECK-NEXT:   (rtt.canon $\7bi32\7d)
- ;; CHECK-NEXT:   (block $label$1 (result i32)
+ ;; CHECK-NEXT:   (block (result (ref $\7bi32\7d))
+ ;; CHECK-NEXT:    (local.set $0
+ ;; CHECK-NEXT:     (struct.new_default $\7bi32\7d)
+ ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (nop)
- ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; NOMNL:      (func $test (type $none_=>_none)
+ ;; NOMNL-NEXT:  (local $0 (ref $\7bi32\7d))
  ;; NOMNL-NEXT:  (call $help
- ;; NOMNL-NEXT:   (rtt.canon $\7bi32\7d)
- ;; NOMNL-NEXT:   (block $label$1 (result i32)
+ ;; NOMNL-NEXT:   (block (result (ref $\7bi32\7d))
+ ;; NOMNL-NEXT:    (local.set $0
+ ;; NOMNL-NEXT:     (struct.new_default $\7bi32\7d)
+ ;; NOMNL-NEXT:    )
  ;; NOMNL-NEXT:    (nop)
- ;; NOMNL-NEXT:    (i32.const 1)
+ ;; NOMNL-NEXT:    (local.get $0)
  ;; NOMNL-NEXT:   )
+ ;; NOMNL-NEXT:   (i32.const 1)
  ;; NOMNL-NEXT:  )
  ;; NOMNL-NEXT: )
  (func $test
   (call $help
-   (rtt.canon ${i32})
+   (struct.new_default ${i32})
    ;; Stack IR optimizations can remove this block, leaving a nop in an odd
-   ;; "stacky" location. On load, we would normally use a local to work around
-   ;; that, creating a block to contain the rtt before us and the nop, and then
-   ;; returning the local. But we can't use a local for an rtt, so we should not
-   ;; optimize this sort of thing in stack IR.
-   (block (result i32)
+   ;; "stacky" location. On load, we will use a local to work around that. It
+   ;; is fine for the local to be non-nullable since the get is later in that
+   ;; same block.
+   (block $block (result i32)
     (nop)
     (i32.const 1)
    )
   )
  )
- ;; CHECK:      (func $help (param $3 (rtt $\7bi32\7d)) (param $4 i32)
+ ;; CHECK:      (func $help (param $3 (ref $\7bi32\7d)) (param $4 i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
- ;; NOMNL:      (func $help (type $rtt_$\7bi32\7d_i32_=>_none) (param $3 (rtt $\7bi32\7d)) (param $4 i32)
+ ;; NOMNL:      (func $help (type $ref|$\7bi32\7d|_i32_=>_none) (param $3 (ref $\7bi32\7d)) (param $4 i32)
  ;; NOMNL-NEXT:  (nop)
  ;; NOMNL-NEXT: )
- (func $help (param $3 (rtt ${i32})) (param $4 i32)
+ (func $help (param $3 (ref ${i32})) (param $4 i32)
   (nop)
  )
 )
diff --git a/test/lit/passes/roundtrip.wast b/test/lit/passes/roundtrip.wast
index 3eca456..b23819b 100644
--- a/test/lit/passes/roundtrip.wast
+++ b/test/lit/passes/roundtrip.wast
@@ -10,7 +10,7 @@
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (block $label$1 (result funcref (ref $none))
  ;; CHECK-NEXT:    (tuple.make
- ;; CHECK-NEXT:     (ref.null func)
+ ;; CHECK-NEXT:     (ref.null nofunc)
  ;; CHECK-NEXT:     (ref.func $foo)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
@@ -36,7 +36,7 @@
  (func $foo
   (drop
    ;; a tuple type with a non-nullable element, that must be carefully handled
-   (block (result funcref (ref $none))
+   (block $block (result funcref (ref $none))
     (tuple.make
      (ref.null func)
      (ref.func $foo)
diff --git a/test/lit/passes/rse-gc.wast b/test/lit/passes/rse-gc.wast
index ddb4aaf..71d9bae 100644
--- a/test/lit/passes/rse-gc.wast
+++ b/test/lit/passes/rse-gc.wast
@@ -1,7 +1,16 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --rse --enable-gc-nn-locals -all -S -o - | filecheck %s
+;; RUN: wasm-opt %s --rse -all -S -o - | filecheck %s
 
 (module
+ ;; CHECK:      (type $B (struct (field (ref data))))
+
+ ;; CHECK:      (type $A (struct (field dataref)))
+ (type $A (struct_subtype (field (ref null data)) data))
+
+ ;; $B is a subtype of $A, and its field has a more refined type (it is non-
+ ;; nullable).
+ (type $B (struct_subtype (field (ref data)) $A))
+
  ;; CHECK:      (func $test
  ;; CHECK-NEXT:  (local $single (ref func))
  ;; CHECK-NEXT:  (local $tuple ((ref any) (ref any)))
@@ -15,4 +24,228 @@
   ;; A non-nullable tuple.
   (local $tuple ((ref any) (ref any)))
  )
+
+ ;; CHECK:      (func $needs-refinalize (param $b (ref $B)) (result anyref)
+ ;; CHECK-NEXT:  (local $a (ref null $A))
+ ;; CHECK-NEXT:  (local.set $a
+ ;; CHECK-NEXT:   (local.get $b)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (struct.get $B 0
+ ;; CHECK-NEXT:   (local.get $b)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $needs-refinalize (param $b (ref $B)) (result anyref)
+  (local $a (ref null $A))
+  ;; Make $a contain $b.
+  (local.set $a
+    (local.get $b)
+  )
+  (struct.get $A 0
+   ;; Once more, make $a contain $b. This set is redundant. After removing it,
+   ;; the struct.get will be reading from type $B, which has a more refined
+   ;; field, so we must refinalize to get the right type for the instruction.
+   (local.tee $a
+    (local.get $b)
+   )
+  )
+ )
+
+ ;; CHECK:      (func $pick-refined (param $A (ref null $A)) (param $x i32)
+ ;; CHECK-NEXT:  (local $B (ref null $B))
+ ;; CHECK-NEXT:  (local.set $B
+ ;; CHECK-NEXT:   (ref.cast_static $B
+ ;; CHECK-NEXT:    (local.get $A)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (if
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:   (drop
+ ;; CHECK-NEXT:    (local.get $B)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (drop
+ ;; CHECK-NEXT:    (local.get $B)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $pick-refined (param $A (ref null $A)) (param $x i32)
+  (local $B (ref null $B))
+  (local.set $B
+   (ref.cast_static $B
+    (local.get $A)
+   )
+  )
+  ;; All these can refer to $B, the more refined type, even in branching and
+  ;; merging control flow later.
+  (drop
+   (local.get $A)
+  )
+  (drop
+   (local.get $B)
+  )
+  (if
+   (local.get $x)
+   (drop
+    (local.get $A)
+   )
+   (drop
+    (local.get $B)
+   )
+  )
+  (drop
+   (local.get $A)
+  )
+  (drop
+   (local.get $B)
+  )
+ )
+
+ ;; CHECK:      (func $pick-refined-nn (param $A (ref $A))
+ ;; CHECK-NEXT:  (local $B (ref $B))
+ ;; CHECK-NEXT:  (local.set $B
+ ;; CHECK-NEXT:   (ref.cast_static $B
+ ;; CHECK-NEXT:    (local.get $A)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $pick-refined-nn (param $A (ref $A))
+  (local $B (ref $B))
+  ;; As above, but now the types are both non-nullable. We should still switch
+  ;; to $B.
+  (local.set $B
+   (ref.cast_static $B
+    (local.get $A)
+   )
+  )
+  (drop
+   (local.get $A)
+  )
+  (drop
+   (local.get $B)
+  )
+ )
+
+ ;; CHECK:      (func $avoid-unrefined (param $A (ref $A))
+ ;; CHECK-NEXT:  (local $B (ref null $B))
+ ;; CHECK-NEXT:  (local.set $B
+ ;; CHECK-NEXT:   (ref.cast_static $B
+ ;; CHECK-NEXT:    (local.get $A)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $A)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $B)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $avoid-unrefined (param $A (ref $A))
+  (local $B (ref null $B))
+  ;; As above, but now the local is nullable. Since the parameter is non-
+  ;; nullable, that means neither is a subtype of the other, and we will make
+  ;; no changes.
+  (local.set $B
+   (ref.cast_static $B
+    (local.get $A)
+   )
+  )
+  (drop
+   (local.get $A)
+  )
+  (drop
+   (local.get $B)
+  )
+ )
+
+ ;; CHECK:      (func $pick-refined-earlier (param $A (ref $A))
+ ;; CHECK-NEXT:  (local $A2 (ref null $A))
+ ;; CHECK-NEXT:  (local.set $A2
+ ;; CHECK-NEXT:   (local.get $A)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $A)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $A)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $pick-refined-earlier (param $A (ref $A))
+  ;; As above but now the local has the same heap type but is nullable. Now we
+  ;; prefer the non-nullable parameter.
+  (local $A2 (ref null $A))
+  (local.set $A2
+   (local.get $A)
+  )
+  (drop
+   (local.get $A)
+  )
+  (drop
+   (local.get $A2)
+  )
+ )
+
+ ;; CHECK:      (func $different-choices (param $non-nullable (ref $A))
+ ;; CHECK-NEXT:  (local $nullable (ref null $A))
+ ;; CHECK-NEXT:  (local.set $nullable
+ ;; CHECK-NEXT:   (local.get $non-nullable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $non-nullable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (local.set $nullable
+ ;; CHECK-NEXT:   (ref.null none)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $nullable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (local.set $nullable
+ ;; CHECK-NEXT:   (local.get $non-nullable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $non-nullable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $different-choices (param $non-nullable (ref $A))
+  (local $nullable (ref null $A))
+  (local.set $nullable
+   (local.get $non-nullable)
+  )
+  ;; Here we can switch to the non-nullable one.
+  (drop
+   (local.get $nullable)
+  )
+
+  (local.set $nullable
+   (ref.null $A)
+  )
+  ;; Here we cannot.
+  (drop
+   (local.get $nullable)
+  )
+
+  (local.set $nullable
+   (local.get $non-nullable)
+  )
+  ;; Here we can switch once more.
+  (drop
+   (local.get $nullable)
+  )
+ )
 )
diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast
index 3c4500e..4b3cfa0 100644
--- a/test/lit/passes/signature-pruning.wast
+++ b/test/lit/passes/signature-pruning.wast
@@ -43,7 +43,7 @@
   ;; CHECK-NEXT:   (i32.const 0)
   ;; CHECK-NEXT:   (f64.const 3)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (i32.const 4)
   ;; CHECK-NEXT:   (f64.const 7)
   ;; CHECK-NEXT:   (ref.func $foo)
@@ -56,7 +56,7 @@
       (f32.const 2)
       (f64.const 3)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 4)
       (i64.const 5)
       (f32.const 6)
@@ -107,7 +107,7 @@
   ;; CHECK-NEXT:   (i64.const 1)
   ;; CHECK-NEXT:   (f32.const 2)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (i64.const 5)
   ;; CHECK-NEXT:   (f32.const 6)
   ;; CHECK-NEXT:   (ref.func $foo)
@@ -120,7 +120,7 @@
       (f32.const 2)
       (f64.const 3)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 4)
       (i64.const 5)
       (f32.const 6)
@@ -167,14 +167,14 @@
 
   ;; CHECK:      (func $caller (type $none_=>_none)
   ;; CHECK-NEXT:  (call $foo
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (call $caller)
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (i64.const 1)
   ;; CHECK-NEXT:   (f32.const 2)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (i32.const 4)
   ;; CHECK-NEXT:   (i64.const 5)
   ;; CHECK-NEXT:   (f32.const 6)
@@ -194,7 +194,7 @@
       (f32.const 2)
       (f64.const 3)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 4)
       (i64.const 5)
       (f32.const 6)
@@ -246,8 +246,8 @@
   ;; CHECK-NEXT:   (i64.const 1)
   ;; CHECK-NEXT:   (f32.const 2)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:  (call_ref $sig
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (call $caller)
   ;; CHECK-NEXT:    (i32.const 4)
   ;; CHECK-NEXT:   )
@@ -263,7 +263,7 @@
       (f32.const 2)
       (f64.const 3)
     )
-    (call_ref
+    (call_ref $sig
       (block (result i32)
         (call $caller)
         (i32.const 4)
@@ -301,7 +301,7 @@
 
   ;; CHECK:      (func $caller (type $none_=>_none)
   ;; CHECK-NEXT:  (call $foo)
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (ref.func $foo)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -312,7 +312,7 @@
       (f32.const 2)
       (f64.const 3)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 4)
       (i64.const 5)
       (f32.const 6)
@@ -501,10 +501,10 @@
   ;; CHECK:      (func $caller (type $none_=>_none)
   ;; CHECK-NEXT:  (call $foo)
   ;; CHECK-NEXT:  (call $bar)
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (ref.func $foo)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (ref.func $bar)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -515,11 +515,11 @@
     (call $bar
       (i32.const 1)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 2)
       (ref.func $foo)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 2)
       (ref.func $bar)
     )
@@ -527,7 +527,7 @@
 
   ;; CHECK:      (func $caller-2 (type $none_=>_none)
   ;; CHECK-NEXT:  (call $bar)
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (ref.func $foo)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
@@ -536,7 +536,7 @@
     (call $bar
       (i32.const 1)
     )
-    (call_ref
+    (call_ref $sig
       (i32.const 2)
       (ref.func $foo)
     )
@@ -747,12 +747,10 @@
 
   ;; CHECK:      (memory $0 1 1)
 
-  ;; CHECK:      (elem declare func $foo)
-
   ;; CHECK:      (func $foo (type $sig-foo)
   ;; CHECK-NEXT:  (local $0 anyref)
   ;; CHECK-NEXT:  (local.set $0
-  ;; CHECK-NEXT:   (ref.null any)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (drop
@@ -765,7 +763,7 @@
   (func $foo (type $sig-foo) (param $anyref anyref)
     (drop (local.get $anyref))
     (call $foo (ref.null any))
-    (call $foo (ref.null func))
+    (call $foo (ref.null data))
   )
 
   ;; CHECK:      (func $bar (type $sig-bar) (param $anyref anyref)
@@ -773,17 +771,19 @@
   ;; CHECK-NEXT:   (local.get $anyref)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (call $bar
-  ;; CHECK-NEXT:   (ref.func $foo)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (call $bar
-  ;; CHECK-NEXT:   (ref.null func)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $bar (type $sig-bar) (param $anyref anyref)
     (drop (local.get $anyref))
     ;; Mixing a null with something else prevents optimization, of course.
-    (call $bar (ref.func $foo))
-    (call $bar (ref.null func))
+    (call $bar (i31.new (i32.const 0)))
+    (call $bar (ref.null data))
   )
 )
 
@@ -810,3 +810,41 @@
     )
   )
 )
+
+;; Do not prune signatures used in the call.without.effects intrinsic.
+(module
+  ;; CHECK:      (type $i32_funcref_=>_i32 (func_subtype (param i32 funcref) (result i32) func))
+
+  ;; CHECK:      (type $i32_=>_i32 (func_subtype (param i32) (result i32) func))
+
+  ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+  ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param i32 funcref) (result i32)))
+  (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param i32 funcref) (result i32)))
+
+  ;; CHECK:      (elem declare func $func)
+
+  ;; CHECK:      (func $func (type $i32_=>_i32) (param $0 i32) (result i32)
+  ;; CHECK-NEXT:  (i32.const 1)
+  ;; CHECK-NEXT: )
+  (func $func (param i32) (result i32)
+    ;; The parameter is unused, so we want to prune it. We won't because of the
+    ;; situation in the calling function, below.
+    (i32.const 1)
+  )
+
+  ;; CHECK:      (func $caller (type $none_=>_i32) (result i32)
+  ;; CHECK-NEXT:  (call $cwe
+  ;; CHECK-NEXT:   (i32.const 41)
+  ;; CHECK-NEXT:   (ref.func $func)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $caller (result i32)
+    ;; We call $func using call.without.effects. This causes us to not optimize
+    ;; in this case.
+    (call $cwe
+      (i32.const 41)
+      (ref.func $func)
+    )
+  )
+)
diff --git a/test/lit/passes/signature-refining-isorecursive.wast b/test/lit/passes/signature-refining-isorecursive.wast
new file mode 100644
index 0000000..d1fef1c
--- /dev/null
+++ b/test/lit/passes/signature-refining-isorecursive.wast
@@ -0,0 +1,207 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --hybrid --signature-refining -all -S -o - | filecheck %s
+
+(module
+ ;; The signature should be refined to a single self-referential type.
+
+ ;; CHECK:      (type $refined (func_subtype (param (ref $refined)) (result (ref $refined)) func))
+ (type $refined (func (param (ref $refined)) (result (ref $refined))))
+
+ ;; CHECK:      (elem declare func $foo)
+
+ ;; CHECK:      (func $foo (type $refined) (param $0 (ref $refined)) (result (ref $refined))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $foo
+ ;; CHECK-NEXT:    (ref.func $foo)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $foo)
+ ;; CHECK-NEXT: )
+ (func $foo (param funcref) (result funcref)
+  (drop
+   (call $foo
+    (ref.func $foo)
+   )
+  )
+  (ref.func $foo)
+ )
+)
+
+(module
+ ;; The signatures should be refined to a pair of mutually self-referential types.
+
+ ;; CHECK:      (rec
+ ;; CHECK-NEXT:  (type $0 (func_subtype (param i32 (ref $0)) (result (ref $1)) func))
+ (type $0 (func (param i32 funcref) (result funcref)))
+ ;; CHECK:       (type $1 (func_subtype (param f32 (ref $1)) (result (ref $0)) func))
+ (type $1 (func (param f32 funcref) (result funcref)))
+
+
+ ;; CHECK:      (elem declare func $bar $foo)
+
+ ;; CHECK:      (func $foo (type $0) (param $0 i32) (param $1 (ref $0)) (result (ref $1))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $foo
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:    (ref.func $foo)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $bar)
+ ;; CHECK-NEXT: )
+ (func $foo (type $0) (param i32 funcref) (result funcref)
+  (drop
+   (call $foo
+    (i32.const 0)
+    (ref.func $foo)
+   )
+  )
+  (ref.func $bar)
+ )
+
+ ;; CHECK:      (func $bar (type $1) (param $0 f32) (param $1 (ref $1)) (result (ref $0))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $bar
+ ;; CHECK-NEXT:    (f32.const 0)
+ ;; CHECK-NEXT:    (ref.func $bar)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $foo)
+ ;; CHECK-NEXT: )
+ (func $bar (type $1) (param f32 funcref) (result funcref)
+  (drop
+   (call $bar
+    (f32.const 0)
+    (ref.func $bar)
+   )
+  )
+  (ref.func $foo)
+ )
+)
+
+(module
+ ;; The signatures start out as separate types, so they must remain separate
+ ;; even though they are refined to the same structure.
+
+ (rec
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $0 (func_subtype (param (ref $0)) func))
+  (type $0 (func (param funcref)))
+  ;; CHECK:       (type $1 (func_subtype (param (ref $0)) func))
+  (type $1 (func (param funcref)))
+ )
+
+ ;; CHECK:      (elem declare func $foo)
+
+ ;; CHECK:      (func $foo (type $0) (param $0 (ref $0))
+ ;; CHECK-NEXT:  (call $foo
+ ;; CHECK-NEXT:   (ref.func $foo)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $foo (type $0) (param funcref)
+  (call $foo
+   (ref.func $foo)
+  )
+ )
+
+ ;; CHECK:      (func $bar (type $1) (param $0 (ref $0))
+ ;; CHECK-NEXT:  (call $bar
+ ;; CHECK-NEXT:   (ref.func $foo)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $bar (type $1) (param funcref)
+  (call $bar
+   (ref.func $foo)
+  )
+ )
+)
+
+(module
+ ;; The signatures should be refined to a pair of mutually recursive types and
+ ;; another type that refers to them.
+
+ ;; CHECK:      (rec
+ ;; CHECK-NEXT:  (type $1 (func_subtype (param f32 (ref $1)) (result (ref $0)) func))
+
+ ;; CHECK:       (type $0 (func_subtype (param i32 (ref $0)) (result (ref $1)) func))
+ (type $0 (func (param i32 funcref) (result funcref)))
+
+ (type $1 (func (param f32 funcref) (result funcref)))
+
+ ;; CHECK:       (type $2 (func_subtype (param (ref $0)) (result (ref $1)) func))
+ (type $2 (func (param funcref) (result funcref)))
+
+
+ ;; CHECK:      (elem declare func $bar $foo)
+
+ ;; CHECK:      (func $foo (type $0) (param $0 i32) (param $1 (ref $0)) (result (ref $1))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $foo
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:    (ref.func $foo)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $bar)
+ ;; CHECK-NEXT: )
+ (func $foo (param i32 funcref) (result funcref)
+  (drop
+   (call $foo
+    (i32.const 0)
+    (ref.func $foo)
+   )
+  )
+  (ref.func $bar)
+ )
+
+ ;; CHECK:      (func $baz (type $2) (param $0 (ref $0)) (result (ref $1))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $quux
+ ;; CHECK-NEXT:    (ref.func $foo)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $bar)
+ ;; CHECK-NEXT: )
+ (func $baz (param funcref) (result funcref)
+  (drop
+   (call $quux
+    (ref.func $foo)
+   )
+  )
+  (ref.func $bar)
+ )
+
+ ;; CHECK:      (func $bar (type $1) (param $0 f32) (param $1 (ref $1)) (result (ref $0))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $bar
+ ;; CHECK-NEXT:    (f32.const 0)
+ ;; CHECK-NEXT:    (ref.func $bar)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $foo)
+ ;; CHECK-NEXT: )
+ (func $bar (param f32 funcref) (result funcref)
+  (drop
+   (call $bar
+    (f32.const 0)
+    (ref.func $bar)
+   )
+  )
+  (ref.func $foo)
+ )
+
+ ;; CHECK:      (func $quux (type $2) (param $0 (ref $0)) (result (ref $1))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (call $baz
+ ;; CHECK-NEXT:    (ref.func $foo)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (ref.func $bar)
+ ;; CHECK-NEXT: )
+ (func $quux (param funcref) (result funcref)
+  (drop
+   (call $baz
+    (ref.func $foo)
+   )
+  )
+  (ref.func $bar)
+ )
+)
diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast
index 19558d9..8313a75 100644
--- a/test/lit/passes/signature-refining.wast
+++ b/test/lit/passes/signature-refining.wast
@@ -36,10 +36,11 @@
 (module
   ;; As above, but the call is via call_ref.
 
+  ;; CHECK:      (type $sig (func_subtype (param (ref $struct)) func))
+
   ;; CHECK:      (type $struct (struct_subtype  data))
   (type $struct (struct_subtype data))
 
-  ;; CHECK:      (type $sig (func_subtype (param (ref $struct)) func))
   (type $sig (func_subtype (param anyref) func))
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
@@ -53,13 +54,13 @@
   )
 
   ;; CHECK:      (func $caller (type $none_=>_none)
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:   (ref.func $func)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $caller
-    (call_ref
+    (call_ref $sig
       (struct.new $struct)
       (ref.func $func)
     )
@@ -71,17 +72,18 @@
   ;; call uses a nullable $struct, the other a non-nullable dataref, so the LUB
   ;; is a nullable dataref.
 
+  ;; CHECK:      (type $sig (func_subtype (param dataref) func))
+
   ;; CHECK:      (type $struct (struct_subtype  data))
   (type $struct (struct_subtype data))
 
-  ;; CHECK:      (type $sig (func_subtype (param (ref null data)) func))
   (type $sig (func_subtype (param anyref) func))
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
   ;; CHECK:      (elem declare func $func)
 
-  ;; CHECK:      (func $func (type $sig) (param $x (ref null data))
+  ;; CHECK:      (func $func (type $sig) (param $x dataref)
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT: )
   (func $func (type $sig) (param $x anyref)
@@ -92,7 +94,7 @@
   ;; CHECK-NEXT:  (call $func
   ;; CHECK-NEXT:   (local.get $struct)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (ref.as_data
   ;; CHECK-NEXT:    (struct.new_default $struct)
   ;; CHECK-NEXT:   )
@@ -105,7 +107,7 @@
       ;; Use a local to avoid a ref.null being updated.
       (local.get $struct)
     )
-    (call_ref
+    (call_ref $sig
       (ref.as_data
         (struct.new $struct)
       )
@@ -203,8 +205,8 @@
   ;; Define a field in the struct of the signature type that will be updated,
   ;; to check for proper validation after the update.
 
-  ;; CHECK:      (type $sig (func_subtype (param (ref $struct)) func))
-  (type $sig (func_subtype (param anyref) func))
+  ;; CHECK:      (type $sig (func_subtype (param (ref $struct) (ref $sig)) func))
+  (type $sig (func_subtype (param anyref funcref) func))
 
   ;; CHECK:      (type $struct (struct_subtype (field (ref $sig)) data))
   (type $struct (struct_subtype (field (ref $sig)) data))
@@ -213,33 +215,33 @@
 
   ;; CHECK:      (elem declare func $func)
 
-  ;; CHECK:      (func $func (type $sig) (param $x (ref $struct))
+  ;; CHECK:      (func $func (type $sig) (param $x (ref $struct)) (param $f (ref $sig))
   ;; CHECK-NEXT:  (local $temp (ref null $sig))
-  ;; CHECK-NEXT:  (local $2 anyref)
-  ;; CHECK-NEXT:  (local.set $2
-  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  (local $3 funcref)
+  ;; CHECK-NEXT:  (local.set $3
+  ;; CHECK-NEXT:   (local.get $f)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (block
   ;; CHECK-NEXT:   (drop
-  ;; CHECK-NEXT:    (local.get $2)
+  ;; CHECK-NEXT:    (local.get $x)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (local.set $2
+  ;; CHECK-NEXT:   (local.set $3
   ;; CHECK-NEXT:    (local.get $temp)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $func (type $sig) (param $x anyref)
+  (func $func (type $sig) (param $x anyref) (param $f funcref)
     ;; Define a local of the signature type that is updated.
     (local $temp (ref null $sig))
     ;; Do a local.get of the param, to verify its type is valid.
     (drop
       (local.get $x)
     )
-    ;; Copy between the param and the local, to verify their types are still
-    ;; compatible after the update. Note that we will need to add a fixup local
-    ;; here, as $x's new type becomes too specific to be assigned the value
-    ;; here.
-    (local.set $x
+    ;; Copy from a funcref local to the formerly funcref param to verify their
+    ;; types are still compatible after the update. Note that we will need to
+    ;; add a fixup local here, as $f's new type becomes too specific to be
+    ;; assigned the value here.
+    (local.set $f
       (local.get $temp)
     )
   )
@@ -249,6 +251,7 @@
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (ref.func $func)
   ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (ref.func $func)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $caller
@@ -256,6 +259,7 @@
       (struct.new $struct
         (ref.func $func)
       )
+      (ref.func $func)
     )
   )
 )
@@ -264,10 +268,11 @@
   ;; An unreachable value does not prevent optimization: we will update the
   ;; param to be $struct.
 
+  ;; CHECK:      (type $sig (func_subtype (param (ref $struct)) func))
+
   ;; CHECK:      (type $struct (struct_subtype  data))
   (type $struct (struct_subtype data))
 
-  ;; CHECK:      (type $sig (func_subtype (param (ref $struct)) func))
   (type $sig (func_subtype (param anyref) func))
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
@@ -284,7 +289,7 @@
   ;; CHECK-NEXT:  (call $func
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:   (ref.func $func)
   ;; CHECK-NEXT:  )
@@ -293,7 +298,7 @@
     (call $func
       (struct.new $struct)
     )
-    (call_ref
+    (call_ref $sig
       (unreachable)
       (ref.func $func)
     )
@@ -320,13 +325,13 @@
   )
 
   ;; CHECK:      (func $caller (type $none_=>_none)
-  ;; CHECK-NEXT:  (call_ref
+  ;; CHECK-NEXT:  (call_ref $sig
   ;; CHECK-NEXT:   (unreachable)
   ;; CHECK-NEXT:   (ref.func $func)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $caller
-    (call_ref
+    (call_ref $sig
       (unreachable)
       (ref.func $func)
     )
@@ -354,22 +359,23 @@
   ;; CHECK:      (type $struct (struct_subtype  data))
   (type $struct (struct_subtype data))
 
-  ;; CHECK:      (type $sig-1 (func_subtype (param (ref null data) anyref) func))
+  ;; CHECK:      (type $sig-2 (func_subtype (param eqref (ref $struct)) func))
+
+  ;; CHECK:      (type $sig-1 (func_subtype (param dataref anyref) func))
   (type $sig-1 (func_subtype (param anyref) (param anyref) func))
-  ;; CHECK:      (type $sig-2 (func_subtype (param anyref (ref $struct)) func))
   (type $sig-2 (func_subtype (param anyref) (param anyref) func))
 
   ;; CHECK:      (type $none_=>_none (func_subtype func))
 
   ;; CHECK:      (elem declare func $func-2)
 
-  ;; CHECK:      (func $func-1 (type $sig-1) (param $x (ref null data)) (param $y anyref)
+  ;; CHECK:      (func $func-1 (type $sig-1) (param $x dataref) (param $y anyref)
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT: )
   (func $func-1 (type $sig-1) (param $x anyref) (param $y anyref)
   )
 
-  ;; CHECK:      (func $func-2 (type $sig-2) (param $x anyref) (param $y (ref $struct))
+  ;; CHECK:      (func $func-2 (type $sig-2) (param $x eqref) (param $y (ref $struct))
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT: )
   (func $func-2 (type $sig-2) (param $x anyref) (param $y anyref)
@@ -377,8 +383,8 @@
 
   ;; CHECK:      (func $caller (type $none_=>_none)
   ;; CHECK-NEXT:  (local $any anyref)
-  ;; CHECK-NEXT:  (local $data (ref null data))
-  ;; CHECK-NEXT:  (local $func funcref)
+  ;; CHECK-NEXT:  (local $data dataref)
+  ;; CHECK-NEXT:  (local $i31 i31ref)
   ;; CHECK-NEXT:  (call $func-1
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:   (local.get $data)
@@ -391,8 +397,8 @@
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (call_ref
-  ;; CHECK-NEXT:   (local.get $func)
+  ;; CHECK-NEXT:  (call_ref $sig-2
+  ;; CHECK-NEXT:   (local.get $i31)
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:   (ref.func $func-2)
   ;; CHECK-NEXT:  )
@@ -400,7 +406,7 @@
   (func $caller
     (local $any (ref null any))
     (local $data (ref null data))
-    (local $func (ref null func))
+    (local $i31 (ref null i31))
 
     (call $func-1
       (struct.new $struct)
@@ -414,8 +420,8 @@
       (struct.new $struct)
       (struct.new $struct)
     )
-    (call_ref
-      (local.get $func)
+    (call_ref $sig-2
+      (local.get $i31)
       (struct.new $struct)
       (ref.func $func-2)
     )
@@ -479,7 +485,7 @@
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (call $func
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $caller
@@ -521,7 +527,7 @@
   )
 
   ;; CHECK:      (func $func-cannot-refine (type $sig-cannot-refine) (result anyref)
-  ;; CHECK-NEXT:  (ref.null any)
+  ;; CHECK-NEXT:  (ref.null none)
   ;; CHECK-NEXT: )
   (func $func-cannot-refine (type $sig-cannot-refine) (result anyref)
     (ref.null any)
@@ -545,7 +551,7 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (if (result (ref $struct))
   ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:    (call_ref
+  ;; CHECK-NEXT:    (call_ref $sig-can-refine
   ;; CHECK-NEXT:     (ref.func $func-can-refine)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (unreachable)
@@ -567,7 +573,7 @@
     (drop
       (if (result anyref)
         (i32.const 1)
-        (call_ref
+        (call_ref $sig-can-refine
           (ref.func $func-can-refine)
         )
         (unreachable)
@@ -577,12 +583,13 @@
 )
 
 (module
+  ;; CHECK:      (type $sig (func_subtype (result (ref null $struct)) func))
+
   ;; CHECK:      (type $struct (struct_subtype  data))
   (type $struct (struct_subtype data))
 
   ;; This signature has multiple functions using it, and some of them have nulls
   ;; which should be updated when we refine.
-  ;; CHECK:      (type $sig (func_subtype (result (ref null $struct)) func))
   (type $sig (func_subtype (result anyref) func))
 
   ;; CHECK:      (func $func-1 (type $sig) (result (ref null $struct))
@@ -593,14 +600,14 @@
   )
 
   ;; CHECK:      (func $func-2 (type $sig) (result (ref null $struct))
-  ;; CHECK-NEXT:  (ref.null $struct)
+  ;; CHECK-NEXT:  (ref.null none)
   ;; CHECK-NEXT: )
   (func $func-2 (type $sig) (result anyref)
     (ref.null any)
   )
 
   ;; CHECK:      (func $func-3 (type $sig) (result (ref null $struct))
-  ;; CHECK-NEXT:  (ref.null $struct)
+  ;; CHECK-NEXT:  (ref.null none)
   ;; CHECK-NEXT: )
   (func $func-3 (type $sig) (result anyref)
     (ref.null eq)
@@ -610,7 +617,7 @@
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (i32.const 1)
   ;; CHECK-NEXT:   (return
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (unreachable)
@@ -674,3 +681,74 @@
     (nop)
   )
 )
+
+(module
+  ;; CHECK:      (type $ref|${}|_i32_=>_none (func_subtype (param (ref ${}) i32) func))
+
+  ;; CHECK:      (type ${} (struct_subtype  data))
+  (type ${} (struct_subtype data))
+
+  ;; CHECK:      (func $foo (type $ref|${}|_i32_=>_none) (param $ref (ref ${})) (param $i32 i32)
+  ;; CHECK-NEXT:  (local $2 eqref)
+  ;; CHECK-NEXT:  (local.set $2
+  ;; CHECK-NEXT:   (local.get $ref)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (block
+  ;; CHECK-NEXT:   (call $foo
+  ;; CHECK-NEXT:    (block
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.set $2
+  ;; CHECK-NEXT:    (ref.null none)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $foo (param $ref eqref) (param $i32 i32)
+    (call $foo
+      ;; The only reference to the ${} type is in this block signature. Even
+      ;; this will go away in the internal ReFinalize (which makes the block
+      ;; type unreachable).
+      (block (result (ref ${}))
+        (unreachable)
+      )
+      (i32.const 0)
+    )
+    ;; Write something of type eqref into $ref. When we refine the type of the
+    ;; parameter from eqref to ${} we must do something here, as we can no
+    ;; longer just write this (ref.null eq) into a parameter of the more
+    ;; refined type. While doing so, we must not be confused by the fact that
+    ;; the only mention of ${} in the original module gets removed during our
+    ;; processing, as mentioned in the earlier comment. This is a regression
+    ;; test for a crash because of that.
+    (local.set $ref
+      (ref.null eq)
+    )
+  )
+)
+
+;; Do not modify the types used on imported functions (until the spec and VM
+;; support becomes stable).
+(module
+  ;; CHECK:      (type $dataref_=>_none (func_subtype (param dataref) func))
+
+  ;; CHECK:      (type $none_=>_none (func_subtype func))
+
+  ;; CHECK:      (type $struct (struct_subtype  data))
+  (type $struct (struct_subtype data))
+
+  ;; CHECK:      (import "a" "b" (func $import (param dataref)))
+  (import "a" "b" (func $import (param (ref null data))))
+
+  ;; CHECK:      (func $test (type $none_=>_none)
+  ;; CHECK-NEXT:  (call $import
+  ;; CHECK-NEXT:   (struct.new_default $struct)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test
+    (call $import
+      (struct.new $struct)
+    )
+  )
+)
diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat
new file mode 100644
index 0000000..183ce7d
--- /dev/null
+++ b/test/lit/passes/signature-refining_gto.wat
@@ -0,0 +1,45 @@
+;; RUN: foreach %s %t wasm-opt --nominal --signature-refining --gto --roundtrip -all -S -o - | filecheck %s
+
+(module
+ ;; The type $A should not be emitted at all (see below).
+ ;; CHECK-NOT: (type $A
+ (type $A (struct_subtype (field (mut (ref null $A))) data))
+
+ ;; CHECK:      (type $ref|none|_=>_none (func_subtype (param (ref none)) func))
+
+ ;; CHECK:      (type $funcref_i32_=>_none (func_subtype (param funcref i32) func))
+
+ ;; CHECK:      (func $struct.get (type $ref|none|_=>_none) (param $0 (ref none))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $struct.get (param $0 (ref $A))
+  ;; This function is always called with a null, so the parameter type will be
+  ;; refined to that. After doing so, the struct.get will be reading from a
+  ;; null type, and we should avoid erroring on that in --gto which scans all
+  ;; the heap types and updates them. Likewise, we should not error during
+  ;; roundtrip which also scans heap types.
+  (drop
+   (struct.get $A 0
+    (local.get $0)
+   )
+  )
+ )
+
+ ;; CHECK:      (func $caller (type $funcref_i32_=>_none) (param $0 funcref) (param $1 i32)
+ ;; CHECK-NEXT:  (call $struct.get
+ ;; CHECK-NEXT:   (ref.as_non_null
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $caller (param $0 funcref) (param $1 i32)
+  (call $struct.get
+   (ref.as_non_null
+    (ref.null none)
+   )
+  )
+ )
+)
diff --git a/test/lit/passes/signext-lowering.wast b/test/lit/passes/signext-lowering.wast
new file mode 100644
index 0000000..f17479e
--- /dev/null
+++ b/test/lit/passes/signext-lowering.wast
@@ -0,0 +1,66 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt %s --signext-lowering --enable-sign-ext -S -o - | filecheck %s
+
+(module
+ ;; CHECK:      (type $0 (func))
+ (type $0 (func))
+ ;; CHECK:      (func $signext
+ ;; CHECK-NEXT:  (local $0 i32)
+ ;; CHECK-NEXT:  (local $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.shr_s
+ ;; CHECK-NEXT:    (i32.shl
+ ;; CHECK-NEXT:     (local.get $0)
+ ;; CHECK-NEXT:     (i32.const 24)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (i32.const 24)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.shr_s
+ ;; CHECK-NEXT:    (i32.shl
+ ;; CHECK-NEXT:     (local.get $0)
+ ;; CHECK-NEXT:     (i32.const 16)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (i32.const 16)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.shr_s
+ ;; CHECK-NEXT:    (i64.shl
+ ;; CHECK-NEXT:     (local.get $1)
+ ;; CHECK-NEXT:     (i64.const 56)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (i64.const 56)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.shr_s
+ ;; CHECK-NEXT:    (i64.shl
+ ;; CHECK-NEXT:     (local.get $1)
+ ;; CHECK-NEXT:     (i64.const 48)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (i64.const 48)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.shr_s
+ ;; CHECK-NEXT:    (i64.shl
+ ;; CHECK-NEXT:     (local.get $1)
+ ;; CHECK-NEXT:     (i64.const 32)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (i64.const 32)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $signext  (type $0)
+  (local $0 i32)
+  (local $1 i64)
+  (drop (i32.extend8_s (local.get $0)))
+  (drop (i32.extend16_s (local.get $0)))
+  (drop (i64.extend8_s (local.get $1)))
+  (drop (i64.extend16_s (local.get $1)))
+  (drop (i64.extend32_s (local.get $1)))
+ )
+)
diff --git a/test/lit/passes/simplify-globals-non-init.wast b/test/lit/passes/simplify-globals-non-init.wast
index 9e32303..9210335 100644
--- a/test/lit/passes/simplify-globals-non-init.wast
+++ b/test/lit/passes/simplify-globals-non-init.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --simplify-globals --enable-mutable-globals -S -o - | filecheck %s
 
diff --git a/test/lit/passes/simplify-globals-read_only_to_write.wast b/test/lit/passes/simplify-globals-read_only_to_write.wast
index 149dd2d..db4738b 100644
--- a/test/lit/passes/simplify-globals-read_only_to_write.wast
+++ b/test/lit/passes/simplify-globals-read_only_to_write.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --simplify-globals -S -o - | filecheck %s
 
@@ -28,7 +28,7 @@
   ;; CHECK-NEXT:   (i32.eqz
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (nop)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 1)
@@ -121,7 +121,7 @@
   ;; CHECK:      (func $side-effects-in-body
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (global.get $global)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (global.set $global
   ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
@@ -159,19 +159,17 @@
   ;; CHECK:      (func $nested
   ;; CHECK-NEXT:  (if
   ;; CHECK-NEXT:   (i32.const 0)
-  ;; CHECK-NEXT:   (block $block
+  ;; CHECK-NEXT:   (block
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (i32.const 0)
-  ;; CHECK-NEXT:     (block $block1
+  ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (if
   ;; CHECK-NEXT:       (i32.const 0)
-  ;; CHECK-NEXT:       (block $block3
-  ;; CHECK-NEXT:        (drop
-  ;; CHECK-NEXT:         (i32.const 2)
-  ;; CHECK-NEXT:        )
+  ;; CHECK-NEXT:       (drop
+  ;; CHECK-NEXT:        (i32.const 2)
   ;; CHECK-NEXT:       )
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (drop
@@ -551,7 +549,7 @@
   ;; CHECK-NEXT:  (local $x i32)
   ;; CHECK-NEXT:  (local $y i32)
   ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (i32.eqz
   ;; CHECK-NEXT:      (i32.const 0)
@@ -611,7 +609,7 @@
   ;; CHECK-NEXT:  (local $x i32)
   ;; CHECK-NEXT:  (local $y i32)
   ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (i32.eqz
   ;; CHECK-NEXT:      (global.get $once)
@@ -673,10 +671,10 @@
   ;; CHECK-NEXT:  (local $x i32)
   ;; CHECK-NEXT:  (local $y i32)
   ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (if
   ;; CHECK-NEXT:     (i32.eqz
-  ;; CHECK-NEXT:      (block $block1 (result i32)
+  ;; CHECK-NEXT:      (block (result i32)
   ;; CHECK-NEXT:       (if
   ;; CHECK-NEXT:        (i32.const 0)
   ;; CHECK-NEXT:        (drop
@@ -748,7 +746,7 @@
   ;; CHECK-NEXT:  (local $x i32)
   ;; CHECK-NEXT:  (local $y i32)
   ;; CHECK-NEXT:  (if
-  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:   (block (result i32)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (global.get $once)
   ;; CHECK-NEXT:    )
diff --git a/test/lit/passes/simplify-locals-gc-nn.wast b/test/lit/passes/simplify-locals-gc-nn.wast
new file mode 100644
index 0000000..c7e4b63
--- /dev/null
+++ b/test/lit/passes/simplify-locals-gc-nn.wast
@@ -0,0 +1,92 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (func $test-nn
+  ;; CHECK-NEXT:  (local $nn anyref)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (try $try
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (local.set $nn
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (ref.null none)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch_all
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (local.get $nn)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test-nn
+    (local $nn (ref any))
+    ;; We can sink this set into the try, but the spec does not allow it to
+    ;; remain non-nullable. Even though we are not changing dominance (we are
+    ;; not changing it, because there is nothing that can throw in the try's
+    ;; body that can reach the catch_all before the local.set that we move
+    ;; there). See
+    ;; https://github.com/WebAssembly/function-references/issues/44#issuecomment-1083146887
+    (local.set $nn
+      (ref.as_non_null
+        (ref.null any)
+      )
+    )
+    (try
+      (do
+        (drop
+          (local.get $nn)
+        )
+      )
+      (catch_all
+        (drop
+          (local.get $nn)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $test-nullable
+  ;; CHECK-NEXT:  (local $nullable anyref)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (try $try
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (local.set $nullable
+  ;; CHECK-NEXT:     (ref.as_non_null
+  ;; CHECK-NEXT:      (ref.null none)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch_all
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (local.get $nullable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test-nullable
+    ;; As above, but now the local is nullable. Here we can optimize the set
+    ;; into the try, with no other necessary changes.
+    (local $nullable (ref null any))
+    (local.set $nullable
+      (ref.as_non_null
+        (ref.null any)
+      )
+    )
+    (try
+      (do
+        (drop
+          (local.get $nullable)
+        )
+      )
+      (catch_all
+        (drop
+          (local.get $nullable)
+        )
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/simplify-locals-gc-validation.wast b/test/lit/passes/simplify-locals-gc-validation.wast
new file mode 100644
index 0000000..0587303
--- /dev/null
+++ b/test/lit/passes/simplify-locals-gc-validation.wast
@@ -0,0 +1,48 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s
+
+;; Tests for validation of non-nullable locals. In this form a local.set allows
+;; a local.get until the end of the current block.
+
+(module
+  ;; CHECK:      (func $test-nn (param $x (ref any))
+  ;; CHECK-NEXT:  (local $nn anyref)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (block $inner
+  ;; CHECK-NEXT:   (call $test-nn
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (local.tee $nn
+  ;; CHECK-NEXT:      (ref.as_non_null
+  ;; CHECK-NEXT:       (ref.null none)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $test-nn
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.get $nn)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $test-nn (param $x (ref any))
+    (local $nn (ref any))
+    ;; We can sink this set into the block, but we should then update things so
+    ;; that we still validate, as then the final local.get is not structurally
+    ;; dominated. (Note that we end up with several ref.as_non_nulls here, but
+    ;; later passes could remove them.)
+    (local.set $nn
+      (ref.as_non_null
+        (ref.null any)
+      )
+    )
+    (block $inner
+      (call $test-nn
+        (local.get $nn)
+      )
+    )
+    (call $test-nn
+      (local.get $nn)
+    )
+  )
+)
diff --git a/test/lit/passes/simplify-locals-gc.wast b/test/lit/passes/simplify-locals-gc.wast
index 33e4d1b..80bbe6d 100644
--- a/test/lit/passes/simplify-locals-gc.wast
+++ b/test/lit/passes/simplify-locals-gc.wast
@@ -9,10 +9,24 @@
   ;; NOMNL:      (type $struct (struct_subtype (field (mut i32)) data))
   (type $struct (struct (field (mut i32))))
 
+  ;; CHECK:      (type $B (struct (field (ref data))))
+
+  ;; CHECK:      (type $A (struct (field dataref)))
+
   ;; CHECK:      (type $struct-immutable (struct (field i32)))
+  ;; NOMNL:      (type $A (struct_subtype (field dataref) data))
+
+  ;; NOMNL:      (type $B (struct_subtype (field (ref data)) $A))
+
   ;; NOMNL:      (type $struct-immutable (struct_subtype (field i32) data))
   (type $struct-immutable (struct (field i32)))
 
+  (type $A (struct_subtype (field (ref null data)) data))
+
+  ;; $B is a subtype of $A, and its field has a more refined type (it is non-
+  ;; nullable).
+  (type $B (struct_subtype (field (ref data)) $A))
+
   ;; Writes to heap objects cannot be reordered with reads.
   ;; CHECK:      (func $no-reorder-past-write (param $x (ref $struct)) (result i32)
   ;; CHECK-NEXT:  (local $temp i32)
@@ -97,6 +111,7 @@
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
@@ -112,6 +127,7 @@
   ;; NOMNL-NEXT:    (drop
   ;; NOMNL-NEXT:     (unreachable)
   ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:    (unreachable)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (struct.set $struct 0
@@ -143,15 +159,15 @@
   ;; CHECK-NEXT:  (block $block
   ;; CHECK-NEXT:   (drop
   ;; CHECK-NEXT:    (br_on_null $block
-  ;; CHECK-NEXT:     (ref.null any)
+  ;; CHECK-NEXT:     (ref.null none)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (local.set $temp
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (br $block)
   ;; CHECK-NEXT:   (local.set $temp
-  ;; CHECK-NEXT:    (ref.null any)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
@@ -165,15 +181,15 @@
   ;; NOMNL-NEXT:  (block $block
   ;; NOMNL-NEXT:   (drop
   ;; NOMNL-NEXT:    (br_on_null $block
-  ;; NOMNL-NEXT:     (ref.null any)
+  ;; NOMNL-NEXT:     (ref.null none)
   ;; NOMNL-NEXT:    )
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (local.set $temp
-  ;; NOMNL-NEXT:    (ref.null any)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:   (br $block)
   ;; NOMNL-NEXT:   (local.set $temp
-  ;; NOMNL-NEXT:    (ref.null any)
+  ;; NOMNL-NEXT:    (ref.null none)
   ;; NOMNL-NEXT:   )
   ;; NOMNL-NEXT:  )
   ;; NOMNL-NEXT:  (drop
@@ -214,4 +230,476 @@
     )
    )
   )
+
+  ;; CHECK:      (func $if-nnl
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $if-nnl)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $helper
+  ;; CHECK-NEXT:   (local.tee $x
+  ;; CHECK-NEXT:    (ref.func $if-nnl)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $helper
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $if-nnl (type $none_=>_none)
+  ;; NOMNL-NEXT:  (local $x (ref func))
+  ;; NOMNL-NEXT:  (if
+  ;; NOMNL-NEXT:   (i32.const 1)
+  ;; NOMNL-NEXT:   (local.set $x
+  ;; NOMNL-NEXT:    (ref.func $if-nnl)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call $helper
+  ;; NOMNL-NEXT:   (local.tee $x
+  ;; NOMNL-NEXT:    (ref.func $if-nnl)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call $helper
+  ;; NOMNL-NEXT:   (local.get $x)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $if-nnl
+   (local $x (ref func))
+   ;; We want to turn this if into an if-else with a set on the outside:
+   ;;
+   ;;  (local.set $x
+   ;;   (if
+   ;;    (i32.const 1)
+   ;;    (ref.func $if-nnl)
+   ;;    (local.get $x)))
+   ;;
+   ;; That will not validate, however (no set dominates the get), so we'll get
+   ;; fixed up by adding a ref.as_non_null. But that may be dangerous - if no
+   ;; set exists before us, then that new instruction will trap, in fact. So we
+   ;; do not optimize here.
+   (if
+    (i32.const 1)
+    (local.set $x
+     (ref.func $if-nnl)
+    )
+   )
+   ;; An exta set + gets, just to avoid other optimizations kicking in
+   ;; (without them, the function only has a set and nothing else, and will
+   ;; remove the set entirely). Nothing should change here.
+   (call $helper
+    (local.tee $x
+     (ref.func $if-nnl)
+    )
+   )
+   (call $helper
+    (local.get $x)
+   )
+  )
+
+  ;; CHECK:      (func $if-nnl-previous-set
+  ;; CHECK-NEXT:  (local $x (ref func))
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (ref.func $if-nnl)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:   (local.set $x
+  ;; CHECK-NEXT:    (ref.func $if-nnl)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $helper
+  ;; CHECK-NEXT:   (local.tee $x
+  ;; CHECK-NEXT:    (ref.func $if-nnl)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $helper
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $if-nnl-previous-set (type $none_=>_none)
+  ;; NOMNL-NEXT:  (local $x (ref func))
+  ;; NOMNL-NEXT:  (local.set $x
+  ;; NOMNL-NEXT:   (ref.func $if-nnl)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (if
+  ;; NOMNL-NEXT:   (i32.const 1)
+  ;; NOMNL-NEXT:   (local.set $x
+  ;; NOMNL-NEXT:    (ref.func $if-nnl)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call $helper
+  ;; NOMNL-NEXT:   (local.tee $x
+  ;; NOMNL-NEXT:    (ref.func $if-nnl)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call $helper
+  ;; NOMNL-NEXT:   (local.get $x)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $if-nnl-previous-set
+   (local $x (ref func))
+   ;; As the above testcase, but now there is a set before the if. We could
+   ;; optimize in this case, but don't atm. TODO
+   (local.set $x
+    (ref.func $if-nnl)
+   )
+   (if
+    (i32.const 1)
+    (local.set $x
+     (ref.func $if-nnl)
+    )
+   )
+   (call $helper
+    (local.tee $x
+     (ref.func $if-nnl)
+    )
+   )
+   (call $helper
+    (local.get $x)
+   )
+  )
+
+  ;; CHECK:      (func $helper (param $ref (ref func))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $helper (type $ref|func|_=>_none) (param $ref (ref func))
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT: )
+  (func $helper (param $ref (ref func))
+  )
+
+  ;; CHECK:      (func $needs-refinalize (param $b (ref $B)) (result anyref)
+  ;; CHECK-NEXT:  (local $a (ref null $A))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (struct.get $B 0
+  ;; CHECK-NEXT:   (local.get $b)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $needs-refinalize (type $ref|$B|_=>_anyref) (param $b (ref $B)) (result anyref)
+  ;; NOMNL-NEXT:  (local $a (ref null $A))
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (struct.get $B 0
+  ;; NOMNL-NEXT:   (local.get $b)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $needs-refinalize (param $b (ref $B)) (result anyref)
+    (local $a (ref null $A))
+    (local.set $a
+      (local.get $b)
+    )
+    ;; This begins as a struct.get of $A, but after we move the set's value onto
+    ;; the get, we'll be reading from $B. $B's field has a more refined type, so
+    ;; we must update the type of the struct.get using refinalize.
+    (struct.get $A 0
+      (local.get $a)
+    )
+  )
+
+  ;; CHECK:      (func $call-vs-mutable-read (param $0 (ref $struct)) (result i32)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (call $side-effect)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $struct 0
+  ;; CHECK-NEXT:    (local.get $0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.get $temp)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $call-vs-mutable-read (type $ref|$struct|_=>_i32) (param $0 (ref $struct)) (result i32)
+  ;; NOMNL-NEXT:  (local $temp i32)
+  ;; NOMNL-NEXT:  (local.set $temp
+  ;; NOMNL-NEXT:   (call $side-effect)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (struct.get $struct 0
+  ;; NOMNL-NEXT:    (local.get $0)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (local.get $temp)
+  ;; NOMNL-NEXT: )
+  (func $call-vs-mutable-read (param $0 (ref $struct)) (result i32)
+    (local $temp i32)
+    (local.set $temp
+      ;; This call may have arbitrary side effects, for all we know, as we
+      ;; optimize this function using --simplify-locals.
+      (call $side-effect)
+    )
+    (drop
+      ;; This reads a mutable field, which means the call might modify it.
+      (struct.get $struct 0
+        (local.get $0)
+      )
+    )
+    ;; We should not move the call to here!
+    (local.get $temp)
+  )
+
+  ;; CHECK:      (func $side-effect (result i32)
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $side-effect (type $none_=>_i32) (result i32)
+  ;; NOMNL-NEXT:  (unreachable)
+  ;; NOMNL-NEXT: )
+  (func $side-effect (result i32)
+    ;; Helper function for the above.
+    (unreachable)
+  )
+
+  ;; CHECK:      (func $pick-refined (param $nn-any (ref any)) (result anyref)
+  ;; CHECK-NEXT:  (local $any anyref)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (call $use-any
+  ;; CHECK-NEXT:   (local.get $nn-any)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $use-nn-any
+  ;; CHECK-NEXT:   (local.get $nn-any)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (local.get $nn-any)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $pick-refined (type $ref|any|_=>_anyref) (param $nn-any (ref any)) (result anyref)
+  ;; NOMNL-NEXT:  (local $any anyref)
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (call $use-any
+  ;; NOMNL-NEXT:   (local.get $nn-any)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call $use-nn-any
+  ;; NOMNL-NEXT:   (local.get $nn-any)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (local.get $nn-any)
+  ;; NOMNL-NEXT: )
+  (func $pick-refined (param $nn-any (ref any)) (result anyref)
+    (local $any anyref)
+    (local.set $any
+      (local.get $nn-any)
+    )
+    ;; Use the locals so neither is trivially removed.
+    (call $use-any
+      (local.get $any)
+    )
+    (call $use-nn-any
+      (local.get $nn-any)
+    )
+    ;; This copy is not needed, as they hold the same value.
+    (local.set $any
+      (local.get $nn-any)
+    )
+    ;; This local.get might as well use the non-nullable local, which is more
+    ;; refined. In fact, all uses of locals can be switched to that one in the
+    ;; entire function (and the other local would be removed by other passes).
+    (local.get $any)
+  )
+
+  ;; CHECK:      (func $pick-casted (param $any anyref) (result anyref)
+  ;; CHECK-NEXT:  (local $nn-any (ref any))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (call $use-any
+  ;; CHECK-NEXT:   (local.tee $nn-any
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (local.get $any)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (call $use-nn-any
+  ;; CHECK-NEXT:   (local.get $nn-any)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (local.get $nn-any)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $pick-casted (type $anyref_=>_anyref) (param $any anyref) (result anyref)
+  ;; NOMNL-NEXT:  (local $nn-any (ref any))
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (call $use-any
+  ;; NOMNL-NEXT:   (local.tee $nn-any
+  ;; NOMNL-NEXT:    (ref.as_non_null
+  ;; NOMNL-NEXT:     (local.get $any)
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (call $use-nn-any
+  ;; NOMNL-NEXT:   (local.get $nn-any)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (local.get $nn-any)
+  ;; NOMNL-NEXT: )
+  (func $pick-casted (param $any anyref) (result anyref)
+    (local $nn-any (ref any))
+    (local.set $nn-any
+      (ref.as_non_null
+        (local.get $any)
+      )
+    )
+    ;; Use the locals so neither is trivially removed.
+    (call $use-any
+      (local.get $any)
+    )
+    (call $use-nn-any
+      (local.get $nn-any)
+    )
+    ;; This copy is not needed, as they hold the same value.
+    (local.set $any
+      (local.get $nn-any)
+    )
+    ;; This local.get might as well use the non-nullable local.
+    (local.get $any)
+  )
+
+  ;; CHECK:      (func $pick-fallthrough (param $x i32)
+  ;; CHECK-NEXT:  (local $t i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (block (result i32)
+  ;; CHECK-NEXT:    (local.get $x)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $pick-fallthrough (type $i32_=>_none) (param $x i32)
+  ;; NOMNL-NEXT:  (local $t i32)
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (local.get $x)
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (block (result i32)
+  ;; NOMNL-NEXT:    (local.get $x)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $pick-fallthrough (param $x i32)
+    (local $t i32)
+    ;; Similar to the above test wth looking through a cast, but using a non-gc
+    ;; type of fallthrough value.
+    (local.set $t
+      (block (result i32)
+        (local.get $x)
+      )
+    )
+    ;; The locals are identical, as we set $t = $x (we can look through to the
+    ;; block value). Both these gets can go to $x, and we do not need to set $t
+    ;; as it will have 0 uses.
+    (drop
+      (local.get $x)
+    )
+    (drop
+      (local.get $t)
+    )
+  )
+
+  ;; CHECK:      (func $ignore-unrefined (param $A (ref $A))
+  ;; CHECK-NEXT:  (local $B (ref null $B))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (local.get $A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $B 0
+  ;; CHECK-NEXT:    (local.tee $B
+  ;; CHECK-NEXT:     (ref.cast_static $B
+  ;; CHECK-NEXT:      (local.get $A)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $A 0
+  ;; CHECK-NEXT:    (local.get $A)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.get $B 0
+  ;; CHECK-NEXT:    (local.get $B)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $ignore-unrefined (type $ref|$A|_=>_none) (param $A (ref $A))
+  ;; NOMNL-NEXT:  (local $B (ref null $B))
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (struct.get $A 0
+  ;; NOMNL-NEXT:    (local.get $A)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (struct.get $B 0
+  ;; NOMNL-NEXT:    (local.tee $B
+  ;; NOMNL-NEXT:     (ref.cast_static $B
+  ;; NOMNL-NEXT:      (local.get $A)
+  ;; NOMNL-NEXT:     )
+  ;; NOMNL-NEXT:    )
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (struct.get $A 0
+  ;; NOMNL-NEXT:    (local.get $A)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT:  (drop
+  ;; NOMNL-NEXT:   (struct.get $B 0
+  ;; NOMNL-NEXT:    (local.get $B)
+  ;; NOMNL-NEXT:   )
+  ;; NOMNL-NEXT:  )
+  ;; NOMNL-NEXT: )
+  (func $ignore-unrefined (param $A (ref $A))
+    ;; $A is a supertype, but non-nullable; $B is a subtype, but nullable. We
+    ;; should not switch any of the gets from $B to $A: that would improve
+    ;; nullability but not the heap type.
+    (local $B (ref null $B))
+    (local.set $B
+      (ref.cast_static $B
+        (local.get $A)
+      )
+    )
+    ;; Read from both locals a few times. We should keep reading from the same
+    ;; locals as before.
+    (drop
+      (struct.get $A 0
+        (local.get $A)
+      )
+    )
+    (drop
+      (struct.get $B 0
+        (local.get $B)
+      )
+    )
+    (drop
+      (struct.get $A 0
+        (local.get $A)
+      )
+    )
+    (drop
+      (struct.get $B 0
+        (local.get $B)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $use-nn-any (param $nn-any (ref any))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $use-nn-any (type $ref|any|_=>_none) (param $nn-any (ref any))
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT: )
+  (func $use-nn-any (param $nn-any (ref any))
+    ;; Helper function for the above.
+  )
+
+  ;; CHECK:      (func $use-any (param $any anyref)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  ;; NOMNL:      (func $use-any (type $anyref_=>_none) (param $any anyref)
+  ;; NOMNL-NEXT:  (nop)
+  ;; NOMNL-NEXT: )
+  (func $use-any (param $any anyref)
+    ;; Helper function for the above.
+  )
 )
diff --git a/test/lit/passes/simplify-locals-strings.wast b/test/lit/passes/simplify-locals-strings.wast
new file mode 100644
index 0000000..1dd28ef
--- /dev/null
+++ b/test/lit/passes/simplify-locals-strings.wast
@@ -0,0 +1,549 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals -all -S -o - \
+;; RUN:   | filecheck %s
+
+(module
+  (memory 10 10)
+
+  ;; CHECK:      (type $array (array (mut i8)))
+  (type $array (array_subtype (mut i8) data))
+  ;; CHECK:      (type $array16 (array (mut i16)))
+  (type $array16 (array_subtype (mut i16) data))
+
+  ;; CHECK:      (func $no-new-past-store
+  ;; CHECK-NEXT:  (local $temp stringref)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf8 utf8
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf8 wtf8
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf8 replace
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf16
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (i32.store
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-new-past-store
+    (local $temp stringref)
+    ;; A string.new cannot be moved past a memory store.
+    (local.set $temp
+      (string.new_wtf8 utf8
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (i32.store
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (string.new_wtf8 wtf8
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (i32.store
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (string.new_wtf8 replace
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (i32.store
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (string.new_wtf16
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (i32.store
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+  )
+
+  ;; CHECK:      (func $yes-new-past-store
+  ;; CHECK-NEXT:  (local $temp stringref)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8 utf8
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $yes-new-past-store
+    (local $temp stringref)
+    ;; A string.new can be moved past a memory load.
+    (local.set $temp
+      (string.new_wtf8 utf8
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (drop
+      (i32.load
+        (i32.const 3)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+  )
+
+  ;; CHECK:      (func $no-new-past-store-gc (param $array (ref $array)) (param $array16 (ref $array16))
+  ;; CHECK-NEXT:  (local $temp stringref)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf8_array utf8
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.set $array
+  ;; CHECK-NEXT:   (local.get $array)
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf8_array wtf8
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.set $array
+  ;; CHECK-NEXT:   (local.get $array)
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf8_array replace
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.set $array
+  ;; CHECK-NEXT:   (local.get $array)
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (string.new_wtf16_array
+  ;; CHECK-NEXT:    (local.get $array16)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (array.set $array
+  ;; CHECK-NEXT:   (local.get $array)
+  ;; CHECK-NEXT:   (i32.const 3)
+  ;; CHECK-NEXT:   (i32.const 4)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-new-past-store-gc (param $array (ref $array)) (param $array16 (ref $array16))
+    (local $temp stringref)
+    ;; A string.new_***_array cannot be moved past a GC store.
+    (local.set $temp
+      (string.new_wtf8_array utf8
+        (local.get $array)
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (array.set $array
+      (local.get $array)
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (string.new_wtf8_array wtf8
+        (local.get $array)
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (array.set $array
+      (local.get $array)
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (string.new_wtf8_array replace
+        (local.get $array)
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (array.set $array
+      (local.get $array)
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (string.new_wtf16_array
+        (local.get $array16)
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (array.set $array
+      (local.get $array)
+      (i32.const 3)
+      (i32.const 4)
+    )
+    (drop
+      (local.get $temp)
+    )
+  )
+
+  ;; CHECK:      (func $no-load-past-encode (param $ref stringref)
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf8 wtf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf8 utf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (i32.load
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf16
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-load-past-encode (param $ref stringref)
+    (local $temp i32)
+    ;; string.encode writes to memory, so a load can't be moved past it.
+    (local.set $temp
+      (i32.load
+        (i32.const 1)
+      )
+    )
+    (drop
+      (string.encode_wtf8 wtf8
+        (local.get $ref)
+        (i32.const 10)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (i32.load
+        (i32.const 1)
+      )
+    )
+    (drop
+      (string.encode_wtf8 utf8
+        (local.get $ref)
+        (i32.const 20)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (i32.load
+        (i32.const 1)
+      )
+    )
+    (drop
+      (string.encode_wtf16
+        (local.get $ref)
+        (i32.const 30)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+  )
+
+  ;; CHECK:      (func $no-load-past-encode-gc (param $ref stringref) (param $array (ref $array)) (param $array16 (ref $array16))
+  ;; CHECK-NEXT:  (local $temp i32)
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (array.get_u $array
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf8_array wtf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 10)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (array.get_u $array
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf8_array utf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $temp
+  ;; CHECK-NEXT:   (array.get_u $array
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf16_array
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (local.get $array16)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $temp)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-load-past-encode-gc (param $ref stringref) (param $array (ref $array)) (param $array16 (ref $array16))
+    (local $temp i32)
+    ;; string.encode_*_array writes to an array, so an array get can't be moved
+    ;; past it.
+    (local.set $temp
+      (array.get $array
+        (local.get $array)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (string.encode_wtf8_array wtf8
+        (local.get $ref)
+        (local.get $array)
+        (i32.const 10)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (array.get $array
+        (local.get $array)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (string.encode_wtf8_array utf8
+        (local.get $ref)
+        (local.get $array)
+        (i32.const 20)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+    (local.set $temp
+      (array.get $array
+        (local.get $array)
+        (i32.const 0)
+      )
+    )
+    (drop
+      (string.encode_wtf16_array
+        (local.get $ref)
+        (local.get $array16)
+        (i32.const 30)
+      )
+    )
+    (drop
+      (local.get $temp)
+    )
+  )
+
+  ;; CHECK:      (func $no-iteration-past-each-other (param $iter stringview_iter)
+  ;; CHECK-NEXT:  (local $i32 i32)
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_iter.next
+  ;; CHECK-NEXT:    (local.get $iter)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (stringview_iter.advance
+  ;; CHECK-NEXT:    (local.get $iter)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $i32)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_iter.next
+  ;; CHECK-NEXT:    (local.get $iter)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (stringview_iter.rewind
+  ;; CHECK-NEXT:    (local.get $iter)
+  ;; CHECK-NEXT:    (i32.const 4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (local.get $i32)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no-iteration-past-each-other
+    (param $iter stringview_iter)
+    (local $i32 i32)
+    ;; Iteration operations interact with each other, and can't be moved past
+    ;; each other.
+    (local.set $i32
+      (stringview_iter.next
+        (local.get $iter)
+      )
+    )
+    (drop
+      (stringview_iter.advance
+        (local.get $iter)
+        (i32.const 3)
+      )
+    )
+    (drop
+      (local.get $i32)
+    )
+    (local.set $i32
+      (stringview_iter.next
+        (local.get $iter)
+      )
+    )
+    (drop
+      (stringview_iter.rewind
+        (local.get $iter)
+        (i32.const 4)
+      )
+    )
+    (drop
+      (local.get $i32)
+    )
+  )
+)
diff --git a/test/lit/passes/simplify-locals_rse_fallthrough.wast b/test/lit/passes/simplify-locals_rse_fallthrough.wast
new file mode 100644
index 0000000..a9161a8
--- /dev/null
+++ b/test/lit/passes/simplify-locals_rse_fallthrough.wast
@@ -0,0 +1,47 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals --rse -S -o - \
+;; RUN:   | filecheck %s
+
+(module
+  ;; CHECK:      (func $no (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (i32.add
+  ;; CHECK-NEXT:   (block $block (result i32)
+  ;; CHECK-NEXT:    (local.tee $x
+  ;; CHECK-NEXT:     (br_if $block
+  ;; CHECK-NEXT:      (local.get $x)
+  ;; CHECK-NEXT:      (local.tee $x
+  ;; CHECK-NEXT:       (i32.const 42)
+  ;; CHECK-NEXT:      )
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $no (param $x i32) (result i32)
+    (i32.add
+      (block $block (result i32)
+        ;; This local.tee is necessary. It might seem that the br_if flows out
+        ;; $x, so we don't need to write that same value to $x once more.
+        ;; SimplifyLocals and RedundantSetElimination try to find such sets and
+        ;; remove them. But this set is necessary, since a tee of $x executes
+        ;; after that local.get. That is, the br_if flows out the *old* value of
+        ;; $x. What happens in practice is that we branch (since the condition
+        ;; is 42), and flow out the old $x, which gets tee'd into $x once more.
+        ;; As a result, the block flows out the original value of $x, and the
+        ;; local.get reads that same value, so the final add returns 2 times the
+        ;; original value of $x. For all that to happen, we need those
+        ;; optimization passes to *not* remove the outer local.tee.
+        (local.tee $x
+          (br_if $block
+            (local.get $x)
+            (local.tee $x
+              (i32.const 42)
+            )
+          )
+        )
+      )
+      (local.get $x)
+    )
+  )
+)
diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast
index 30640a8..420761e 100644
--- a/test/lit/passes/ssa.wast
+++ b/test/lit/passes/ssa.wast
@@ -8,23 +8,19 @@
  (func $foo)
 
  ;; CHECK:      (func $bar (param $x (ref func))
- ;; CHECK-NEXT:  (local $1 funcref)
- ;; CHECK-NEXT:  (local $2 funcref)
+ ;; CHECK-NEXT:  (local $1 (ref func))
+ ;; CHECK-NEXT:  (local $2 (ref func))
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (ref.func $foo)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.get $1)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $2
  ;; CHECK-NEXT:   (ref.func $bar)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
- ;; CHECK-NEXT:   (ref.as_non_null
- ;; CHECK-NEXT:    (local.get $2)
- ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $bar (param $x (ref func))
diff --git a/test/lit/passes/stack-check-memory64.wast b/test/lit/passes/stack-check-memory64.wast
index 0eec0b0..d844b6d 100644
--- a/test/lit/passes/stack-check-memory64.wast
+++ b/test/lit/passes/stack-check-memory64.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --stack-check --enable-memory64 -S -o - | filecheck %s
 
diff --git a/test/lit/passes/stack-ir-roundtrip-eh.wast b/test/lit/passes/stack-ir-roundtrip-eh.wast
index df68d3f..790462c 100644
--- a/test/lit/passes/stack-ir-roundtrip-eh.wast
+++ b/test/lit/passes/stack-ir-roundtrip-eh.wast
@@ -2,6 +2,7 @@
 ;; RUN: wasm-opt %s --generate-stack-ir --roundtrip -all -S -o - | filecheck %s
 
 (module
+ ;; CHECK:      (tag $tag (param i32))
  (tag $tag (param i32))
   ;; CHECK:      (func $delegate-child
   ;; CHECK-NEXT:  (try $label$9
@@ -10,7 +11,7 @@
   ;; CHECK-NEXT:     (do
   ;; CHECK-NEXT:      (nop)
   ;; CHECK-NEXT:     )
-  ;; CHECK-NEXT:     (catch $tag$0
+  ;; CHECK-NEXT:     (catch $tag
   ;; CHECK-NEXT:      (drop
   ;; CHECK-NEXT:       (pop i32)
   ;; CHECK-NEXT:      )
@@ -23,7 +24,7 @@
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:   (catch $tag$0
+  ;; CHECK-NEXT:   (catch $tag
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (pop i32)
   ;; CHECK-NEXT:    )
diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast
new file mode 100644
index 0000000..9df6861
--- /dev/null
+++ b/test/lit/passes/type-refining-isorecursive.wast
@@ -0,0 +1,141 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --hybrid --type-refining -all -S -o - | filecheck %s
+
+(module
+ ;; The types should be refined to a set of three mutually recursive types.
+
+ ;; CHECK:      (rec
+ ;; CHECK-NEXT:  (type $0 (struct_subtype (field anyref) (field (ref $1)) data))
+ (type $0 (struct_subtype (ref null any) anyref data))
+ ;; CHECK:       (type $1 (struct_subtype (field eqref) (field (ref $2)) data))
+ (type $1 (struct_subtype (ref null eq) anyref data))
+ ;; CHECK:       (type $2 (struct_subtype (field i31ref) (field (ref $0)) data))
+ (type $2 (struct_subtype (ref null i31) anyref data))
+
+ ;; CHECK:       (type $ref|$0|_ref|$1|_ref|$2|_=>_none (func_subtype (param (ref $0) (ref $1) (ref $2)) func))
+
+ ;; CHECK:      (func $foo (type $ref|$0|_ref|$1|_ref|$2|_=>_none) (param $x (ref $0)) (param $y (ref $1)) (param $z (ref $2))
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (struct.new $0
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $y)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (struct.new $1
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $z)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (struct.new $2
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $foo (param $x (ref $0)) (param $y (ref $1)) (param $z (ref $2))
+  (drop
+   (struct.new $0
+    (ref.null any)
+    (local.get $y)
+   )
+  )
+  (drop
+   (struct.new $1
+    (ref.null eq)
+    (local.get $z)
+   )
+  )
+  (drop
+   (struct.new $2
+    (ref.null i31)
+    (local.get $x)
+   )
+  )
+ )
+)
+
+(module
+ ;; The types will all be mutually recursive because they all reference and are
+ ;; referenced by $all, but now we need to worry about ordering supertypes
+ ;; correctly.
+
+ ;; CHECK:      (rec
+ ;; CHECK-NEXT:  (type $all (struct_subtype (field i32) (field (ref $0)) (field (ref $1)) (field (ref $2)) data))
+ (type $all (struct_subtype i32 anyref anyref anyref data))
+
+ ;; CHECK:       (type $0 (struct_subtype (field anyref) (field (ref null $all)) (field (ref $0)) data))
+ (type $0 (struct_subtype (ref null any) anyref anyref data))
+ ;; CHECK:       (type $1 (struct_subtype (field eqref) (field (ref null $all)) (field (ref $0)) $0))
+ (type $1 (struct_subtype (ref null eq) anyref anyref $0))
+ ;; CHECK:       (type $2 (struct_subtype (field i31ref) (field (ref null $all)) (field (ref $0)) $1))
+ (type $2 (struct_subtype (ref null i31) anyref anyref $1))
+
+ ;; CHECK:       (type $ref|$0|_ref|$1|_ref|$2|_=>_none (func_subtype (param (ref $0) (ref $1) (ref $2)) func))
+
+ ;; CHECK:      (func $foo (type $ref|$0|_ref|$1|_ref|$2|_=>_none) (param $x (ref $0)) (param $y (ref $1)) (param $z (ref $2))
+ ;; CHECK-NEXT:  (local $all (ref null $all))
+ ;; CHECK-NEXT:  (local.set $all
+ ;; CHECK-NEXT:   (struct.new $all
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:    (local.get $y)
+ ;; CHECK-NEXT:    (local.get $z)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (struct.new $0
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $all)
+ ;; CHECK-NEXT:    (local.get $y)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (struct.new $1
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $all)
+ ;; CHECK-NEXT:    (local.get $z)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (struct.new $2
+ ;; CHECK-NEXT:    (ref.null none)
+ ;; CHECK-NEXT:    (local.get $all)
+ ;; CHECK-NEXT:    (local.get $x)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $foo (param $x (ref $0)) (param $y (ref $1)) (param $z (ref $2))
+  (local $all (ref null $all))
+  (local.set $all
+   (struct.new $all
+    (i32.const 0)
+    (local.get $x)
+    (local.get $y)
+    (local.get $z)
+   )
+  )
+  (drop
+   (struct.new $0
+    (ref.null any)
+    (local.get $all)
+    (local.get $y)
+   )
+  )
+  (drop
+   (struct.new $1
+    (ref.null eq)
+    (local.get $all)
+    (local.get $z)
+   )
+  )
+  (drop
+   (struct.new $2
+    (ref.null i31)
+    (local.get $all)
+    (local.get $x)
+   )
+  )
+ )
+)
diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast
index 91e47cd..f578e34 100644
--- a/test/lit/passes/type-refining.wast
+++ b/test/lit/passes/type-refining.wast
@@ -5,21 +5,21 @@
   ;; A struct with three fields. The first will have no writes, the second one
   ;; write of the same type, and the last a write of a subtype, which will allow
   ;; us to specialize that one.
-  ;; CHECK:      (type $struct (struct_subtype (field (mut anyref)) (field (mut anyref)) (field (mut (ref $ref|$struct|_=>_none))) data))
+  ;; CHECK:      (type $struct (struct_subtype (field (mut anyref)) (field (mut anyref)) (field (mut (ref i31))) data))
   (type $struct (struct_subtype (field (mut anyref)) (field (mut anyref)) (field (mut anyref)) data))
 
   ;; CHECK:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
 
-  ;; CHECK:      (elem declare func $work)
-
   ;; CHECK:      (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
   ;; CHECK-NEXT:  (struct.set $struct 1
   ;; CHECK-NEXT:   (local.get $struct)
-  ;; CHECK-NEXT:   (ref.null any)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 2
   ;; CHECK-NEXT:   (local.get $struct)
-  ;; CHECK-NEXT:   (ref.func $work)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.get $struct 2
@@ -34,7 +34,7 @@
     )
     (struct.set $struct 2
       (local.get $struct)
-      (ref.func $work)
+      (i31.new (i32.const 0))
     )
     (drop
       ;; The type of this struct.get must be updated after the field's type
@@ -51,20 +51,20 @@
   ;; must keep the type nullable, unlike in the previous module, due to the
   ;; default value being null.
 
-  ;; CHECK:      (type $struct (struct_subtype (field (mut (ref null $ref|$struct|_=>_none))) data))
+  ;; CHECK:      (type $struct (struct_subtype (field (mut i31ref)) data))
   (type $struct (struct_subtype (field (mut anyref)) data))
 
   ;; CHECK:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
 
-  ;; CHECK:      (elem declare func $work)
-
   ;; CHECK:      (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new_default $struct)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
   ;; CHECK-NEXT:   (local.get $struct)
-  ;; CHECK-NEXT:   (ref.func $work)
+  ;; CHECK-NEXT:   (i31.new
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $work (param $struct (ref $struct))
@@ -73,7 +73,7 @@
     )
     (struct.set $struct 0
       (local.get $struct)
-      (ref.func $work)
+      (i31.new (i32.const 0))
     )
   )
 )
@@ -571,7 +571,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
   ;; CHECK-NEXT:   (local.get $struct)
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $update-null (param $struct (ref $struct))
@@ -607,7 +607,7 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $child 0
   ;; CHECK-NEXT:   (local.get $child)
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
@@ -635,7 +635,7 @@
   ;; CHECK:      (func $update-null (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child))
   ;; CHECK-NEXT:  (struct.set $struct 0
   ;; CHECK-NEXT:   (local.get $struct)
-  ;; CHECK-NEXT:   (ref.null $struct)
+  ;; CHECK-NEXT:   (ref.null none)
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $child 0
   ;; CHECK-NEXT:   (local.get $child)
@@ -655,7 +655,7 @@
 )
 
 (module
-  ;; CHECK:      (type $struct (struct_subtype (field (mut (ref null data))) data))
+  ;; CHECK:      (type $struct (struct_subtype (field (mut dataref)) data))
   (type $struct (struct_subtype (field (mut (ref null data))) data))
 
   ;; CHECK:      (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
@@ -711,7 +711,7 @@
   ;; CHECK:      (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (struct.set $struct 0
@@ -747,12 +747,12 @@
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
   ;; CHECK-NEXT:    (local.get $child)
-  ;; CHECK-NEXT:    (ref.null $struct)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.new $struct
-  ;; CHECK-NEXT:    (ref.null $child)
+  ;; CHECK-NEXT:    (ref.null none)
   ;; CHECK-NEXT:    (local.get $struct)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
@@ -795,14 +795,15 @@
   ;; CHECK:      (type $Leaf2-Inner (struct_subtype  $Root-Inner))
   (type $Leaf2-Inner (struct_subtype  $Root-Inner))
 
-  ;; CHECK:      (type $none_=>_none (func_subtype func))
+  ;; CHECK:      (type $ref?|$Leaf1-Outer|_=>_none (func_subtype (param (ref null $Leaf1-Outer)) func))
 
   ;; CHECK:      (type $Root-Outer (struct_subtype (field (ref $Leaf2-Inner)) data))
 
+  ;; CHECK:      (type $Leaf2-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer))
+
   ;; CHECK:      (type $Leaf1-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer))
   (type $Leaf1-Outer (struct_subtype (field (ref $Leaf1-Inner)) $Root-Outer))
 
- ;; CHECK:      (type $Leaf2-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer))
  (type $Leaf2-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer))
 
   (type $Root-Outer (struct_subtype (field (ref $Root-Inner)) data))
@@ -811,17 +812,18 @@
 
   (type $Leaf1-Inner (struct_subtype (field i32) $Root-Inner))
 
-  ;; CHECK:      (func $func (type $none_=>_none)
+  ;; CHECK:      (func $func (type $ref?|$Leaf1-Outer|_=>_none) (param $Leaf1-Outer (ref null $Leaf1-Outer))
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block ;; (replaces something unreachable we can't emit)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (block
   ;; CHECK-NEXT:      (drop
-  ;; CHECK-NEXT:       (ref.null $Leaf1-Outer)
+  ;; CHECK-NEXT:       (local.get $Leaf1-Outer)
   ;; CHECK-NEXT:      )
   ;; CHECK-NEXT:      (unreachable)
   ;; CHECK-NEXT:     )
   ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT:  (drop
@@ -830,7 +832,7 @@
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $func
+  (func $func (param $Leaf1-Outer (ref null $Leaf1-Outer))
     (drop
       ;; The situation here is that we have only a get for some types, and no
       ;; other constraints. As we ignore gets, we work under no constraints at
@@ -852,7 +854,7 @@
       ;; unreachable here.
       (struct.get $Leaf1-Inner 0
         (struct.get $Leaf1-Outer 0
-          (ref.null $Leaf1-Outer)
+          (local.get $Leaf1-Outer)
         )
       )
     )
@@ -863,3 +865,181 @@
     )
   )
 )
+
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut (ref null $A))) data))
+  (type $A (struct_subtype (field (mut (ref null $A))) data))
+
+  ;; CHECK:      (type $ref|$A|_ref?|$A|_=>_none (func_subtype (param (ref $A) (ref null $A)) func))
+
+  ;; CHECK:      (func $non-nullability (type $ref|$A|_ref?|$A|_=>_none) (param $nn (ref $A)) (param $A (ref null $A))
+  ;; CHECK-NEXT:  (local $temp (ref null $A))
+  ;; CHECK-NEXT:  (struct.set $A 0
+  ;; CHECK-NEXT:   (local.get $A)
+  ;; CHECK-NEXT:   (local.get $nn)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $A 0
+  ;; CHECK-NEXT:   (local.get $A)
+  ;; CHECK-NEXT:   (local.tee $temp
+  ;; CHECK-NEXT:    (struct.get $A 0
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (local.tee $temp
+  ;; CHECK-NEXT:     (struct.get $A 0
+  ;; CHECK-NEXT:      (local.get $A)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $non-nullability (param $nn (ref $A)) (param $A (ref null $A))
+    (local $temp (ref null $A))
+    ;; Set a non-null value to the field.
+    (struct.set $A 0
+      (local.get $A)
+      (local.get $nn)
+    )
+    ;; Set a get of the same field to the field - this is a copy. However, the
+    ;; copy goes through a local.tee. Even after we refine the type of the field
+    ;; to non-nullable, the tee will remain nullable since it has the type of
+    ;; the local. We could add casts perhaps, but for now we do not optimize,
+    ;; and type $A's field will remain nullable.
+    (struct.set $A 0
+      (local.get $A)
+      (local.tee $temp
+        (struct.get $A 0
+          (local.get $A)
+        )
+      )
+    )
+    ;; The same, but with a struct.new.
+    (drop
+      (struct.new $A
+        (local.tee $temp
+          (struct.get $A 0
+            (local.get $A)
+          )
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (ref null $A)) data))
+  (type $A (struct_subtype (field (ref null $A)) data))
+  ;; CHECK:      (type $B (struct_subtype (field (ref null $B)) $A))
+  (type $B (struct_subtype (field (ref null $A)) $A))
+
+  ;; CHECK:      (type $ref?|$B|_ref?|$A|_=>_none (func_subtype (param (ref null $B) (ref null $A)) func))
+
+  ;; CHECK:      (func $heap-type (type $ref?|$B|_ref?|$A|_=>_none) (param $b (ref null $B)) (param $A (ref null $A))
+  ;; CHECK-NEXT:  (local $a (ref null $A))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $B
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (local.tee $a
+  ;; CHECK-NEXT:     (struct.get $A 0
+  ;; CHECK-NEXT:      (local.get $A)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $heap-type (param $b (ref null $B)) (param $A (ref null $A))
+    (local $a (ref null $A))
+    ;; Similar to the above, but instead of non-nullability being the issue,
+    ;; now it is the heap type. We write a B to B's field, so we can trivially
+    ;; refine that, and we want to do a similar refinement to the supertype A.
+    ;; But below we do a copy on A through a tee. As above, the tee's type will
+    ;; not change, so we do not optimize type $A's field (however, we can
+    ;; refine $B's field, which is safe to do).
+    (drop
+      (struct.new $B
+        (local.get $b)
+      )
+    )
+    (drop
+      (struct.new $A
+        (local.tee $a
+          (struct.get $A 0
+            (local.get $A)
+          )
+        )
+      )
+    )
+  )
+)
+
+(module
+  ;; CHECK:      (type $A (struct_subtype (field (mut (ref $A))) data))
+  (type $A (struct_subtype (field (mut (ref null $A))) data))
+
+  ;; CHECK:      (type $ref|$A|_ref?|$A|_=>_none (func_subtype (param (ref $A) (ref null $A)) func))
+
+  ;; CHECK:      (func $non-nullability-block (type $ref|$A|_ref?|$A|_=>_none) (param $nn (ref $A)) (param $A (ref null $A))
+  ;; CHECK-NEXT:  (struct.set $A 0
+  ;; CHECK-NEXT:   (local.get $A)
+  ;; CHECK-NEXT:   (local.get $nn)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $A 0
+  ;; CHECK-NEXT:   (local.get $A)
+  ;; CHECK-NEXT:   (if (result (ref $A))
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (struct.get $A 0
+  ;; CHECK-NEXT:     (local.get $A)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (unreachable)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (struct.new $A
+  ;; CHECK-NEXT:    (if (result (ref $A))
+  ;; CHECK-NEXT:     (i32.const 1)
+  ;; CHECK-NEXT:     (struct.get $A 0
+  ;; CHECK-NEXT:      (local.get $A)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $non-nullability-block (param $nn (ref $A)) (param $A (ref null $A))
+    (struct.set $A 0
+      (local.get $A)
+      (local.get $nn)
+    )
+    ;; As above, but instead of a local.tee fallthrough, use an if. We *can*
+    ;; optimize in this case, as ifs etc do not pose a problem (we'll refinalize
+    ;; the ifs to the proper, non-nullable type, the same as the field).
+    (struct.set $A 0
+      (local.get $A)
+      (if (result (ref null $A))
+        (i32.const 1)
+        (struct.get $A 0
+          (local.get $A)
+        )
+        (unreachable)
+      )
+    )
+    (drop
+      (struct.new $A
+        (if (result (ref null $A))
+          (i32.const 1)
+          (struct.get $A 0
+            (local.get $A)
+          )
+          (unreachable)
+        )
+      )
+    )
+  )
+)
diff --git a/test/lit/passes/untee.wast b/test/lit/passes/untee.wast
index a352aa8..40e6235 100644
--- a/test/lit/passes/untee.wast
+++ b/test/lit/passes/untee.wast
@@ -1,5 +1,5 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; NOTE: This test was ported using port_test.py and could be cleaned up.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
 
 ;; RUN: foreach %s %t wasm-opt --untee -S -o - | filecheck %s
 
diff --git a/test/lit/passes/vacuum-eh.wast b/test/lit/passes/vacuum-eh.wast
index 574a525..5a6a4b2 100644
--- a/test/lit/passes/vacuum-eh.wast
+++ b/test/lit/passes/vacuum-eh.wast
@@ -22,7 +22,7 @@
     )
   )
 
-  ;; CHECK:      (func $inner-try-catch_all-test
+  ;; CHECK:      (func $inner-try-catch_all-test (result i32)
   ;; CHECK-NEXT:  (local $0 i32)
   ;; CHECK-NEXT:  (try $try0
   ;; CHECK-NEXT:   (do
@@ -31,13 +31,14 @@
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:   (catch_all
-  ;; CHECK-NEXT:    (local.set $0
+  ;; CHECK-NEXT:    (return
   ;; CHECK-NEXT:     (i32.const 1)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
-  (func $inner-try-catch_all-test (local $0 i32)
+  (func $inner-try-catch_all-test (result i32)
+    (local $0 i32)
     ;; The exception thrown in the inner try is caught by the inner catch_all,
     ;; so the outer try body does not throw and the outer try-catch can be
     ;; removed
@@ -48,7 +49,7 @@
             (throw $e (i32.const 0))
           )
           (catch_all
-            (local.set $0 (i32.const 1))
+            (return (i32.const 1))
           )
         )
       )
@@ -56,6 +57,7 @@
         (drop (pop i32))
       )
     )
+    (i32.const 2)
   )
 
   ;; CHECK:      (func $inner-try-catch-test
@@ -176,4 +178,43 @@
       )
     )
   )
+
+  ;; CHECK:      (func $trivial-catch-all-of-throw
+  ;; CHECK-NEXT:  (local $0 i32)
+  ;; CHECK-NEXT:  (try $try3
+  ;; CHECK-NEXT:   (do
+  ;; CHECK-NEXT:    (if
+  ;; CHECK-NEXT:     (local.get $0)
+  ;; CHECK-NEXT:     (throw $e
+  ;; CHECK-NEXT:      (i32.const 0)
+  ;; CHECK-NEXT:     )
+  ;; CHECK-NEXT:     (unreachable)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:   (catch_all
+  ;; CHECK-NEXT:    (nop)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $trivial-catch-all-of-throw (local $0 i32)
+    ;; This try-catch's body throws, but the catch-all catches it, so the entire
+    ;; try can be optimized out.
+    (try
+      (do
+        (throw $e (i32.const 0))
+      )
+      (catch_all)
+    )
+    ;; Here we also have a possible trap, so we can't do it.
+    (try
+      (do
+        (if
+          (local.get $0)
+          (throw $e (i32.const 0))
+          (unreachable)
+        )
+      )
+      (catch_all)
+    )
+  )
 )
diff --git a/test/lit/passes/vacuum-func.wast b/test/lit/passes/vacuum-func.wast
new file mode 100644
index 0000000..fb400b7
--- /dev/null
+++ b/test/lit/passes/vacuum-func.wast
@@ -0,0 +1,95 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s
+
+;; Tests vacuuming the entire body of a function. In that case we can ignore
+;; effects like a return or changes to locals.
+
+(module
+  ;; CHECK:      (func $optimizable (param $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
+  (func $optimizable (param $x i32)
+    (local $y i32)
+    ;; This entire function body can be optimized out. First, operations on
+    ;; locals are not observable once the function exits.
+    (local.set $x
+      (i32.const 1)
+    )
+    (local.set $y
+      (i32.const 2)
+    )
+    (drop
+      (local.get $x)
+    )
+    ;; Second, a return has no noticeable effect for the caller to notice.
+    (return)
+  )
+
+  ;; CHECK:      (func $result (param $x i32) (result i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $y
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (return
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $result (param $x i32) (result i32)
+    (local $y i32)
+    ;; As above, but this function returns a value, so we cannot optimize here:
+    ;; the value must be computed and returned. (We could in theory remove just
+    ;; the parts that are valid to remove, but other passes will do so anyhow
+    ;; for the code in this test at least.)
+    (local.set $x
+      (i32.const 1)
+    )
+    (local.set $y
+      (i32.const 2)
+    )
+    (return
+      (local.get $x)
+    )
+  )
+
+  ;; CHECK:      (func $partial (param $x i32)
+  ;; CHECK-NEXT:  (local $y i32)
+  ;; CHECK-NEXT:  (if
+  ;; CHECK-NEXT:   (local.get $x)
+  ;; CHECK-NEXT:   (unreachable)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $x
+  ;; CHECK-NEXT:   (i32.const 1)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $y
+  ;; CHECK-NEXT:   (i32.const 2)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (return)
+  ;; CHECK-NEXT: )
+  (func $partial (param $x i32)
+    (local $y i32)
+
+    ;; As above, but with this |if| added with extra possible effects. This
+    ;; prevents optimization. (We could in theory remove just the parts that are
+    ;; valid to remove, but other passes will do so anyhow for the code in this
+    ;; test at least.)
+    (if
+      (local.get $x)
+      (unreachable)
+    )
+
+    (local.set $x
+      (i32.const 1)
+    )
+    (local.set $y
+      (i32.const 2)
+    )
+    (drop
+      (local.get $x)
+    )
+    (return)
+  )
+)
diff --git a/test/lit/passes/vacuum-gc.wast b/test/lit/passes/vacuum-gc.wast
index f370854..8ec2733 100644
--- a/test/lit/passes/vacuum-gc.wast
+++ b/test/lit/passes/vacuum-gc.wast
@@ -51,28 +51,45 @@
     )
   )
 
-  ;; CHECK:      (func $vacuum-rtt-with-depth
+  ;; CHECK:      (func $vacuum-nonnull
   ;; CHECK-NEXT:  (nop)
   ;; CHECK-NEXT: )
-  (func $vacuum-rtt-with-depth
+  (func $vacuum-nonnull
     (drop
-      (if (result (rtt 1 ${}))
+      (if (result (ref ${}))
         (i32.const 1)
         ;; This block's result is not used. As a consequence vacuum will try to
-        ;; generate a replacement zero for the block's fallthrough value. An rtt
-        ;; with depth is a problem for that, since we can't just create an
-        ;; rtt.canon - we'd need to add some rtt.subs, and it's not clear that we'd
-        ;; be improving code size while doing so, hence we do not allow making a
-        ;; zero of that type. Vacuum should not error on trying to do so. And
-        ;; the end result of this function should simply be empty, as everything
-        ;; here can be vacuumed away.
-        (block (result (rtt 1 ${}))
-          (rtt.sub ${}
-            (rtt.canon ${})
-          )
+        ;; generate a replacement zero for the block's fallthrough value. A
+        ;; non-nullable reference is a problem for that, since we don't want to
+        ;; synthesize and allocate a new struct value.  Vacuum should not error
+        ;; on this case, though. Instead, the end result of this function should
+        ;; simply be empty, as everything here can be vacuumed away.
+        (block (result (ref ${}))
+          (struct.new ${})
         )
         (unreachable)
       )
     )
   )
+
+  ;; CHECK:      (func $drop-i31.get (param $ref i31ref) (param $ref-nn (ref i31))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i31.get_s
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $drop-i31.get (param $ref i31ref) (param $ref-nn (ref i31))
+    ;; A nullable get might trap, so only the second item can be removed.
+    (drop
+      (i31.get_s
+        (local.get $ref)
+      )
+    )
+    (drop
+      (i31.get_s
+        (local.get $ref-nn)
+      )
+    )
+  )
 )
diff --git a/test/lit/passes/vacuum-intrinsics.wast b/test/lit/passes/vacuum-intrinsics.wast
index bd0f8ed..263c07c 100644
--- a/test/lit/passes/vacuum-intrinsics.wast
+++ b/test/lit/passes/vacuum-intrinsics.wast
@@ -11,20 +11,22 @@
   ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-ref (param funcref) (result (ref any))))
   (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-ref (param funcref) (result (ref any))))
 
-  ;; CHECK:      (func $used
+  ;; CHECK:      (func $used (result i32)
   ;; CHECK-NEXT:  (local $i32 i32)
   ;; CHECK-NEXT:  (local.set $i32
   ;; CHECK-NEXT:   (call $call.without.effects
   ;; CHECK-NEXT:    (ref.func $i)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.get $i32)
   ;; CHECK-NEXT: )
-  (func $used
+  (func $used (result i32)
     (local $i32 i32)
     ;; The result is used (by the local.set), so we cannot do anything here.
     (local.set $i32
       (call $call.without.effects (ref.func $i))
     )
+    (local.get $i32)
   )
 
   ;; CHECK:      (func $unused
@@ -47,13 +49,14 @@
     )
   )
 
-  ;; CHECK:      (func $unused-fj-side-effects
+  ;; CHECK:      (func $unused-fj-side-effects (result f32)
   ;; CHECK-NEXT:  (local $f32 f32)
   ;; CHECK-NEXT:  (local.set $f32
   ;; CHECK-NEXT:   (f32.const 2.718280076980591)
   ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.get $f32)
   ;; CHECK-NEXT: )
-  (func $unused-fj-side-effects
+  (func $unused-fj-side-effects (result f32)
     (local $f32 f32)
     ;; As above, but side effects in the param. We must keep the params around
     ;; and drop them.
@@ -65,6 +68,7 @@
         (ref.func $fj)
       )
     )
+    (local.get $f32)
   )
 
   ;; CHECK:      (func $unused-unreachable
diff --git a/test/lit/passes/vacuum-tnh.wast b/test/lit/passes/vacuum-tnh.wast
index 83cb298..37aecc5 100644
--- a/test/lit/passes/vacuum-tnh.wast
+++ b/test/lit/passes/vacuum-tnh.wast
@@ -1,15 +1,50 @@
 ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --vacuum --traps-never-happen -all -S -o - | filecheck %s
+
+;; Run in both TNH and non-TNH mode.
+
+;; RUN: wasm-opt %s --vacuum --traps-never-happen -all -S -o - | filecheck %s --check-prefix=YESTNH
+;; RUN: wasm-opt %s --vacuum                      -all -S -o - | filecheck %s --check-prefix=NO_TNH
 
 (module
   (memory 1 1)
 
-  ;; CHECK:      (type $struct (struct (field (mut i32))))
+  ;; YESTNH:      (type $struct (struct (field (mut i32))))
+  ;; NO_TNH:      (type $struct (struct (field (mut i32))))
   (type $struct (struct (field (mut i32))))
 
-  ;; CHECK:      (func $drop (param $x i32) (param $y anyref)
-  ;; CHECK-NEXT:  (nop)
-  ;; CHECK-NEXT: )
+  ;; YESTNH:      (func $drop (param $x i32) (param $y anyref)
+  ;; YESTNH-NEXT:  (nop)
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $drop (param $x i32) (param $y anyref)
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (i32.load
+  ;; NO_TNH-NEXT:    (local.get $x)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.as_non_null
+  ;; NO_TNH-NEXT:    (local.get $y)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.as_func
+  ;; NO_TNH-NEXT:    (local.get $y)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.as_data
+  ;; NO_TNH-NEXT:    (local.get $y)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (ref.as_i31
+  ;; NO_TNH-NEXT:    (local.get $y)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (unreachable)
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT: )
   (func $drop (param $x i32) (param $y anyref)
     ;; A load might trap, normally, but if traps never happen then we can
     ;; remove it.
@@ -48,17 +83,35 @@
   )
 
   ;; Other side effects prevent us making any changes.
-  ;; CHECK:      (func $other-side-effects (param $x i32) (result i32)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (call $other-side-effects
-  ;; CHECK-NEXT:    (i32.const 1)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $x
-  ;; CHECK-NEXT:   (i32.const 2)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (i32.const 1)
-  ;; CHECK-NEXT: )
+  ;; YESTNH:      (func $other-side-effects (param $x i32) (result i32)
+  ;; YESTNH-NEXT:  (drop
+  ;; YESTNH-NEXT:   (call $other-side-effects
+  ;; YESTNH-NEXT:    (i32.const 1)
+  ;; YESTNH-NEXT:   )
+  ;; YESTNH-NEXT:  )
+  ;; YESTNH-NEXT:  (local.set $x
+  ;; YESTNH-NEXT:   (i32.const 2)
+  ;; YESTNH-NEXT:  )
+  ;; YESTNH-NEXT:  (i32.const 1)
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $other-side-effects (param $x i32) (result i32)
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (call $other-side-effects
+  ;; NO_TNH-NEXT:    (i32.const 1)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (block (result i32)
+  ;; NO_TNH-NEXT:    (local.set $x
+  ;; NO_TNH-NEXT:     (i32.const 2)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:    (i32.load
+  ;; NO_TNH-NEXT:     (local.get $x)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (i32.const 1)
+  ;; NO_TNH-NEXT: )
   (func $other-side-effects (param $x i32) (result i32)
     ;; A call has all manner of other side effects.
     (drop
@@ -78,21 +131,43 @@
   )
 
   ;; A helper function for the above, that returns nothing.
-  ;; CHECK:      (func $return-nothing
-  ;; CHECK-NEXT:  (nop)
-  ;; CHECK-NEXT: )
+  ;; YESTNH:      (func $return-nothing
+  ;; YESTNH-NEXT:  (nop)
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $return-nothing
+  ;; NO_TNH-NEXT:  (nop)
+  ;; NO_TNH-NEXT: )
   (func $return-nothing)
 
-  ;; CHECK:      (func $partial (param $x (ref $struct))
-  ;; CHECK-NEXT:  (local $y (ref null $struct))
-  ;; CHECK-NEXT:  (local.set $y
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (local.set $y
-  ;; CHECK-NEXT:   (local.get $x)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  (func $partial (param $x (ref $struct))
+  ;; YESTNH:      (func $partial (param $x (ref $struct)) (result (ref null $struct))
+  ;; YESTNH-NEXT:  (local $y (ref null $struct))
+  ;; YESTNH-NEXT:  (local.set $y
+  ;; YESTNH-NEXT:   (local.get $x)
+  ;; YESTNH-NEXT:  )
+  ;; YESTNH-NEXT:  (local.set $y
+  ;; YESTNH-NEXT:   (local.get $x)
+  ;; YESTNH-NEXT:  )
+  ;; YESTNH-NEXT:  (local.get $y)
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $partial (param $x (ref $struct)) (result (ref null $struct))
+  ;; NO_TNH-NEXT:  (local $y (ref null $struct))
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (struct.get $struct 0
+  ;; NO_TNH-NEXT:    (local.tee $y
+  ;; NO_TNH-NEXT:     (local.get $x)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (struct.get $struct 0
+  ;; NO_TNH-NEXT:    (local.tee $y
+  ;; NO_TNH-NEXT:     (local.get $x)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT:  (local.get $y)
+  ;; NO_TNH-NEXT: )
+  (func $partial (param $x (ref $struct)) (result (ref null $struct))
     (local $y (ref null $struct))
     ;; The struct.get's side effect can be ignored due to tnh, and the value is
     ;; dropped anyhow, so we can remove it. We cannot remove the local.tee
@@ -115,14 +190,92 @@
         )
       )
     )
+    (local.get $y)
   )
 
-  ;; CHECK:      (func $toplevel
-  ;; CHECK-NEXT:  (nop)
-  ;; CHECK-NEXT: )
+  ;; YESTNH:      (func $toplevel
+  ;; YESTNH-NEXT:  (nop)
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $toplevel
+  ;; NO_TNH-NEXT:  (unreachable)
+  ;; NO_TNH-NEXT: )
   (func $toplevel
     ;; A removable side effect at the top level of a function. We can turn this
     ;; into a nop.
     (unreachable)
   )
+
+  ;; YESTNH:      (func $drop-loop
+  ;; YESTNH-NEXT:  (nop)
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $drop-loop
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (loop $loop (result i32)
+  ;; NO_TNH-NEXT:    (br_if $loop
+  ;; NO_TNH-NEXT:     (i32.const 1)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:    (i32.const 10)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT: )
+  (func $drop-loop
+    ;; A loop has effects, since it might infinite loop (and hit a timeout trap
+    ;; eventually), so we do not vacuum it out unless we are ignoring traps.
+    (drop
+      (loop $loop (result i32)
+        (br_if $loop
+          (i32.const 1)
+        )
+        (i32.const 10)
+      )
+    )
+  )
+
+  ;; YESTNH:      (func $loop-effects
+  ;; YESTNH-NEXT:  (drop
+  ;; YESTNH-NEXT:   (loop $loop (result i32)
+  ;; YESTNH-NEXT:    (drop
+  ;; YESTNH-NEXT:     (i32.atomic.load
+  ;; YESTNH-NEXT:      (i32.const 0)
+  ;; YESTNH-NEXT:     )
+  ;; YESTNH-NEXT:    )
+  ;; YESTNH-NEXT:    (br_if $loop
+  ;; YESTNH-NEXT:     (i32.const 1)
+  ;; YESTNH-NEXT:    )
+  ;; YESTNH-NEXT:    (i32.const 10)
+  ;; YESTNH-NEXT:   )
+  ;; YESTNH-NEXT:  )
+  ;; YESTNH-NEXT: )
+  ;; NO_TNH:      (func $loop-effects
+  ;; NO_TNH-NEXT:  (drop
+  ;; NO_TNH-NEXT:   (loop $loop (result i32)
+  ;; NO_TNH-NEXT:    (drop
+  ;; NO_TNH-NEXT:     (i32.atomic.load
+  ;; NO_TNH-NEXT:      (i32.const 0)
+  ;; NO_TNH-NEXT:     )
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:    (br_if $loop
+  ;; NO_TNH-NEXT:     (i32.const 1)
+  ;; NO_TNH-NEXT:    )
+  ;; NO_TNH-NEXT:    (i32.const 10)
+  ;; NO_TNH-NEXT:   )
+  ;; NO_TNH-NEXT:  )
+  ;; NO_TNH-NEXT: )
+  (func $loop-effects
+    ;; As above, but the loop also has an atomic load effect. That prevents
+    ;; optimization.
+    (drop
+      (loop $loop (result i32)
+        (drop
+          (i32.atomic.load
+            (i32.const 0)
+          )
+        )
+        (br_if $loop
+          (i32.const 1)
+        )
+        (i32.const 10)
+      )
+    )
+  )
 )
diff --git a/test/lit/relaxed-simd.wast b/test/lit/relaxed-simd.wast
index 9d957ff..772ff20 100644
--- a/test/lit/relaxed-simd.wast
+++ b/test/lit/relaxed-simd.wast
@@ -388,25 +388,6 @@
   )
  )
 
- ;; CHECK-BINARY:      (func $i16x8.dot_i8x16_i7x16_u (param $0 v128) (param $1 v128) (result v128)
- ;; CHECK-BINARY-NEXT:  (i16x8.dot_i8x16_i7x16_u
- ;; CHECK-BINARY-NEXT:   (local.get $0)
- ;; CHECK-BINARY-NEXT:   (local.get $1)
- ;; CHECK-BINARY-NEXT:  )
- ;; CHECK-BINARY-NEXT: )
- ;; CHECK-TEXT:      (func $i16x8.dot_i8x16_i7x16_u (param $0 v128) (param $1 v128) (result v128)
- ;; CHECK-TEXT-NEXT:  (i16x8.dot_i8x16_i7x16_u
- ;; CHECK-TEXT-NEXT:   (local.get $0)
- ;; CHECK-TEXT-NEXT:   (local.get $1)
- ;; CHECK-TEXT-NEXT:  )
- ;; CHECK-TEXT-NEXT: )
- (func $i16x8.dot_i8x16_i7x16_u (param $0 v128) (param $1 v128) (result v128)
-  (i16x8.dot_i8x16_i7x16_u
-   (local.get $0)
-   (local.get $1)
-  )
- )
-
 ;; CHECK-BINARY:      (func $i32x4.dot_i8x16_i7x16_add_s (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
 ;; CHECK-BINARY-NEXT:  (i32x4.dot_i8x16_i7x16_add_s
 ;; CHECK-BINARY-NEXT:   (local.get $0)
@@ -429,28 +410,6 @@
   )
  )
 
-;; CHECK-BINARY:      (func $i32x4.dot_i8x16_i7x16_add_u (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
-;; CHECK-BINARY-NEXT:  (i32x4.dot_i8x16_i7x16_add_u
-;; CHECK-BINARY-NEXT:   (local.get $0)
-;; CHECK-BINARY-NEXT:   (local.get $1)
-;; CHECK-BINARY-NEXT:   (local.get $2)
-;; CHECK-BINARY-NEXT:  )
-;; CHECK-BINARY-NEXT: )
-;; CHECK-TEXT:      (func $i32x4.dot_i8x16_i7x16_add_u (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
-;; CHECK-TEXT-NEXT:  (i32x4.dot_i8x16_i7x16_add_u
-;; CHECK-TEXT-NEXT:   (local.get $0)
-;; CHECK-TEXT-NEXT:   (local.get $1)
-;; CHECK-TEXT-NEXT:   (local.get $2)
-;; CHECK-TEXT-NEXT:  )
-;; CHECK-TEXT-NEXT: )
-(func $i32x4.dot_i8x16_i7x16_add_u (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
-  (i32x4.dot_i8x16_i7x16_add_u
-   (local.get $0)
-   (local.get $1)
-   (local.get $2)
-  )
- )
-
 )
 ;; CHECK-NODEBUG:      (type $v128_v128_v128_=>_v128 (func (param v128 v128 v128) (result v128)))
 
@@ -597,25 +556,10 @@
 ;; CHECK-NODEBUG-NEXT:  )
 ;; CHECK-NODEBUG-NEXT: )
 
-;; CHECK-NODEBUG:      (func $19 (param $0 v128) (param $1 v128) (result v128)
-;; CHECK-NODEBUG-NEXT:  (i16x8.dot_i8x16_i7x16_u
-;; CHECK-NODEBUG-NEXT:   (local.get $0)
-;; CHECK-NODEBUG-NEXT:   (local.get $1)
-;; CHECK-NODEBUG-NEXT:  )
-;; CHECK-NODEBUG-NEXT: )
-
-;; CHECK-NODEBUG:      (func $20 (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
+;; CHECK-NODEBUG:      (func $19 (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
 ;; CHECK-NODEBUG-NEXT:  (i32x4.dot_i8x16_i7x16_add_s
 ;; CHECK-NODEBUG-NEXT:   (local.get $0)
 ;; CHECK-NODEBUG-NEXT:   (local.get $1)
 ;; CHECK-NODEBUG-NEXT:   (local.get $2)
 ;; CHECK-NODEBUG-NEXT:  )
 ;; CHECK-NODEBUG-NEXT: )
-
-;; CHECK-NODEBUG:      (func $21 (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
-;; CHECK-NODEBUG-NEXT:  (i32x4.dot_i8x16_i7x16_add_u
-;; CHECK-NODEBUG-NEXT:   (local.get $0)
-;; CHECK-NODEBUG-NEXT:   (local.get $1)
-;; CHECK-NODEBUG-NEXT:   (local.get $2)
-;; CHECK-NODEBUG-NEXT:  )
-;; CHECK-NODEBUG-NEXT: )
diff --git a/test/lit/strings.wast b/test/lit/strings.wast
new file mode 100644
index 0000000..6b55f28
--- /dev/null
+++ b/test/lit/strings.wast
@@ -0,0 +1,560 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Check that string types are emitted properly in the binary format.
+;;
+;; runs --precompute in order to verify no problems occur in the optimizer's
+;; invocation of the interpreter.
+
+;; RUN: wasm-opt %s --enable-strings --enable-reference-types --enable-gc --roundtrip --precompute -S -o - | filecheck %s
+
+;; Check that we can roundtrip through the text format as well.
+
+;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all --precompute -S -o - | filecheck %s
+
+(module
+  (memory 10 10)
+
+  ;; CHECK:      (type $stringref_=>_none (func (param stringref)))
+
+  ;; CHECK:      (type $stringref_stringview_wtf8_stringview_wtf16_stringview_iter_=>_none (func (param stringref stringview_wtf8 stringview_wtf16 stringview_iter)))
+
+  ;; CHECK:      (type $stringref_stringref_=>_none (func (param stringref stringref)))
+
+  ;; CHECK:      (type $array (array (mut i8)))
+  (type $array (array_subtype (mut i8) data))
+  ;; CHECK:      (type $array16 (array (mut i16)))
+  (type $array16 (array_subtype (mut i16) data))
+
+  ;; CHECK:      (type $stringref_stringview_wtf8_stringview_wtf16_stringview_iter_stringref_stringview_wtf8_stringview_wtf16_stringview_iter_ref|string|_ref|stringview_wtf8|_ref|stringview_wtf16|_ref|stringview_iter|_=>_none (func (param stringref stringview_wtf8 stringview_wtf16 stringview_iter stringref stringview_wtf8 stringview_wtf16 stringview_iter (ref string) (ref stringview_wtf8) (ref stringview_wtf16) (ref stringview_iter))))
+
+  ;; CHECK:      (type $none_=>_none (func))
+
+  ;; CHECK:      (type $stringview_wtf16_=>_none (func (param stringview_wtf16)))
+
+  ;; CHECK:      (type $ref|$array|_ref|$array16|_=>_none (func (param (ref $array) (ref $array16))))
+
+  ;; CHECK:      (type $stringref_ref|$array|_ref|$array16|_=>_none (func (param stringref (ref $array) (ref $array16))))
+
+  ;; CHECK:      (global $string-const stringref (string.const "string in a global \01\ff\00\t\t\n\n\r\r\"\"\'\'\\\\"))
+  (global $string-const stringref (string.const "string in a global \01\ff\00\t\09\n\0a\r\0d\"\22\'\27\\\5c"))
+
+  ;; CHECK:      (memory $0 10 10)
+
+  ;; CHECK:      (func $string.new (param $a stringref) (param $b stringview_wtf8) (param $c stringview_wtf16) (param $d stringview_iter) (param $e stringref) (param $f stringview_wtf8) (param $g stringview_wtf16) (param $h stringview_iter) (param $i (ref string)) (param $j (ref stringview_wtf8)) (param $k (ref stringview_wtf16)) (param $l (ref stringview_iter))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8 utf8
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8 wtf8
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:    (i32.const 4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8 replace
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:    (i32.const 6)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf16
+  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:    (i32.const 8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.new
+    (param $a stringref)
+    (param $b stringview_wtf8)
+    (param $c stringview_wtf16)
+    (param $d stringview_iter)
+    (param $e (ref null string))
+    (param $f (ref null stringview_wtf8))
+    (param $g (ref null stringview_wtf16))
+    (param $h (ref null stringview_iter))
+    (param $i (ref string))
+    (param $j (ref stringview_wtf8))
+    (param $k (ref stringview_wtf16))
+    (param $l (ref stringview_iter))
+    (drop
+      (string.new_wtf8 utf8
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (drop
+      (string.new_wtf8 wtf8
+        (i32.const 3)
+        (i32.const 4)
+      )
+    )
+    (drop
+      (string.new_wtf8 replace
+        (i32.const 5)
+        (i32.const 6)
+      )
+    )
+    (drop
+      (string.new_wtf16
+        (i32.const 7)
+        (i32.const 8)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.const
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.const "foo")
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.const "foo")
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.const "bar")
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.const
+    (drop
+      (string.const "foo")
+    )
+    (drop
+      (string.const "foo") ;; intentionally repeat the previous one
+    )
+    (drop
+      (string.const "bar")
+    )
+  )
+
+  ;; CHECK:      (func $string.measure (param $ref stringref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (string.measure_wtf8 wtf8
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.measure_wtf8 utf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.measure_wtf16
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.measure (param $ref stringref)
+    (drop
+      (i32.eqz ;; validate the output is i32
+        (string.measure_wtf8 wtf8
+          (local.get $ref)
+        )
+      )
+    )
+    (drop
+      (string.measure_wtf8 utf8
+        (local.get $ref)
+      )
+    )
+    (drop
+      (string.measure_wtf16
+        (local.get $ref)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.encode (param $ref stringref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (string.encode_wtf8 wtf8
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf8 utf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf16
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.encode (param $ref stringref)
+    (drop
+      (i32.eqz ;; validate the output is i32
+        (string.encode_wtf8 wtf8
+          (local.get $ref)
+          (i32.const 10)
+        )
+      )
+    )
+    (drop
+      (string.encode_wtf8 utf8
+        (local.get $ref)
+        (i32.const 20)
+      )
+    )
+    (drop
+      (string.encode_wtf16
+        (local.get $ref)
+        (i32.const 30)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.concat (param $a stringref) (param $b stringref)
+  ;; CHECK-NEXT:  (local.set $a
+  ;; CHECK-NEXT:   (string.concat
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.concat (param $a stringref) (param $b stringref)
+    (local.set $a ;; validate the output is a stringref
+      (string.concat
+        (local.get $a)
+        (local.get $b)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.eq (param $a stringref) (param $b stringref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (string.eq
+  ;; CHECK-NEXT:     (local.get $a)
+  ;; CHECK-NEXT:     (local.get $b)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.eq (param $a stringref) (param $b stringref)
+    (drop
+      (i32.eqz ;; validate the output is an i32
+        (string.eq
+          (local.get $a)
+          (local.get $b)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.is_usv_sequence (param $ref stringref)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (string.is_usv_sequence
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.is_usv_sequence (param $ref stringref)
+    (drop
+      (i32.eqz ;; validate the output is i32
+        (string.is_usv_sequence
+          (local.get $ref)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.as (param $a stringref) (param $b stringview_wtf8) (param $c stringview_wtf16) (param $d stringview_iter)
+  ;; CHECK-NEXT:  (local.set $b
+  ;; CHECK-NEXT:   (string.as_wtf8
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $c
+  ;; CHECK-NEXT:   (string.as_wtf16
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $d
+  ;; CHECK-NEXT:   (string.as_iter
+  ;; CHECK-NEXT:    (local.get $a)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.as
+    (param $a stringref)
+    (param $b stringview_wtf8)
+    (param $c stringview_wtf16)
+    (param $d stringview_iter)
+    (local.set $b ;; validate the output type
+      (string.as_wtf8
+        (local.get $a)
+      )
+    )
+    (local.set $c
+      (string.as_wtf16
+        (local.get $a)
+      )
+    )
+    (local.set $d
+      (string.as_iter
+        (local.get $a)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $stringview-access (param $a stringref) (param $b stringview_wtf8) (param $c stringview_wtf16) (param $d stringview_iter)
+  ;; CHECK-NEXT:  (local $i32 i32)
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_wtf8.advance
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_wtf16.get_codeunit
+  ;; CHECK-NEXT:    (local.get $c)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_iter.next
+  ;; CHECK-NEXT:    (local.get $d)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_iter.advance
+  ;; CHECK-NEXT:    (local.get $d)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $i32
+  ;; CHECK-NEXT:   (stringview_iter.rewind
+  ;; CHECK-NEXT:    (local.get $d)
+  ;; CHECK-NEXT:    (i32.const 4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $stringview-access
+    (param $a stringref)
+    (param $b stringview_wtf8)
+    (param $c stringview_wtf16)
+    (param $d stringview_iter)
+    (local $i32 i32)
+    (local.set $i32 ;; validate the output type
+      (stringview_wtf8.advance
+        (local.get $b)
+        (i32.const 0)
+        (i32.const 1)
+      )
+    )
+    (local.set $i32
+      (stringview_wtf16.get_codeunit
+        (local.get $c)
+        (i32.const 2)
+      )
+    )
+    (local.set $i32
+      (stringview_iter.next
+        (local.get $d)
+      )
+    )
+    (local.set $i32
+      (stringview_iter.advance
+        (local.get $d)
+        (i32.const 3)
+      )
+    )
+    (local.set $i32
+      (stringview_iter.rewind
+        (local.get $d)
+        (i32.const 4)
+      )
+    )
+  )
+  ;; CHECK:      (func $stringview-slice (param $a stringref) (param $b stringview_wtf8) (param $c stringview_wtf16) (param $d stringview_iter)
+  ;; CHECK-NEXT:  (local.set $a
+  ;; CHECK-NEXT:   (stringview_wtf8.slice
+  ;; CHECK-NEXT:    (local.get $b)
+  ;; CHECK-NEXT:    (i32.const 0)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $a
+  ;; CHECK-NEXT:   (stringview_wtf16.slice
+  ;; CHECK-NEXT:    (local.get $c)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $a
+  ;; CHECK-NEXT:   (stringview_iter.slice
+  ;; CHECK-NEXT:    (local.get $d)
+  ;; CHECK-NEXT:    (i32.const 4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $stringview-slice
+    (param $a stringref)
+    (param $b stringview_wtf8)
+    (param $c stringview_wtf16)
+    (param $d stringview_iter)
+    (local.set $a ;; validate the output type
+      (stringview_wtf8.slice
+        (local.get $b)
+        (i32.const 0)
+        (i32.const 1)
+      )
+    )
+    (local.set $a
+      (stringview_wtf16.slice
+        (local.get $c)
+        (i32.const 2)
+        (i32.const 3)
+      )
+    )
+    (local.set $a
+      (stringview_iter.slice
+        (local.get $d)
+        (i32.const 4)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.length (param $ref stringview_wtf16)
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (stringview_wtf16.length
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.length (param $ref stringview_wtf16)
+    (drop
+      (i32.eqz ;; validate the output is i32
+        (stringview_wtf16.length
+          (local.get $ref)
+        )
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.new.gc (param $array (ref $array)) (param $array16 (ref $array16))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8_array utf8
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 1)
+  ;; CHECK-NEXT:    (i32.const 2)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8_array wtf8
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 3)
+  ;; CHECK-NEXT:    (i32.const 4)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf8_array replace
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 5)
+  ;; CHECK-NEXT:    (i32.const 6)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.new_wtf16_array
+  ;; CHECK-NEXT:    (local.get $array16)
+  ;; CHECK-NEXT:    (i32.const 7)
+  ;; CHECK-NEXT:    (i32.const 8)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.new.gc (param $array (ref $array)) (param $array16 (ref $array16))
+    (drop
+      (string.new_wtf8_array utf8
+        (local.get $array)
+        (i32.const 1)
+        (i32.const 2)
+      )
+    )
+    (drop
+      (string.new_wtf8_array wtf8
+        (local.get $array)
+        (i32.const 3)
+        (i32.const 4)
+      )
+    )
+    (drop
+      (string.new_wtf8_array replace
+        (local.get $array)
+        (i32.const 5)
+        (i32.const 6)
+      )
+    )
+    (drop
+      (string.new_wtf16_array
+        (local.get $array16)
+        (i32.const 7)
+        (i32.const 8)
+      )
+    )
+  )
+
+  ;; CHECK:      (func $string.encode.gc (param $ref stringref) (param $array (ref $array)) (param $array16 (ref $array16))
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (i32.eqz
+  ;; CHECK-NEXT:    (string.encode_wtf8_array wtf8
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:     (local.get $array)
+  ;; CHECK-NEXT:     (i32.const 10)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf8_array utf8
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (local.get $array)
+  ;; CHECK-NEXT:    (i32.const 20)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (drop
+  ;; CHECK-NEXT:   (string.encode_wtf16_array
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:    (local.get $array16)
+  ;; CHECK-NEXT:    (i32.const 30)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $string.encode.gc (param $ref stringref) (param $array (ref $array)) (param $array16 (ref $array16))
+    (drop
+      (i32.eqz ;; validate the output is i32
+        (string.encode_wtf8_array wtf8
+          (local.get $ref)
+          (local.get $array)
+          (i32.const 10)
+        )
+      )
+    )
+    (drop
+      (string.encode_wtf8_array utf8
+        (local.get $ref)
+        (local.get $array)
+        (i32.const 20)
+      )
+    )
+    (drop
+      (string.encode_wtf16_array
+        (local.get $ref)
+        (local.get $array16)
+        (i32.const 30)
+      )
+    )
+  )
+)
diff --git a/test/lit/structref.wast b/test/lit/structref.wast
new file mode 100644
index 0000000..5e674eb
--- /dev/null
+++ b/test/lit/structref.wast
@@ -0,0 +1,13 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+
+;; RUN: wasm-opt -all %s -S -o - | filecheck %s
+
+;; Check that `struct` is correctly parsed as an alias for `data`.
+(module
+  ;; CHECK:      (func $foo (param $x dataref) (param $y (ref data))
+  ;; CHECK-NEXT:  (unreachable)
+  ;; CHECK-NEXT: )
+  (func $foo (param $x structref) (param $y (ref struct))
+    (unreachable)
+  )
+)
diff --git a/test/lit/table-multi-export.wast b/test/lit/table-multi-export.wast
new file mode 100644
index 0000000..fd3cc1f
--- /dev/null
+++ b/test/lit/table-multi-export.wast
@@ -0,0 +1,20 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt %s -all --roundtrip --print | filecheck %s
+
+;; Test that we properly read and write table exports in the binary format.
+(module
+ ;; Two different tables, each exported.
+
+ ;; CHECK:      (table $0 25 25 funcref)
+ (table $0 25 25 funcref)
+ ;; CHECK:      (table $1 32 anyref)
+ (table $1 32 anyref)
+
+ ;; Each export should export the right table.
+
+ ;; CHECK:      (export "table0" (table $0))
+ (export "table0" (table $0))
+ ;; CHECK:      (export "table1" (table $1))
+ (export "table1" (table $1))
+)
diff --git a/test/lit/table-operations.wast b/test/lit/table-operations.wast
index ace31a9..8422705 100644
--- a/test/lit/table-operations.wast
+++ b/test/lit/table-operations.wast
@@ -134,13 +134,13 @@
 
   ;; CHECK-BINARY:      (func $table-grow (param $sz i32) (result i32)
   ;; CHECK-BINARY-NEXT:  (table.grow $table-1
-  ;; CHECK-BINARY-NEXT:   (ref.null func)
+  ;; CHECK-BINARY-NEXT:   (ref.null nofunc)
   ;; CHECK-BINARY-NEXT:   (local.get $sz)
   ;; CHECK-BINARY-NEXT:  )
   ;; CHECK-BINARY-NEXT: )
   ;; CHECK-TEXT:      (func $table-grow (param $sz i32) (result i32)
   ;; CHECK-TEXT-NEXT:  (table.grow $table-1
-  ;; CHECK-TEXT-NEXT:   (ref.null func)
+  ;; CHECK-TEXT-NEXT:   (ref.null nofunc)
   ;; CHECK-TEXT-NEXT:   (local.get $sz)
   ;; CHECK-TEXT-NEXT:  )
   ;; CHECK-TEXT-NEXT: )
@@ -197,7 +197,7 @@
 
 ;; CHECK-NODEBUG:      (func $4 (param $0 i32) (result i32)
 ;; CHECK-NODEBUG-NEXT:  (table.grow $0
-;; CHECK-NODEBUG-NEXT:   (ref.null func)
+;; CHECK-NODEBUG-NEXT:   (ref.null nofunc)
 ;; CHECK-NODEBUG-NEXT:   (local.get $0)
 ;; CHECK-NODEBUG-NEXT:  )
 ;; CHECK-NODEBUG-NEXT: )
diff --git a/test/lit/types-function-references.wast b/test/lit/types-function-references.wast
new file mode 100644
index 0000000..d51b73a
--- /dev/null
+++ b/test/lit/types-function-references.wast
@@ -0,0 +1,529 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-as %s -all -g -o %t.wasm
+;; RUN: wasm-dis %t.wasm -all -o %t.wast
+;; RUN: wasm-as %s -all -o %t.nodebug.wasm
+;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.nodebug.wast
+;; RUN: wasm-opt %t.wast -all -o %t.text.wast -g -S
+;; RUN: cat %t.wast | filecheck %s --check-prefix=CHECK-BINARY
+;; RUN: cat %t.nodebug.wast | filecheck %s --check-prefix=CHECK-NODEBUG
+;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT
+
+(module
+  ;; CHECK-BINARY:      (type $mixed_results (func (result anyref f32 anyref f32)))
+
+  ;; CHECK-BINARY:      (type $void (func))
+  ;; CHECK-TEXT:      (type $mixed_results (func (result anyref f32 anyref f32)))
+
+  ;; CHECK-TEXT:      (type $void (func))
+  (type $void (func))
+
+  ;; inline ref type in result
+  (type $_=>_eqref (func (result eqref)))
+  ;; CHECK-BINARY:      (type $i32-i32 (func (param i32) (result i32)))
+
+  ;; CHECK-BINARY:      (type $ref|$i32-i32|_=>_i32 (func (param (ref $i32-i32)) (result i32)))
+
+  ;; CHECK-BINARY:      (type $ref?|$i32-i32|_=>_i32 (func (param (ref null $i32-i32)) (result i32)))
+
+  ;; CHECK-BINARY:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK-BINARY:      (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $=>eqref))))
+  ;; CHECK-TEXT:      (type $i32-i32 (func (param i32) (result i32)))
+
+  ;; CHECK-TEXT:      (type $ref|$i32-i32|_=>_i32 (func (param (ref $i32-i32)) (result i32)))
+
+  ;; CHECK-TEXT:      (type $ref?|$i32-i32|_=>_i32 (func (param (ref null $i32-i32)) (result i32)))
+
+  ;; CHECK-TEXT:      (type $none_=>_i32 (func (result i32)))
+
+  ;; CHECK-TEXT:      (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $=>eqref))))
+  (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $_=>_eqref))))
+  ;; CHECK-BINARY:      (type $=>anyref (func (result anyref)))
+
+  ;; CHECK-BINARY:      (type $none_=>_i32_ref?|$mixed_results|_f64 (func (result i32 (ref null $mixed_results) f64)))
+
+  ;; CHECK-BINARY:      (type $ref?|$mixed_results|_=>_none (func (param (ref null $mixed_results))))
+
+  ;; CHECK-BINARY:      (type $=>eqref (func (result eqref)))
+  ;; CHECK-TEXT:      (type $=>anyref (func (result anyref)))
+
+  ;; CHECK-TEXT:      (type $none_=>_i32_ref?|$mixed_results|_f64 (func (result i32 (ref null $mixed_results) f64)))
+
+  ;; CHECK-TEXT:      (type $ref?|$mixed_results|_=>_none (func (param (ref null $mixed_results))))
+
+  ;; CHECK-TEXT:      (type $=>eqref (func (result eqref)))
+  (type $=>eqref (func (result eqref)))
+  (type $=>anyref (func (result anyref)))
+  (type $mixed_results (func (result anyref f32 anyref f32)))
+
+  (type $i32-i32 (func (param i32) (result i32)))
+
+  ;; CHECK-BINARY:      (elem declare func $call-ref $call-ref-more)
+
+  ;; CHECK-BINARY:      (func $call-ref
+  ;; CHECK-BINARY-NEXT:  (call_ref $void
+  ;; CHECK-BINARY-NEXT:   (ref.func $call-ref)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (elem declare func $call-ref $call-ref-more)
+
+  ;; CHECK-TEXT:      (func $call-ref
+  ;; CHECK-TEXT-NEXT:  (call_ref $void
+  ;; CHECK-TEXT-NEXT:   (ref.func $call-ref)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $call-ref
+    (call_ref $void (ref.func $call-ref))
+  )
+  ;; CHECK-BINARY:      (func $return-call-ref
+  ;; CHECK-BINARY-NEXT:  (return_call_ref $void
+  ;; CHECK-BINARY-NEXT:   (ref.func $call-ref)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $return-call-ref
+  ;; CHECK-TEXT-NEXT:  (return_call_ref $void
+  ;; CHECK-TEXT-NEXT:   (ref.func $call-ref)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $return-call-ref
+    (return_call_ref $void (ref.func $call-ref))
+  )
+  ;; CHECK-BINARY:      (func $call-ref-more (param $0 i32) (result i32)
+  ;; CHECK-BINARY-NEXT:  (call_ref $i32-i32
+  ;; CHECK-BINARY-NEXT:   (i32.const 42)
+  ;; CHECK-BINARY-NEXT:   (ref.func $call-ref-more)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $call-ref-more (param $0 i32) (result i32)
+  ;; CHECK-TEXT-NEXT:  (call_ref $i32-i32
+  ;; CHECK-TEXT-NEXT:   (i32.const 42)
+  ;; CHECK-TEXT-NEXT:   (ref.func $call-ref-more)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $call-ref-more (param i32) (result i32)
+    (call_ref $i32-i32 (i32.const 42) (ref.func $call-ref-more))
+  )
+  ;; CHECK-BINARY:      (func $call_from-param (param $f (ref $i32-i32)) (result i32)
+  ;; CHECK-BINARY-NEXT:  (call_ref $i32-i32
+  ;; CHECK-BINARY-NEXT:   (i32.const 42)
+  ;; CHECK-BINARY-NEXT:   (local.get $f)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $call_from-param (param $f (ref $i32-i32)) (result i32)
+  ;; CHECK-TEXT-NEXT:  (call_ref $i32-i32
+  ;; CHECK-TEXT-NEXT:   (i32.const 42)
+  ;; CHECK-TEXT-NEXT:   (local.get $f)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $call_from-param (param $f (ref $i32-i32)) (result i32)
+    (call_ref $i32-i32 (i32.const 42) (local.get $f))
+  )
+  ;; CHECK-BINARY:      (func $call_from-param-null (param $f (ref null $i32-i32)) (result i32)
+  ;; CHECK-BINARY-NEXT:  (call_ref $i32-i32
+  ;; CHECK-BINARY-NEXT:   (i32.const 42)
+  ;; CHECK-BINARY-NEXT:   (local.get $f)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $call_from-param-null (param $f (ref null $i32-i32)) (result i32)
+  ;; CHECK-TEXT-NEXT:  (call_ref $i32-i32
+  ;; CHECK-TEXT-NEXT:   (i32.const 42)
+  ;; CHECK-TEXT-NEXT:   (local.get $f)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $call_from-param-null (param $f (ref null $i32-i32)) (result i32)
+    (call_ref $i32-i32 (i32.const 42) (local.get $f))
+  )
+  ;; CHECK-BINARY:      (func $call_from-local-null (result i32)
+  ;; CHECK-BINARY-NEXT:  (local $f (ref null $i32-i32))
+  ;; CHECK-BINARY-NEXT:  (local.set $f
+  ;; CHECK-BINARY-NEXT:   (ref.func $call-ref-more)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT:  (call_ref $i32-i32
+  ;; CHECK-BINARY-NEXT:   (i32.const 42)
+  ;; CHECK-BINARY-NEXT:   (local.get $f)
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $call_from-local-null (result i32)
+  ;; CHECK-TEXT-NEXT:  (local $f (ref null $i32-i32))
+  ;; CHECK-TEXT-NEXT:  (local.set $f
+  ;; CHECK-TEXT-NEXT:   (ref.func $call-ref-more)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT:  (call_ref $i32-i32
+  ;; CHECK-TEXT-NEXT:   (i32.const 42)
+  ;; CHECK-TEXT-NEXT:   (local.get $f)
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $call_from-local-null (result i32)
+    (local $f (ref null $i32-i32))
+    (local.set $f (ref.func $call-ref-more))
+    (call_ref $i32-i32 (i32.const 42) (local.get $f))
+  )
+  ;; CHECK-BINARY:      (func $ref-in-sig (param $0 f64) (result (ref null $=>eqref))
+  ;; CHECK-BINARY-NEXT:  (ref.null nofunc)
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $ref-in-sig (param $0 f64) (result (ref null $=>eqref))
+  ;; CHECK-TEXT-NEXT:  (ref.null nofunc)
+  ;; CHECK-TEXT-NEXT: )
+  (func $ref-in-sig (param $0 f64) (result (ref null $=>eqref))
+    (ref.null $=>eqref)
+  )
+  ;; CHECK-BINARY:      (func $type-only-in-tuple-local
+  ;; CHECK-BINARY-NEXT:  (local $x i32)
+  ;; CHECK-BINARY-NEXT:  (local $1 f64)
+  ;; CHECK-BINARY-NEXT:  (local $2 (ref null $=>anyref))
+  ;; CHECK-BINARY-NEXT:  (nop)
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $type-only-in-tuple-local
+  ;; CHECK-TEXT-NEXT:  (local $x i32)
+  ;; CHECK-TEXT-NEXT:  (local $1 f64)
+  ;; CHECK-TEXT-NEXT:  (local $2 (ref null $=>anyref))
+  ;; CHECK-TEXT-NEXT:  (nop)
+  ;; CHECK-TEXT-NEXT: )
+  (func $type-only-in-tuple-local
+    (local $x (i32 (ref null $=>anyref) f64))
+  )
+  ;; CHECK-BINARY:      (func $type-only-in-tuple-block
+  ;; CHECK-BINARY-NEXT:  (local $0 (i32 (ref null $mixed_results) f64))
+  ;; CHECK-BINARY-NEXT:  (local $1 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $2 i32)
+  ;; CHECK-BINARY-NEXT:  (local.set $0
+  ;; CHECK-BINARY-NEXT:   (block $label$1 (result i32 (ref null $mixed_results) f64)
+  ;; CHECK-BINARY-NEXT:    (unreachable)
+  ;; CHECK-BINARY-NEXT:   )
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT:  (drop
+  ;; CHECK-BINARY-NEXT:   (block (result i32)
+  ;; CHECK-BINARY-NEXT:    (local.set $2
+  ;; CHECK-BINARY-NEXT:     (tuple.extract 0
+  ;; CHECK-BINARY-NEXT:      (local.get $0)
+  ;; CHECK-BINARY-NEXT:     )
+  ;; CHECK-BINARY-NEXT:    )
+  ;; CHECK-BINARY-NEXT:    (drop
+  ;; CHECK-BINARY-NEXT:     (block (result (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:      (local.set $1
+  ;; CHECK-BINARY-NEXT:       (tuple.extract 1
+  ;; CHECK-BINARY-NEXT:        (local.get $0)
+  ;; CHECK-BINARY-NEXT:       )
+  ;; CHECK-BINARY-NEXT:      )
+  ;; CHECK-BINARY-NEXT:      (drop
+  ;; CHECK-BINARY-NEXT:       (tuple.extract 2
+  ;; CHECK-BINARY-NEXT:        (local.get $0)
+  ;; CHECK-BINARY-NEXT:       )
+  ;; CHECK-BINARY-NEXT:      )
+  ;; CHECK-BINARY-NEXT:      (local.get $1)
+  ;; CHECK-BINARY-NEXT:     )
+  ;; CHECK-BINARY-NEXT:    )
+  ;; CHECK-BINARY-NEXT:    (local.get $2)
+  ;; CHECK-BINARY-NEXT:   )
+  ;; CHECK-BINARY-NEXT:  )
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $type-only-in-tuple-block
+  ;; CHECK-TEXT-NEXT:  (local $0 (i32 (ref null $mixed_results) f64))
+  ;; CHECK-TEXT-NEXT:  (local $1 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $2 i32)
+  ;; CHECK-TEXT-NEXT:  (local.set $0
+  ;; CHECK-TEXT-NEXT:   (block $label$1 (result i32 (ref null $mixed_results) f64)
+  ;; CHECK-TEXT-NEXT:    (unreachable)
+  ;; CHECK-TEXT-NEXT:   )
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT:  (drop
+  ;; CHECK-TEXT-NEXT:   (block (result i32)
+  ;; CHECK-TEXT-NEXT:    (local.set $2
+  ;; CHECK-TEXT-NEXT:     (tuple.extract 0
+  ;; CHECK-TEXT-NEXT:      (local.get $0)
+  ;; CHECK-TEXT-NEXT:     )
+  ;; CHECK-TEXT-NEXT:    )
+  ;; CHECK-TEXT-NEXT:    (drop
+  ;; CHECK-TEXT-NEXT:     (block (result (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:      (local.set $1
+  ;; CHECK-TEXT-NEXT:       (tuple.extract 1
+  ;; CHECK-TEXT-NEXT:        (local.get $0)
+  ;; CHECK-TEXT-NEXT:       )
+  ;; CHECK-TEXT-NEXT:      )
+  ;; CHECK-TEXT-NEXT:      (drop
+  ;; CHECK-TEXT-NEXT:       (tuple.extract 2
+  ;; CHECK-TEXT-NEXT:        (local.get $0)
+  ;; CHECK-TEXT-NEXT:       )
+  ;; CHECK-TEXT-NEXT:      )
+  ;; CHECK-TEXT-NEXT:      (local.get $1)
+  ;; CHECK-TEXT-NEXT:     )
+  ;; CHECK-TEXT-NEXT:    )
+  ;; CHECK-TEXT-NEXT:    (local.get $2)
+  ;; CHECK-TEXT-NEXT:   )
+  ;; CHECK-TEXT-NEXT:  )
+  ;; CHECK-TEXT-NEXT: )
+  (func $type-only-in-tuple-block
+    (drop
+      (block $block (result i32 (ref null $mixed_results) f64)
+        (unreachable)
+      )
+    )
+  )
+  ;; CHECK-BINARY:      (func $ref-types-first
+  ;; CHECK-BINARY-NEXT:  (local $r1 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $r2 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $r3 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r4 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r5 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r6 funcref)
+  ;; CHECK-BINARY-NEXT:  (local $i1 i32)
+  ;; CHECK-BINARY-NEXT:  (local $i2 i64)
+  ;; CHECK-BINARY-NEXT:  (local $i3 i64)
+  ;; CHECK-BINARY-NEXT:  (nop)
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $ref-types-first
+  ;; CHECK-TEXT-NEXT:  (local $r1 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $r2 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $r3 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r4 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r5 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r6 funcref)
+  ;; CHECK-TEXT-NEXT:  (local $i1 i32)
+  ;; CHECK-TEXT-NEXT:  (local $i2 i64)
+  ;; CHECK-TEXT-NEXT:  (local $i3 i64)
+  ;; CHECK-TEXT-NEXT:  (nop)
+  ;; CHECK-TEXT-NEXT: )
+  (func $ref-types-first
+    ;; 6 reference types and 3 MVP types. The binary format should emit all the
+    ;; reference types first since a reference type appears first. In addition,
+    ;; types should be emitted in blocks there, that is, locals of identical
+    ;; types should be adjacent.
+    (local $r1 (ref null $mixed_results))
+    (local $r2 (ref null $mixed_results))
+    (local $i1 i32)
+    (local $r3 anyref)
+    (local $i2 i64)
+    (local $r4 anyref)
+    (local $i3 i64)
+    (local $r5 anyref)
+    (local $r6 funcref)
+  )
+  ;; CHECK-BINARY:      (func $mvp-types-first
+  ;; CHECK-BINARY-NEXT:  (local $i1 i32)
+  ;; CHECK-BINARY-NEXT:  (local $i2 i64)
+  ;; CHECK-BINARY-NEXT:  (local $i3 i64)
+  ;; CHECK-BINARY-NEXT:  (local $r1 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $r2 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $r3 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r4 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r5 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r6 funcref)
+  ;; CHECK-BINARY-NEXT:  (nop)
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $mvp-types-first
+  ;; CHECK-TEXT-NEXT:  (local $i1 i32)
+  ;; CHECK-TEXT-NEXT:  (local $i2 i64)
+  ;; CHECK-TEXT-NEXT:  (local $i3 i64)
+  ;; CHECK-TEXT-NEXT:  (local $r1 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $r2 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $r3 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r4 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r5 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r6 funcref)
+  ;; CHECK-TEXT-NEXT:  (nop)
+  ;; CHECK-TEXT-NEXT: )
+  (func $mvp-types-first
+    ;; Reversed from before, now an MVP type appears first, so they should all
+    ;; be before reference types in the binary format.
+    (local $i1 i32) ;; only this local was moved up.
+    (local $r1 (ref null $mixed_results))
+    (local $r2 (ref null $mixed_results))
+    (local $r3 anyref)
+    (local $i2 i64)
+    (local $r4 anyref)
+    (local $i3 i64)
+    (local $r5 anyref)
+    (local $r6 funcref)
+  )
+  ;; CHECK-BINARY:      (func $mvp-types-first-param (param $r0 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $i1 i32)
+  ;; CHECK-BINARY-NEXT:  (local $i2 i64)
+  ;; CHECK-BINARY-NEXT:  (local $i3 i64)
+  ;; CHECK-BINARY-NEXT:  (local $r1 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $r2 (ref null $mixed_results))
+  ;; CHECK-BINARY-NEXT:  (local $r3 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r4 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r5 anyref)
+  ;; CHECK-BINARY-NEXT:  (local $r6 funcref)
+  ;; CHECK-BINARY-NEXT:  (nop)
+  ;; CHECK-BINARY-NEXT: )
+  ;; CHECK-TEXT:      (func $mvp-types-first-param (param $r0 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $i1 i32)
+  ;; CHECK-TEXT-NEXT:  (local $i2 i64)
+  ;; CHECK-TEXT-NEXT:  (local $i3 i64)
+  ;; CHECK-TEXT-NEXT:  (local $r1 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $r2 (ref null $mixed_results))
+  ;; CHECK-TEXT-NEXT:  (local $r3 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r4 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r5 anyref)
+  ;; CHECK-TEXT-NEXT:  (local $r6 funcref)
+  ;; CHECK-TEXT-NEXT:  (nop)
+  ;; CHECK-TEXT-NEXT: )
+  (func $mvp-types-first-param (param $r0 (ref null $mixed_results))
+    ;; As before, but now there is a reference type *parameter*. We should
+    ;; ignore that and sort as in the last function.
+    (local $i1 i32) ;; only this local was moved up.
+    (local $r1 (ref null $mixed_results))
+    (local $r2 (ref null $mixed_results))
+    (local $r3 anyref)
+    (local $i2 i64)
+    (local $r4 anyref)
+    (local $i3 i64)
+    (local $r5 anyref)
+    (local $r6 funcref)
+  )
+)
+;; CHECK-NODEBUG:      (type $none_=>_anyref_f32_anyref_f32 (func (result anyref f32 anyref f32)))
+
+;; CHECK-NODEBUG:      (type $none_=>_none (func))
+
+;; CHECK-NODEBUG:      (type $i32_=>_i32 (func (param i32) (result i32)))
+
+;; CHECK-NODEBUG:      (type $ref|i32_->_i32|_=>_i32 (func (param (ref $i32_=>_i32)) (result i32)))
+
+;; CHECK-NODEBUG:      (type $ref?|i32_->_i32|_=>_i32 (func (param (ref null $i32_=>_i32)) (result i32)))
+
+;; CHECK-NODEBUG:      (type $none_=>_i32 (func (result i32)))
+
+;; CHECK-NODEBUG:      (type $f64_=>_ref?|none_->_eqref| (func (param f64) (result (ref null $none_=>_eqref))))
+
+;; CHECK-NODEBUG:      (type $none_=>_anyref (func (result anyref)))
+
+;; CHECK-NODEBUG:      (type $none_=>_i32_ref?|none_->_anyref_f32_anyref_f32|_f64 (func (result i32 (ref null $none_=>_anyref_f32_anyref_f32) f64)))
+
+;; CHECK-NODEBUG:      (type $ref?|none_->_anyref_f32_anyref_f32|_=>_none (func (param (ref null $none_=>_anyref_f32_anyref_f32))))
+
+;; CHECK-NODEBUG:      (type $none_=>_eqref (func (result eqref)))
+
+;; CHECK-NODEBUG:      (elem declare func $0 $2)
+
+;; CHECK-NODEBUG:      (func $0
+;; CHECK-NODEBUG-NEXT:  (call_ref $none_=>_none
+;; CHECK-NODEBUG-NEXT:   (ref.func $0)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $1
+;; CHECK-NODEBUG-NEXT:  (return_call_ref $none_=>_none
+;; CHECK-NODEBUG-NEXT:   (ref.func $0)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $2 (param $0 i32) (result i32)
+;; CHECK-NODEBUG-NEXT:  (call_ref $i32_=>_i32
+;; CHECK-NODEBUG-NEXT:   (i32.const 42)
+;; CHECK-NODEBUG-NEXT:   (ref.func $2)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $3 (param $0 (ref $i32_=>_i32)) (result i32)
+;; CHECK-NODEBUG-NEXT:  (call_ref $i32_=>_i32
+;; CHECK-NODEBUG-NEXT:   (i32.const 42)
+;; CHECK-NODEBUG-NEXT:   (local.get $0)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $4 (param $0 (ref null $i32_=>_i32)) (result i32)
+;; CHECK-NODEBUG-NEXT:  (call_ref $i32_=>_i32
+;; CHECK-NODEBUG-NEXT:   (i32.const 42)
+;; CHECK-NODEBUG-NEXT:   (local.get $0)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $5 (result i32)
+;; CHECK-NODEBUG-NEXT:  (local $0 (ref null $i32_=>_i32))
+;; CHECK-NODEBUG-NEXT:  (local.set $0
+;; CHECK-NODEBUG-NEXT:   (ref.func $2)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT:  (call_ref $i32_=>_i32
+;; CHECK-NODEBUG-NEXT:   (i32.const 42)
+;; CHECK-NODEBUG-NEXT:   (local.get $0)
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $6 (param $0 f64) (result (ref null $none_=>_eqref))
+;; CHECK-NODEBUG-NEXT:  (ref.null nofunc)
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $7
+;; CHECK-NODEBUG-NEXT:  (local $0 i32)
+;; CHECK-NODEBUG-NEXT:  (local $1 f64)
+;; CHECK-NODEBUG-NEXT:  (local $2 (ref null $none_=>_anyref))
+;; CHECK-NODEBUG-NEXT:  (nop)
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $8
+;; CHECK-NODEBUG-NEXT:  (local $0 (i32 (ref null $none_=>_anyref_f32_anyref_f32) f64))
+;; CHECK-NODEBUG-NEXT:  (local $1 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $2 i32)
+;; CHECK-NODEBUG-NEXT:  (local.set $0
+;; CHECK-NODEBUG-NEXT:   (block $label$1 (result i32 (ref null $none_=>_anyref_f32_anyref_f32) f64)
+;; CHECK-NODEBUG-NEXT:    (unreachable)
+;; CHECK-NODEBUG-NEXT:   )
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT:  (drop
+;; CHECK-NODEBUG-NEXT:   (block (result i32)
+;; CHECK-NODEBUG-NEXT:    (local.set $2
+;; CHECK-NODEBUG-NEXT:     (tuple.extract 0
+;; CHECK-NODEBUG-NEXT:      (local.get $0)
+;; CHECK-NODEBUG-NEXT:     )
+;; CHECK-NODEBUG-NEXT:    )
+;; CHECK-NODEBUG-NEXT:    (drop
+;; CHECK-NODEBUG-NEXT:     (block (result (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:      (local.set $1
+;; CHECK-NODEBUG-NEXT:       (tuple.extract 1
+;; CHECK-NODEBUG-NEXT:        (local.get $0)
+;; CHECK-NODEBUG-NEXT:       )
+;; CHECK-NODEBUG-NEXT:      )
+;; CHECK-NODEBUG-NEXT:      (drop
+;; CHECK-NODEBUG-NEXT:       (tuple.extract 2
+;; CHECK-NODEBUG-NEXT:        (local.get $0)
+;; CHECK-NODEBUG-NEXT:       )
+;; CHECK-NODEBUG-NEXT:      )
+;; CHECK-NODEBUG-NEXT:      (local.get $1)
+;; CHECK-NODEBUG-NEXT:     )
+;; CHECK-NODEBUG-NEXT:    )
+;; CHECK-NODEBUG-NEXT:    (local.get $2)
+;; CHECK-NODEBUG-NEXT:   )
+;; CHECK-NODEBUG-NEXT:  )
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $9
+;; CHECK-NODEBUG-NEXT:  (local $0 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $1 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $2 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $3 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $4 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $5 funcref)
+;; CHECK-NODEBUG-NEXT:  (local $6 i32)
+;; CHECK-NODEBUG-NEXT:  (local $7 i64)
+;; CHECK-NODEBUG-NEXT:  (local $8 i64)
+;; CHECK-NODEBUG-NEXT:  (nop)
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $10
+;; CHECK-NODEBUG-NEXT:  (local $0 i32)
+;; CHECK-NODEBUG-NEXT:  (local $1 i64)
+;; CHECK-NODEBUG-NEXT:  (local $2 i64)
+;; CHECK-NODEBUG-NEXT:  (local $3 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $4 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $5 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $6 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $7 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $8 funcref)
+;; CHECK-NODEBUG-NEXT:  (nop)
+;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG:      (func $11 (param $0 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $1 i32)
+;; CHECK-NODEBUG-NEXT:  (local $2 i64)
+;; CHECK-NODEBUG-NEXT:  (local $3 i64)
+;; CHECK-NODEBUG-NEXT:  (local $4 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $5 (ref null $none_=>_anyref_f32_anyref_f32))
+;; CHECK-NODEBUG-NEXT:  (local $6 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $7 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $8 anyref)
+;; CHECK-NODEBUG-NEXT:  (local $9 funcref)
+;; CHECK-NODEBUG-NEXT:  (nop)
+;; CHECK-NODEBUG-NEXT: )
diff --git a/test/lit/validation/bad-non-nullable-locals.wast b/test/lit/validation/bad-non-nullable-locals.wast
new file mode 100644
index 0000000..f227715
--- /dev/null
+++ b/test/lit/validation/bad-non-nullable-locals.wast
@@ -0,0 +1,61 @@
+;; RUN: foreach %s %t not wasm-opt -all 2>&1 | filecheck %s
+
+;; CHECK: non-nullable local's sets must dominate gets
+(module
+  (func $inner-to-func
+    ;; a set in an inner scope does *not* help a get validate.
+    (local $x (ref func))
+    (block $b
+      (local.set $x
+        (ref.func $helper)
+      )
+    )
+    (drop
+      (local.get $x)
+    )
+  )
+
+  (func $helper)
+)
+
+;; CHECK: non-nullable local's sets must dominate gets
+(module
+  (func $get-without-set
+    (local $x (ref func))
+    (drop
+      (local.get $x)
+    )
+  )
+
+  (func $helper)
+)
+
+;; CHECK: non-nullable local's sets must dominate gets
+(module
+  (func $get-before-set
+    (local $x (ref func))
+    (local.set $x
+      (local.get $x)
+    )
+  )
+
+  (func $helper)
+)
+
+;; CHECK: non-nullable local's sets must dominate gets
+(module
+  (func $if-arms
+    (local $x (ref func))
+    (if
+      (i32.const 1)
+      ;; Superficially the order is right, but not really.
+      (local.set $x
+        (ref.func $helper)
+      )
+      (local.get $x)
+    )
+  )
+
+  (func $helper)
+)
+
diff --git a/test/lit/validation/eqref.wast b/test/lit/validation/eqref.wast
new file mode 100644
index 0000000..4c97ad3
--- /dev/null
+++ b/test/lit/validation/eqref.wast
@@ -0,0 +1,15 @@
+;; Test for eqref validating only with GC, and not just reference types, even
+;; when only declared in a null.
+
+;; RUN: not wasm-opt --enable-reference-types %s 2>&1 | filecheck %s --check-prefix NO-GC
+;; RUN:     wasm-opt --enable-reference-types --enable-gc %s -o - -S | filecheck %s --check-prefix GC
+
+;; NO-GC: all used types should be allowed
+
+;; GC:   (func $foo (param $x eqref)
+
+(module
+  (func $foo (param $x eqref)
+    (nop)
+  )
+)
diff --git a/test/lit/validation/intrinsics.wast b/test/lit/validation/intrinsics.wast
new file mode 100644
index 0000000..6437b22
--- /dev/null
+++ b/test/lit/validation/intrinsics.wast
@@ -0,0 +1,23 @@
+;; Test for a validation error on bad usage of call.without.effects
+
+;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s
+
+;; CHECK: param number must match
+
+(module
+  (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param i32 funcref) (result i32)))
+
+  (func "get-ref" (result i32)
+    ;; This call-without-effects is done to a $func, but $func has the wrong
+    ;; signature - it lacks the i32 parameter.
+    (call $cwe
+      (i32.const 41)
+      (ref.func $func)
+    )
+  )
+
+  (func $func (result i32)
+    (i32.const 1)
+  )
+)
+
diff --git a/test/lit/validation/nn-locals-bad-call_ref.wast b/test/lit/validation/nn-locals-bad-call_ref.wast
new file mode 100644
index 0000000..9a96ed4
--- /dev/null
+++ b/test/lit/validation/nn-locals-bad-call_ref.wast
@@ -0,0 +1,32 @@
+;; Test for validation of non-nullable locals
+
+;; RUN: not wasm-opt -all --enable-gc-nn-locals %s 2>&1 | filecheck %s
+
+;; CHECK: non-nullable local must not read null
+
+(module
+  (tag $tag (param i32))
+  (type $void (func))
+  (func $func
+    (local $0 (ref any))
+    (try
+      (do
+        (call_ref $void
+          (ref.func $func)
+        )
+      )
+      (catch $tag
+        (drop
+          (pop (i32))
+        )
+        ;; The path to here is from a possible exception thrown in the call_ref.
+        ;; This is a regression test for call_ref not being seen as possibly
+        ;; throwing. We should see a validation error here, as the local.get is
+        ;; of a null default, and it *is* reachable thanks to the call_ref.
+        (drop
+          (local.get $0)
+        )
+      )
+    )
+  )
+)
diff --git a/test/lit/validation/nn-locals-bad.wast b/test/lit/validation/nn-locals-bad.wast
new file mode 100644
index 0000000..17a5c14
--- /dev/null
+++ b/test/lit/validation/nn-locals-bad.wast
@@ -0,0 +1,15 @@
+;; Test for validation of non-nullable locals
+
+;; RUN: not wasm-opt -all --enable-gc-nn-locals %s 2>&1 | filecheck %s
+
+;; CHECK: non-nullable local must not read null
+
+(module
+  (func $foo
+    (local $nn (ref any))
+    ;; It is not ok to read a non-nullable local.
+    (drop
+      (local.get $nn)
+    )
+  )
+)
diff --git a/test/lit/validation/nn-locals-ok.wast b/test/lit/validation/nn-locals-ok.wast
new file mode 100644
index 0000000..1dfd209
--- /dev/null
+++ b/test/lit/validation/nn-locals-ok.wast
@@ -0,0 +1,14 @@
+;; Test for validation of non-nullable locals
+
+;; RUN: wasm-opt -all --enable-gc-nn-locals %s --print | filecheck %s
+
+;; CHECK: (module
+
+(module
+  (func $foo (param $nn (ref any))
+    ;; It is ok to read a non-nullable param.
+    (drop
+      (local.get $nn)
+    )
+  )
+)
diff --git a/test/lit/validation/nn-tuples.wast b/test/lit/validation/nn-tuples.wast
index 452a6c7..5ecf55f 100644
--- a/test/lit/validation/nn-tuples.wast
+++ b/test/lit/validation/nn-tuples.wast
@@ -1,16 +1,15 @@
-;; Test for non-nullable types in tuples
-
-;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s --check-prefix NO-NN-LOCALS
-;; RUN:     wasm-opt -all %s --enable-gc-nn-locals -o - -S | filecheck %s --check-prefix NN-LOCALS
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
 
-;; NO-NN-LOCALS: vars must be defaultable
+;; RUN: wasm-opt %s -all -S -o - \
+;; RUN:   | filecheck %s
 
-;; NN-LOCALS: (module
-;; NN-LOCALS:  (local $tuple ((ref any) (ref any)))
-;; NN-LOCALS:  (nop)
-;; NN-LOCALS: )
+;; Test for non-nullable types in tuples
 
 (module
+  ;; CHECK:      (func $foo
+  ;; CHECK-NEXT:  (local $tuple ((ref any) (ref any)))
+  ;; CHECK-NEXT:  (nop)
+  ;; CHECK-NEXT: )
   (func $foo
     (local $tuple ((ref any) (ref any)))
   )
diff --git a/test/lit/validation/shared-memory.wast b/test/lit/validation/shared-memory.wast
index 259e89b..0312875 100644
--- a/test/lit/validation/shared-memory.wast
+++ b/test/lit/validation/shared-memory.wast
@@ -3,7 +3,7 @@
 ;; RUN: not wasm-opt %s 2>&1 | filecheck %s --check-prefix NO-ATOMICS
 ;; RUN: wasm-opt %s --enable-threads -o - -S | filecheck %s --check-prefix ATOMICS
 
-;; NO-ATOMICS: memory is shared, but atomics are disabled
+;; NO-ATOMICS: shared memory requires threads [--enable-threads]
 ;; ATOMICS: (memory $0 (shared 10 20))
 
 (module
diff --git a/test/lit/wasm-emscripten-finalize/em_asm.wat b/test/lit/wasm-emscripten-finalize/em_asm.wat
deleted file mode 100644
index 43dbfb9..0000000
--- a/test/lit/wasm-emscripten-finalize/em_asm.wat
+++ /dev/null
@@ -1,29 +0,0 @@
-;; Test that em_asm string are extracted correctly when the __start_em_asm
-;; and __stop_em_asm globals are exported.
-
-;; RUN: wasm-emscripten-finalize %s -S | filecheck %s
-
-;; Check that the data segment that contains only EM_ASM strings resized to
-;; zero, and that the string are extracted into the metadata.
-
-;; CHECK:      (data (i32.const 100) "normal data")
-;; CHECK-NEXT: (data (i32.const 512) "")
-;; CHECK-NEXT: (data (i32.const 1024) "more data")
-
-;; CHECK:       "asmConsts": {
-;; CHECK-NEXT:     "512": "{ console.log('JS hello'); }",
-;; CHECK-NEXT:     "541": "{ console.log('hello again'); }"
-;; CHECK-NEXT:   },
-
-;; Check that the exports are removed
-;; CHECK-NOT: export
-
-(module
- (memory 1 1)
- (global (export "__start_em_asm") i32 (i32.const 512))
- (global (export "__stop_em_asm") i32 (i32.const 573))
-
- (data (i32.const 100) "normal data")
- (data (i32.const 512) "{ console.log('JS hello'); }\00{ console.log('hello again'); }\00")
- (data (i32.const 1024) "more data")
-)
diff --git a/test/lit/wasm-emscripten-finalize/em_asm_partial.wat b/test/lit/wasm-emscripten-finalize/em_asm_partial.wat
deleted file mode 100644
index 6432f16..0000000
--- a/test/lit/wasm-emscripten-finalize/em_asm_partial.wat
+++ /dev/null
@@ -1,24 +0,0 @@
-;; Test that em_asm string are extraced correctly when the __start_em_asm
-;; and __stop_em_asm globals are exported.
-
-;; RUN: wasm-emscripten-finalize %s -S | filecheck %s
-
-;; Check for the case when __start_em_asm and __stop_em_asm don't define an
-;; entire segment. In this case we preserve the segment but zero the data.
-
-;; CHECK: (data (i32.const 512) "xx\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00yy")
-
-;; CHECK:       "asmConsts": {
-;; CHECK-NEXT:     "514": "{ console.log('JS hello'); }",
-;; CHECK-NEXT:     "543": "{ console.log('hello again'); }"
-;; CHECK-NEXT:   },
-
-;; Check that the exports are removed
-;; CHECK-NOT: export
-
-(module
- (memory 1 1)
- (global (export "__start_em_asm") i32 (i32.const 514))
- (global (export "__stop_em_asm") i32 (i32.const 575))
- (data (i32.const 512) "xx{ console.log('JS hello'); }\00{ console.log('hello again'); }\00yy")
-)
diff --git a/test/lit/wasm-emscripten-finalize/em_js.wat b/test/lit/wasm-emscripten-finalize/em_js.wat
deleted file mode 100644
index 02fc32b..0000000
--- a/test/lit/wasm-emscripten-finalize/em_js.wat
+++ /dev/null
@@ -1,35 +0,0 @@
-;; Test that funcions exported with __em_js are correctly removed
-;; once they strings they return are extracted.
-
-;; RUN: wasm-emscripten-finalize %s -S | filecheck %s
-
-;; All functions should be stripped from the binary, regardless
-;; of internal name
-;; CHECK-NOT: (global
-
-;; The data section that contains only em_js strings should
-;; be stripped (shrunk to zero size):
-;; CHECK: (data (i32.const 1024) "some JS string data\00xxx")
-;; CHECK: (data (i32.const 512) "")
-;; CHECK: (data (i32.const 2048) "more JS string data\00yyy")
-
-;;      CHECK:  "emJsFuncs": {
-;; CHECK-NEXT:    "bar": "more JS string data",
-;; CHECK-NEXT:    "baz": "Only em_js strings here",
-;; CHECK-NEXT:    "foo": "some JS string data"
-;; CHECK-NEXT:  },
-
-(module
- (memory 1 1)
- (data (i32.const 1024) "some JS string data\00xxx")
- (data (i32.const 512) "Only em_js strings here\00")
- (data (i32.const 2048) "more JS string data\00yyy")
- (export "__em_js__foo" (global $__em_js__foo))
- (export "__em_js__bar" (global $bar))
- (export "__em_js__baz" (global $baz))
- ;; Name matches export name
- (global $__em_js__foo i32 (i32.const 1024))
- ;; Name does not match export name
- (global $bar i32 (i32.const 2048))
- (global $baz i32 (i32.const 512))
-)
diff --git a/test/lit/wasm-emscripten-finalize/passive-pic.wat b/test/lit/wasm-emscripten-finalize/passive-pic.wat
deleted file mode 100644
index 63e2cb7..0000000
--- a/test/lit/wasm-emscripten-finalize/passive-pic.wat
+++ /dev/null
@@ -1,40 +0,0 @@
-;; Test that wasm-emscripten-finalize can locate data within passive segments
-;; even when compiled with PIC, which means that segment addresses are non-constant.
-
-;; RUN: wasm-emscripten-finalize --enable-bulk-memory %s -o out.wasm | filecheck %s
-
-;; CHECK:  "asmConsts": {
-;; CHECK:    "3": "hello"
-;; CHECK:  },
-
-(module
- (import "env" "memory" (memory $memory 1 1))
- (import "env" "__memory_base" (global $__memory_base i32))
- (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i32 i32 i32) (result i32)))
- (data "xxxhello\00yyy")
- (global (export "__start_em_asm") i32 (i32.const 3))
- (global (export "__stop_em_asm") i32 (i32.const 9))
- ;; memory init function similar to those generated by wasm-ld
- (start $__wasm_init_memory)
- (func $__wasm_init_memory
-  (memory.init 0
-   (i32.add
-    (i32.const 0)
-    (global.get $__memory_base)
-   )
-   (i32.const 0)
-   (i32.const 12)
-  )
- )
- ;; EM_ASM call passing string at address 3 in the passive segment
- (func $foo (result i32)
-  (call $emscripten_asm_const_int
-   (i32.add
-    (global.get $__memory_base)
-    (i32.const 3)
-   )
-   (i32.const 0)
-   (i32.const 0)
-  )
- )
-)
diff --git a/test/lit/wasm-split/export-name-already-exists.wast b/test/lit/wasm-split/export-name-already-exists.wast
index 708929e..8395179 100644
--- a/test/lit/wasm-split/export-name-already-exists.wast
+++ b/test/lit/wasm-split/export-name-already-exists.wast
@@ -4,5 +4,6 @@
 ;; CHECK: error: Export foo already exists.
 
 (module
+  (memory 0 0)
   (export "foo" (memory 0 0))
 )
diff --git a/test/lit/wasm-split/instrument-in-memory.wast b/test/lit/wasm-split/instrument-in-memory.wast
index 568ce22..3a3c19d 100644
--- a/test/lit/wasm-split/instrument-in-memory.wast
+++ b/test/lit/wasm-split/instrument-in-memory.wast
@@ -7,6 +7,7 @@
 (module
   (import "env" "foo" (func $foo))
   (export "bar" (func $bar))
+  (memory $0 1 1)
   (func $bar
     (call $foo)
   )
diff --git a/test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast b/test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast
new file mode 100644
index 0000000..9035a0b
--- /dev/null
+++ b/test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast
@@ -0,0 +1,31 @@
+;; RUN: wasm-split %s --instrument --in-secondary-memory --import-namespace=custom_env --secondary-memory-name=custom_name -all -S -o - | filecheck %s
+
+;; Check that the output round trips and validates as well
+;; RUN: wasm-split %s --instrument --in-secondary-memory -all -g -o %t.wasm
+;; RUN: wasm-opt -all %t.wasm -S -o -
+
+(module
+  (import "env" "foo" (func $foo))
+  (export "bar" (func $bar))
+  (memory $0 1 1)
+  (func $bar
+    (call $foo)
+  )
+  (func $baz (param i32) (result i32)
+    (local.get 0)
+  )
+)
+
+;; Check that a memory import has been added for secondary memory
+;; CHECK: (import "custom_env" "custom_name" (memory $custom_name (shared 1 1)))
+
+;; And the profiling function exported
+;; CHECK: (export "__write_profile" (func $__write_profile))
+
+;; And main memory has been exported
+;; CHECK: (export "profile-memory" (memory $0))
+
+;; Check that the function instrumentation uses the correct memory name
+;; CHECK:  (i32.atomic.store8 $custom_name
+;; CHECK:  (i32.atomic.store8 $custom_name offset=1
+;; CHECK:  (i32.atomic.load8_u $custom_name
diff --git a/test/lit/wasm-split/instrument-in-secondary-memory.wast b/test/lit/wasm-split/instrument-in-secondary-memory.wast
new file mode 100644
index 0000000..3e01229
--- /dev/null
+++ b/test/lit/wasm-split/instrument-in-secondary-memory.wast
@@ -0,0 +1,92 @@
+;; RUN: wasm-split %s --instrument --in-secondary-memory -all -S -o - | filecheck %s
+
+;; Check that the output round trips and validates as well
+;; RUN: wasm-split %s --instrument --in-secondary-memory -all -g -o %t.wasm
+;; RUN: wasm-opt -all %t.wasm -S -o -
+
+(module
+  (import "env" "foo" (func $foo))
+  (export "bar" (func $bar))
+  (memory $0 1 1)
+  (func $bar
+    (call $foo)
+  )
+  (func $baz (param i32) (result i32)
+    (local.get 0)
+  )
+)
+
+;; Check that a memory import has been added for secondary memory
+;; CHECK: (import "env" "profile-data" (memory $profile-data (shared 1 1)))
+
+;; And the profiling function exported
+;; CHECK: (export "__write_profile" (func $__write_profile))
+
+;; And main memory has been exported
+;; CHECK: (export "profile-memory" (memory $0))
+
+;; Check that the function instrumentation is correct
+
+;; CHECK:      (func $bar
+;; CHECK-NEXT:  (i32.atomic.store8 $profile-data
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:   (i32.const 1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (call $foo)
+;; CHECK-NEXT: )
+
+;; CHECK-NEXT: (func $baz (param $0 i32) (result i32)
+;; CHECK-NEXT:  (i32.atomic.store8 $profile-data offset=1
+;; CHECK-NEXT:   (i32.const 0)
+;; CHECK-NEXT:   (i32.const 1)
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (local.get $0)
+;; CHECK-NEXT: )
+
+;; Check that the profiling function is correct.
+
+;; CHECK:      (func $__write_profile (param $addr i32) (param $size i32) (result i32)
+;; CHECK-NEXT:  (local $funcIdx i32)
+;; CHECK-NEXT:  (if
+;; CHECK-NEXT:   (i32.ge_u
+;; CHECK-NEXT:    (local.get $size)
+;; CHECK-NEXT:    (i32.const 16)
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:   (block
+;; CHECK-NEXT:    (i64.store $0 align=1
+;; CHECK-NEXT:     (local.get $addr)
+;; CHECK-NEXT:     (i64.const {{.*}})
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:    (block $outer
+;; CHECK-NEXT:     (loop $l
+;; CHECK-NEXT:      (br_if $outer
+;; CHECK-NEXT:       (i32.eq
+;; CHECK-NEXT:        (local.get $funcIdx)
+;; CHECK-NEXT:        (i32.const 2)
+;; CHECK-NEXT:       )
+;; CHECK-NEXT:      )
+;; CHECK-NEXT:      (i32.store $0 offset=8
+;; CHECK-NEXT:       (i32.add
+;; CHECK-NEXT:        (local.get $addr)
+;; CHECK-NEXT:        (i32.mul
+;; CHECK-NEXT:         (local.get $funcIdx)
+;; CHECK-NEXT:         (i32.const 4)
+;; CHECK-NEXT:        )
+;; CHECK-NEXT:       )
+;; CHECK-NEXT:       (i32.atomic.load8_u $profile-data
+;; CHECK-NEXT:        (local.get $funcIdx)
+;; CHECK-NEXT:       )
+;; CHECK-NEXT:      )
+;; CHECK-NEXT:      (local.set $funcIdx
+;; CHECK-NEXT:       (i32.add
+;; CHECK-NEXT:        (local.get $funcIdx)
+;; CHECK-NEXT:        (i32.const 1)
+;; CHECK-NEXT:       )
+;; CHECK-NEXT:      )
+;; CHECK-NEXT:      (br $l)
+;; CHECK-NEXT:     )
+;; CHECK-NEXT:    )
+;; CHECK-NEXT:   )
+;; CHECK-NEXT:  )
+;; CHECK-NEXT:  (i32.const 16)
+;; CHECK-NEXT: )
diff --git a/test/lit/wasm-split/invalid-options.wast b/test/lit/wasm-split/invalid-options.wast
index 2f9c0a1..c5a56b5 100644
--- a/test/lit/wasm-split/invalid-options.wast
+++ b/test/lit/wasm-split/invalid-options.wast
@@ -17,14 +17,9 @@
 ;; RUN: not wasm-split %s --instrument --symbolmap 2>&1 \
 ;; RUN:   | filecheck %s --check-prefix INSTRUMENT-SYMBOLMAP
 
-;; --instrument cannot be used with --import-namespace
-;; RUN: not wasm-split %s --instrument --import-namespace=foo 2>&1 \
-;; RUN:   | filecheck %s --check-prefix INSTRUMENT-IMPORT-NS
-
 ;; --instrument cannot be used with --placeholder-namespace
 ;; RUN: not wasm-split %s --instrument --placeholder-namespace=foo 2>&1 \
 ;; RUN:   | filecheck %s --check-prefix INSTRUMENT-PLACEHOLDER-NS
-
 ;; --instrument cannot be used with --export-prefix
 ;; RUN: not wasm-split %s --instrument --export-prefix=foo 2>&1 \
 ;; RUN:   | filecheck %s --check-prefix INSTRUMENT-EXPORT-PREFIX
@@ -45,6 +40,10 @@
 ;; RUN: not wasm-split %s --profile-export=foo 2>&1 \
 ;; RUN:   | filecheck %s --check-prefix SPLIT-PROFILE-EXPORT
 
+;; --secondary-memory-name cannot be used with Split mode
+;; RUN: not wasm-split %s --secondary-memory-name=foo 2>&1 \
+;; RUN:   | filecheck %s --check-prefix SPLIT-SECONDARY-MEMORY-NAME
+
 ;; -S cannot be used with --merge-profiles
 ;; RUN: not wasm-split %s --merge-profiles -S 2>&1 \
 ;; RUN:   | filecheck %s --check-prefix MERGE-EMIT-TEXT
@@ -73,8 +72,6 @@
 
 ;; INSTRUMENT-SYMBOLMAP: error: Option --symbolmap cannot be used in instrument mode.
 
-;; INSTRUMENT-IMPORT-NS: error: Option --import-namespace cannot be used in instrument mode.
-
 ;; INSTRUMENT-PLACEHOLDER-NS: error: Option --placeholder-namespace cannot be used in instrument mode.
 
 ;; INSTRUMENT-EXPORT-PREFIX: error: Option --export-prefix cannot be used in instrument mode.
@@ -87,6 +84,8 @@
 
 ;; SPLIT-PROFILE-EXPORT: error: Option --profile-export cannot be used in split mode.
 
+;; SPLIT-SECONDARY-MEMORY-NAME: error: Option --secondary-memory-name cannot be used in split mode.
+
 ;; MERGE-EMIT-TEXT: error: Option --emit-text cannot be used in merge-profiles mode.
 
 ;; MERGE-DEBUGINFO: error: Option --debuginfo cannot be used in merge-profiles mode.
diff --git a/test/lit/wasm-split/merge-profiles.wast b/test/lit/wasm-split/merge-profiles.wast
index fdaf667..2d09fef 100644
--- a/test/lit/wasm-split/merge-profiles.wast
+++ b/test/lit/wasm-split/merge-profiles.wast
@@ -22,6 +22,7 @@
 ;; SPLIT-NEXT: Splitting out functions: qux{{$}}
 
 (module
+  (memory 0 0)
   (export "memory" (memory 0 0))
   (export "foo" (func $foo))
   (export "bar" (func $bar))
diff --git a/test/lit/wasm-split/mismatched-hashes.wast b/test/lit/wasm-split/mismatched-hashes.wast
index fd3ebb8..347fb17 100644
--- a/test/lit/wasm-split/mismatched-hashes.wast
+++ b/test/lit/wasm-split/mismatched-hashes.wast
@@ -19,5 +19,6 @@
 ;; RUN: wasm-split %s --profile=%t.prof -o1 %t.1.wasm -o2 %t.2.wasm
 
 (module
+ (memory 0 0)
  (export "memory" (memory 0 0))
 )
diff --git a/test/lit/wasm-split/print-profile.wast b/test/lit/wasm-split/print-profile.wast
new file mode 100644
index 0000000..cea7016
--- /dev/null
+++ b/test/lit/wasm-split/print-profile.wast
@@ -0,0 +1,28 @@
+;; Instrument the module
+;; RUN: wasm-split --instrument %s -o %t.instrumented.wasm -g
+
+;; Generate profile
+;; RUN: node %S/call_exports.mjs %t.instrumented.wasm %t.foo.prof foo
+
+;; Print profile
+;; RUN: wasm-split %s --print-profile=%t.foo.prof | filecheck %s --check-prefix=ESCAPED
+
+;; Print profile + unescape function names
+;; RUN: wasm-split %s --print-profile=%t.foo.prof --unescape | filecheck %s --check-prefix=UNESCAPED
+
+;; ESCAPED: - bar\28double\5b3\5d\29
+
+;; UNESCAPED: - bar(double[3])
+
+(module
+  (memory 0 0)
+  (export "memory" (memory 0 0))
+  (export "foo" (func $foo))
+  (export "bar" (func $bar\28double\5b3\5d\29))
+  (func $foo
+    (nop)
+  )
+  (func $bar\28double\5b3\5d\29
+    (nop)
+  )
+)
diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast
new file mode 100644
index 0000000..ca81a8a
--- /dev/null
+++ b/test/lit/wat-kitchen-sink.wast
@@ -0,0 +1,1370 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt --new-wat-parser --hybrid -all %s -S -o - | filecheck %s
+
+(module $parse
+ ;; types
+
+ ;; CHECK:      (type $pair (struct_subtype (field (mut i32)) (field (mut i64)) data))
+
+ ;; CHECK:      (type $void (func_subtype func))
+
+ ;; CHECK:      (type $none_=>_i32 (func_subtype (result i32) func))
+
+ ;; CHECK:      (type $ret2 (func_subtype (result i32 i32) func))
+ (type $ret2 (func (result i32 i32)))
+
+ (rec
+  ;; CHECK:      (type $i32_i64_=>_none (func_subtype (param i32 i64) func))
+
+  ;; CHECK:      (type $i32_=>_none (func_subtype (param i32) func))
+
+  ;; CHECK:      (type $v128_i32_=>_v128 (func_subtype (param v128 i32) (result v128) func))
+
+  ;; CHECK:      (type $many (func_subtype (param i32 i64 f32 f64) (result anyref (ref func)) func))
+
+  ;; CHECK:      (type $i32_i32_=>_none (func_subtype (param i32 i32) func))
+
+  ;; CHECK:      (type $i32_i32_f64_f64_=>_none (func_subtype (param i32 i32 f64 f64) func))
+
+  ;; CHECK:      (type $i64_=>_none (func_subtype (param i64) func))
+
+  ;; CHECK:      (type $i32_i32_i32_=>_none (func_subtype (param i32 i32 i32) func))
+
+  ;; CHECK:      (type $v128_=>_i32 (func_subtype (param v128) (result i32) func))
+
+  ;; CHECK:      (type $v128_v128_=>_v128 (func_subtype (param v128 v128) (result v128) func))
+
+  ;; CHECK:      (type $v128_v128_v128_=>_v128 (func_subtype (param v128 v128 v128) (result v128) func))
+
+  ;; CHECK:      (type $i32_i64_v128_=>_none (func_subtype (param i32 i64 v128) func))
+
+  ;; CHECK:      (type $i32_i32_i64_i64_=>_none (func_subtype (param i32 i32 i64 i64) func))
+
+  ;; CHECK:      (type $i32_=>_i32 (func_subtype (param i32) (result i32) func))
+
+  ;; CHECK:      (type $i32_i64_=>_i32_i64 (func_subtype (param i32 i64) (result i32 i64) func))
+
+  ;; CHECK:      (type $i64_=>_i32_i64 (func_subtype (param i64) (result i32 i64) func))
+
+  ;; CHECK:      (type $i32_=>_i32_i64 (func_subtype (param i32) (result i32 i64) func))
+
+  ;; CHECK:      (type $none_=>_i32_i64 (func_subtype (result i32 i64) func))
+
+  ;; CHECK:      (type $anyref_=>_none (func_subtype (param anyref) func))
+
+  ;; CHECK:      (type $eqref_eqref_=>_i32 (func_subtype (param eqref eqref) (result i32) func))
+
+  ;; CHECK:      (type $i32_=>_i31ref (func_subtype (param i32) (result i31ref) func))
+
+  ;; CHECK:      (type $i31ref_=>_none (func_subtype (param i31ref) func))
+
+  ;; CHECK:      (type $i32_i64_=>_ref|$pair| (func_subtype (param i32 i64) (result (ref $pair)) func))
+
+  ;; CHECK:      (type $none_=>_ref|$pair| (func_subtype (result (ref $pair)) func))
+
+  ;; CHECK:      (type $ref|$pair|_=>_i32 (func_subtype (param (ref $pair)) (result i32) func))
+
+  ;; CHECK:      (type $ref|$pair|_=>_i64 (func_subtype (param (ref $pair)) (result i64) func))
+
+  ;; CHECK:      (type $ref|$pair|_i32_=>_none (func_subtype (param (ref $pair) i32) func))
+
+  ;; CHECK:      (type $ref|$pair|_i64_=>_none (func_subtype (param (ref $pair) i64) func))
+
+  ;; CHECK:      (rec
+  ;; CHECK-NEXT:  (type $s0 (struct_subtype  data))
+  (type $s0 (sub (struct)))
+  ;; CHECK:       (type $s1 (struct_subtype  data))
+  (type $s1 (struct (field)))
+ )
+
+ (rec)
+
+ ;; CHECK:      (type $s2 (struct_subtype (field i32) data))
+ (type $s2 (struct i32))
+ ;; CHECK:      (type $s3 (struct_subtype (field i64) data))
+ (type $s3 (struct (field i64)))
+ ;; CHECK:      (type $s4 (struct_subtype (field $x f32) data))
+ (type $s4 (struct (field $x f32)))
+ ;; CHECK:      (type $s5 (struct_subtype (field i32) (field i64) data))
+ (type $s5 (struct i32 i64))
+ ;; CHECK:      (type $s6 (struct_subtype (field i64) (field f32) data))
+ (type $s6 (struct (field i64 f32)))
+ ;; CHECK:      (type $s7 (struct_subtype (field $x f32) (field $y f64) data))
+ (type $s7 (struct (field $x f32) (field $y f64)))
+ ;; CHECK:      (type $s8 (struct_subtype (field i32) (field i64) (field $z f32) (field f64) (field (mut i32)) data))
+ (type $s8 (struct i32 (field) i64 (field $z f32) (field f64 (mut i32))))
+
+ ;; CHECK:      (type $a0 (array_subtype i32 data))
+ (type $a0 (array i32))
+ ;; CHECK:      (type $a1 (array_subtype i64 data))
+ (type $a1 (array (field i64)))
+ ;; CHECK:      (type $a2 (array_subtype (mut f32) data))
+ (type $a2 (array (mut f32)))
+ ;; CHECK:      (type $a3 (array_subtype (mut f64) data))
+ (type $a3 (array (field $x (mut f64))))
+
+ (type $pair (struct (mut i32) (mut i64)))
+
+ (rec
+   (type $void (func))
+ )
+
+ ;; CHECK:      (type $subvoid (func_subtype $void))
+ (type $subvoid (sub $void (func)))
+
+ (type $many (func (param $x i32) (param i64 f32) (param) (param $y f64)
+                   (result anyref (ref func))))
+
+ ;; CHECK:      (type $submany (func_subtype (param i32 i64 f32 f64) (result anyref (ref func)) $many))
+ (type $submany (sub $many (func (param i32 i64 f32 f64) (result anyref (ref func)))))
+
+ ;; globals
+ (global $g1 (export "g1") (export "g1.1") (import "mod" "g1") i32)
+ (global $g2 (import "mod" "g2") (mut i64))
+ (global (import "" "g3") (ref 0))
+ (global (import "mod" "") (ref null $many))
+
+ (global (mut i32) i32.const 0)
+ ;; CHECK:      (type $ref|$s0|_ref|$s1|_ref|$s2|_ref|$s3|_ref|$s4|_ref|$s5|_ref|$s6|_ref|$s7|_ref|$s8|_ref|$a0|_ref|$a1|_ref|$a2|_ref|$a3|_ref|$subvoid|_ref|$submany|_=>_none (func_subtype (param (ref $s0) (ref $s1) (ref $s2) (ref $s3) (ref $s4) (ref $s5) (ref $s6) (ref $s7) (ref $s8) (ref $a0) (ref $a1) (ref $a2) (ref $a3) (ref $subvoid) (ref $submany)) func))
+
+ ;; CHECK:      (import "" "mem" (memory $mimport$1 0))
+
+ ;; CHECK:      (import "mod" "g1" (global $g1 i32))
+
+ ;; CHECK:      (import "mod" "g2" (global $g2 (mut i64)))
+
+ ;; CHECK:      (import "" "g3" (global $gimport$0 (ref $ret2)))
+
+ ;; CHECK:      (import "mod" "" (global $gimport$1 (ref null $many)))
+
+ ;; CHECK:      (import "mod" "f5" (func $fimport$1))
+
+ ;; CHECK:      (global $2 (mut i32) (i32.const 0))
+
+ ;; CHECK:      (global $i32 i32 (i32.const 42))
+ (global $i32 i32 i32.const 42)
+
+ ;; memories
+ ;; CHECK:      (memory $mem (shared 1 1))
+ (memory $mem 1 1 shared)
+ (memory 0 1 shared)
+ ;; CHECK:      (memory $0 (shared 0 1))
+
+ ;; CHECK:      (memory $mem-i32 0 1)
+ (memory $mem-i32 i32 0 1)
+ ;; CHECK:      (memory $mem-i64 i64 2)
+ (memory $mem-i64 i64 2)
+ (memory (export "mem") (export "mem2") (import "" "mem") 0)
+ ;; CHECK:      (memory $mem-init 1 1)
+ (memory $mem-init (data "hello, world!"))
+
+ ;; functions
+ (func)
+
+ ;; CHECK:      (export "g1" (global $g1))
+
+ ;; CHECK:      (export "g1.1" (global $g1))
+
+ ;; CHECK:      (export "mem" (memory $mimport$1))
+
+ ;; CHECK:      (export "mem2" (memory $mimport$1))
+
+ ;; CHECK:      (export "f5.0" (func $fimport$1))
+
+ ;; CHECK:      (export "f5.1" (func $fimport$1))
+
+ ;; CHECK:      (func $0 (type $void)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+
+ ;; CHECK:      (func $f1 (type $i32_=>_none) (param $0 i32)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $f1 (param i32))
+ ;; CHECK:      (func $f2 (type $i32_=>_none) (param $x i32)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $f2 (param $x i32))
+ ;; CHECK:      (func $f3 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $f3 (result i32)
+  i32.const 0
+ )
+ ;; CHECK:      (func $f4 (type $void)
+ ;; CHECK-NEXT:  (local $0 i32)
+ ;; CHECK-NEXT:  (local $1 i64)
+ ;; CHECK-NEXT:  (local $l f32)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $f4 (type 15) (local i32 i64) (local $l f32))
+ (func (export "f5.0") (export "f5.1") (import "mod" "f5"))
+
+ ;; CHECK:      (func $nop-skate (type $void)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $nop-skate
+  nop
+  nop
+  unreachable
+  nop
+  nop
+ )
+
+ ;; CHECK:      (func $nop-ski (type $void)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $nop-ski
+  (unreachable
+   (nop
+    (nop)
+    (nop)
+    (nop
+     (nop)
+    )
+   )
+   (nop)
+  )
+  (nop)
+ )
+
+ ;; CHECK:      (func $nop-sled (type $void)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $nop-sled
+  nop
+  (nop
+   (nop
+    (unreachable)
+   )
+  )
+  nop
+  (unreachable)
+  nop
+ )
+
+ ;; CHECK:      (func $add (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add (result i32)
+  i32.const 1
+  i32.const 2
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-folded (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-folded (result i32)
+  (i32.add
+   (i32.const 1)
+   (i32.const 2)
+  )
+ )
+
+ ;; CHECK:      (func $add-stacky (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (local $scratch i32)
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (block (result i32)
+ ;; CHECK-NEXT:    (local.set $scratch
+ ;; CHECK-NEXT:     (i32.const 1)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (nop)
+ ;; CHECK-NEXT:    (local.get $scratch)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-stacky (result i32)
+  i32.const 1
+  nop
+  i32.const 2
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-stacky-2 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (local $scratch i32)
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:   (block (result i32)
+ ;; CHECK-NEXT:    (local.set $scratch
+ ;; CHECK-NEXT:     (i32.const 2)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (nop)
+ ;; CHECK-NEXT:    (local.get $scratch)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-stacky-2 (result i32)
+  i32.const 1
+  i32.const 2
+  nop
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-stacky-3 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (local $scratch i32)
+ ;; CHECK-NEXT:  (local.set $scratch
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i32.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (local.get $scratch)
+ ;; CHECK-NEXT: )
+ (func $add-stacky-3 (result i32)
+  i32.const 1
+  i32.const 2
+  i32.add
+  nop
+ )
+
+ ;; CHECK:      (func $add-stacky-4 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (local $scratch i32)
+ ;; CHECK-NEXT:  (local $scratch_0 i32)
+ ;; CHECK-NEXT:  (local $scratch_1 i32)
+ ;; CHECK-NEXT:  (local.set $scratch_1
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (block (result i32)
+ ;; CHECK-NEXT:     (local.set $scratch_0
+ ;; CHECK-NEXT:      (i32.const 1)
+ ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (nop)
+ ;; CHECK-NEXT:     (local.get $scratch_0)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (block (result i32)
+ ;; CHECK-NEXT:     (local.set $scratch
+ ;; CHECK-NEXT:      (i32.const 2)
+ ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:     (nop)
+ ;; CHECK-NEXT:     (local.get $scratch)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT:  (local.get $scratch_1)
+ ;; CHECK-NEXT: )
+ (func $add-stacky-4 (result i32)
+  i32.const 1
+  nop
+  i32.const 2
+  nop
+  i32.add
+  nop
+ )
+
+ ;; CHECK:      (func $add-unreachable (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-unreachable (result i32)
+  unreachable
+  i32.const 1
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-unreachable-2 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-unreachable-2 (result i32)
+  i32.const 1
+  unreachable
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-unreachable-3 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $add-unreachable-3 (result i32)
+  i32.const 1
+  i32.const 2
+  unreachable
+ )
+
+ ;; CHECK:      (func $add-twice (type $ret2) (result i32 i32)
+ ;; CHECK-NEXT:  (tuple.make
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i32.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 3)
+ ;; CHECK-NEXT:    (i32.const 4)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-twice (type $ret2)
+  i32.const 1
+  i32.const 2
+  i32.add
+  i32.const 3
+  i32.const 4
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-twice-stacky (type $ret2) (result i32 i32)
+ ;; CHECK-NEXT:  (local $scratch i32)
+ ;; CHECK-NEXT:  (tuple.make
+ ;; CHECK-NEXT:   (block (result i32)
+ ;; CHECK-NEXT:    (local.set $scratch
+ ;; CHECK-NEXT:     (i32.add
+ ;; CHECK-NEXT:      (i32.const 1)
+ ;; CHECK-NEXT:      (i32.const 2)
+ ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (nop)
+ ;; CHECK-NEXT:    (local.get $scratch)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 3)
+ ;; CHECK-NEXT:    (i32.const 4)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-twice-stacky (type $ret2)
+  i32.const 1
+  i32.const 2
+  i32.add
+  nop
+  i32.const 3
+  i32.const 4
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-twice-stacky-2 (type $ret2) (result i32 i32)
+ ;; CHECK-NEXT:  (local $scratch i32)
+ ;; CHECK-NEXT:  (tuple.make
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i32.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:   (block (result i32)
+ ;; CHECK-NEXT:    (local.set $scratch
+ ;; CHECK-NEXT:     (i32.add
+ ;; CHECK-NEXT:      (i32.const 3)
+ ;; CHECK-NEXT:      (i32.const 4)
+ ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (nop)
+ ;; CHECK-NEXT:    (local.get $scratch)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-twice-stacky-2 (type $ret2)
+  i32.const 1
+  i32.const 2
+  i32.add
+  i32.const 3
+  i32.const 4
+  i32.add
+  nop
+ )
+
+ ;; CHECK:      (func $add-twice-unreachable (type $ret2) (result i32 i32)
+ ;; CHECK-NEXT:  (i32.add
+ ;; CHECK-NEXT:   (unreachable)
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $add-twice-unreachable (type $ret2)
+  unreachable
+  i32.const 2
+  i32.add
+  i32.const 3
+  i32.const 4
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-twice-unreachable-2 (type $ret2) (result i32 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i32.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $add-twice-unreachable-2 (type $ret2)
+  i32.const 1
+  i32.const 2
+  i32.add
+  unreachable
+  i32.const 3
+  i32.const 4
+  i32.add
+ )
+
+ ;; CHECK:      (func $add-twice-unreachable-3 (type $ret2) (result i32 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i32.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (i32.const 3)
+ ;; CHECK-NEXT:    (i32.const 4)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (unreachable)
+ ;; CHECK-NEXT: )
+ (func $add-twice-unreachable-3 (type $ret2)
+  i32.const 1
+  i32.const 2
+  i32.add
+  i32.const 3
+  i32.const 4
+  i32.add
+  unreachable
+ )
+
+ ;; CHECK:      (func $big-stack (type $void)
+ ;; CHECK-NEXT:  (local $scratch f64)
+ ;; CHECK-NEXT:  (local $scratch_0 i64)
+ ;; CHECK-NEXT:  (local $scratch_1 f32)
+ ;; CHECK-NEXT:  (local $scratch_2 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (block (result i32)
+ ;; CHECK-NEXT:    (local.set $scratch_2
+ ;; CHECK-NEXT:     (i32.const 0)
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (drop
+ ;; CHECK-NEXT:     (block (result f32)
+ ;; CHECK-NEXT:      (local.set $scratch_1
+ ;; CHECK-NEXT:       (f32.const 1)
+ ;; CHECK-NEXT:      )
+ ;; CHECK-NEXT:      (drop
+ ;; CHECK-NEXT:       (block (result i64)
+ ;; CHECK-NEXT:        (local.set $scratch_0
+ ;; CHECK-NEXT:         (i64.const 2)
+ ;; CHECK-NEXT:        )
+ ;; CHECK-NEXT:        (drop
+ ;; CHECK-NEXT:         (block (result f64)
+ ;; CHECK-NEXT:          (local.set $scratch
+ ;; CHECK-NEXT:           (f64.const 3)
+ ;; CHECK-NEXT:          )
+ ;; CHECK-NEXT:          (drop
+ ;; CHECK-NEXT:           (ref.null none)
+ ;; CHECK-NEXT:          )
+ ;; CHECK-NEXT:          (local.get $scratch)
+ ;; CHECK-NEXT:         )
+ ;; CHECK-NEXT:        )
+ ;; CHECK-NEXT:        (local.get $scratch_0)
+ ;; CHECK-NEXT:       )
+ ;; CHECK-NEXT:      )
+ ;; CHECK-NEXT:      (local.get $scratch_1)
+ ;; CHECK-NEXT:     )
+ ;; CHECK-NEXT:    )
+ ;; CHECK-NEXT:    (local.get $scratch_2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $big-stack
+  i32.const 0
+  f32.const 1
+  i64.const 2
+  f64.const 3
+  ref.null any
+  drop
+  drop
+  drop
+  drop
+  drop
+ )
+
+ ;; CHECK:      (func $locals (type $i32_i32_=>_none) (param $0 i32) (param $x i32)
+ ;; CHECK-NEXT:  (local $2 i32)
+ ;; CHECK-NEXT:  (local $y i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $y)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (local.set $x
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.tee $y
+ ;; CHECK-NEXT:    (local.get $y)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $locals (param i32) (param $x i32)
+  (local i32)
+  (local $y i32)
+  local.get 0
+  drop
+  local.get 1
+  drop
+  local.get 2
+  drop
+  local.get 3
+  drop
+  local.get $x
+  local.set 1
+  local.get $y
+  local.tee 3
+  drop
+ )
+
+
+ ;; CHECK:      (func $binary (type $i32_i32_f64_f64_=>_none) (param $0 i32) (param $1 i32) (param $2 f64) (param $3 f64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.add
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (f64.mul
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:    (local.get $3)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $binary (param i32 i32 f64 f64)
+  local.get 0
+  local.get 1
+  i32.add
+  drop
+  local.get 2
+  local.get 3
+  f64.mul
+  drop
+ )
+
+ ;; CHECK:      (func $unary (type $i64_=>_none) (param $0 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.eqz
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $unary (param i64)
+  local.get 0
+  i64.eqz
+  drop
+ )
+
+ ;; CHECK:      (func $select (type $i32_i32_i32_=>_none) (param $0 i32) (param $1 i32) (param $2 i32)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (select
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (select
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (select
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (select
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $select (param i32 i32 i32)
+  local.get 0
+  local.get 1
+  local.get 2
+  select
+  drop
+  local.get 0
+  local.get 1
+  local.get 2
+  select (result)
+  drop
+  local.get 0
+  local.get 1
+  local.get 2
+  select (result i32)
+  drop
+  local.get 0
+  local.get 1
+  local.get 2
+  select (result) (result i32) (result)
+  drop
+ )
+
+ ;; CHECK:      (func $memory-size (type $void)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.size $mem)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.size $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.size $mem-i64)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $memory-size
+  memory.size
+  drop
+  memory.size 1
+  drop
+  memory.size $mem-i64
+  drop
+ )
+
+ ;; CHECK:      (func $memory-grow (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.grow $mem
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.grow $0
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.grow $mem-i64
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $memory-grow (param i32 i64)
+  local.get 0
+  memory.grow
+  drop
+  local.get 0
+  memory.grow 1
+  drop
+  local.get 1
+  memory.grow $mem-i64
+  drop
+ )
+
+ ;; CHECK:      (func $globals (type $void)
+ ;; CHECK-NEXT:  (global.set $2
+ ;; CHECK-NEXT:   (global.get $i32)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $globals
+  global.get $i32
+  global.set 4
+ )
+
+ ;; CHECK:      (func $load (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.load $mem offset=42
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.load8_s $0
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.load16_u $mem-i64 offset=42
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $load (param i32 i64)
+  local.get 0
+  i32.load offset=42
+  drop
+  local.get 0
+  i64.load8_s 1 align=1
+  drop
+  local.get 1
+  i32.atomic.load16_u $mem-i64 offset=42 align=2
+  drop
+ )
+
+ ;; CHECK:      (func $store (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (i32.store $mem offset=42 align=1
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (i32.const 0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (i64.atomic.store8 $0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (i64.const 1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (f32.store $mem-i64
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:   (f32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $store (param i32 i64)
+  local.get 0
+  i32.const 0
+  i32.store offset=42 align=1
+  local.get 0
+  i64.const 1
+  i64.atomic.store8 1
+  local.get 1
+  f32.const 2
+  f32.store $mem-i64
+ )
+
+ ;; CHECK:      (func $atomic-rmw (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw16.add_u $mem
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw.xor $mem-i64 offset=8
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (i64.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-rmw (param i32 i64)
+  local.get 0
+  i32.const 1
+  i32.atomic.rmw16.add_u
+  drop
+  local.get 1
+  i64.const 2
+  i64.atomic.rmw.xor $mem-i64 offset=8 align=8
+  drop
+ )
+
+ ;; CHECK:      (func $atomic-cmpxchg (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i32.atomic.rmw8.cmpxchg_u $mem
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i32.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i64.atomic.rmw32.cmpxchg_u $mem-i64 offset=16
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (i64.const 3)
+ ;; CHECK-NEXT:    (i64.const 4)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-cmpxchg (param i32 i64)
+  local.get 0
+  i32.const 1
+  i32.const 2
+  i32.atomic.rmw8.cmpxchg_u 0 align=1
+  drop
+  local.get 1
+  i64.const 3
+  i64.const 4
+  i64.atomic.rmw32.cmpxchg_u 3 offset=16
+  drop
+ )
+
+ ;; CHECK:      (func $atomic-wait (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait32 $mem
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:    (i64.const 2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.wait64 $mem-i64 offset=8
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (i64.const 3)
+ ;; CHECK-NEXT:    (i64.const 4)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-wait (param i32 i64)
+  local.get 0
+  i32.const 1
+  i64.const 2
+  memory.atomic.wait32
+  drop
+  local.get 1
+  i64.const 3
+  i64.const 4
+  memory.atomic.wait64 $mem-i64 offset=8 align=8
+  drop
+ )
+
+ ;; CHECK:      (func $atomic-notify (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.notify $mem offset=8
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (i32.const 0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (memory.atomic.notify $mem-i64
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:    (i32.const 1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $atomic-notify (param i32 i64)
+  local.get 0
+  i32.const 0
+  memory.atomic.notify offset=8 align=4
+  drop
+  local.get 1
+  i32.const 1
+  memory.atomic.notify $mem-i64
+  drop
+ )
+
+ ;; CHECK:      (func $atomic-fence (type $void)
+ ;; CHECK-NEXT:  (atomic.fence)
+ ;; CHECK-NEXT: )
+ (func $atomic-fence
+  atomic.fence
+ )
+
+ ;; CHECK:      (func $simd-extract (type $v128_=>_i32) (param $0 v128) (result i32)
+ ;; CHECK-NEXT:  (i32x4.extract_lane 3
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-extract (param v128) (result i32)
+  local.get 0
+  i32x4.extract_lane 3
+ )
+
+ ;; CHECK:      (func $simd-replace (type $v128_i32_=>_v128) (param $0 v128) (param $1 i32) (result v128)
+ ;; CHECK-NEXT:  (i32x4.replace_lane 2
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-replace (param v128 i32) (result v128)
+  local.get 0
+  local.get 1
+  i32x4.replace_lane 2
+ )
+
+ ;; CHECK:      (func $simd-shuffle (type $v128_v128_=>_v128) (param $0 v128) (param $1 v128) (result v128)
+ ;; CHECK-NEXT:  (i8x16.shuffle 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-shuffle (param v128 v128) (result v128)
+  local.get 0
+  local.get 1
+  i8x16.shuffle 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
+ )
+
+ ;; CHECK:      (func $simd-ternary (type $v128_v128_v128_=>_v128) (param $0 v128) (param $1 v128) (param $2 v128) (result v128)
+ ;; CHECK-NEXT:  (v128.bitselect
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-ternary (param v128 v128 v128) (result v128)
+  local.get 0
+  local.get 1
+  local.get 2
+  v128.bitselect
+ )
+
+ ;; CHECK:      (func $simd-shift (type $v128_i32_=>_v128) (param $0 v128) (param $1 i32) (result v128)
+ ;; CHECK-NEXT:  (i8x16.shl
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-shift (param v128 i32) (result v128)
+  local.get 0
+  local.get 1
+  i8x16.shl
+ )
+
+ ;; CHECK:      (func $simd-load (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (v128.load8x8_s $mem offset=8
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (v128.load16_splat $mem-i64 offset=2 align=1
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-load (param i32 i64)
+  local.get 0
+  v128.load8x8_s offset=8 align=8
+  drop
+  local.get 1
+  v128.load16_splat $mem-i64 offset=2 align=1
+  drop
+ )
+
+ ;; CHECK:      (func $simd-load-store-lane (type $i32_i64_v128_=>_none) (param $0 i32) (param $1 i64) (param $2 v128)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (v128.load16_lane $mem 7
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $2)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (v128.store64_lane $mem-i64 align=4 0
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $simd-load-store-lane (param i32 i64 v128)
+  local.get 0
+  local.get 2
+  v128.load16_lane 7
+  drop
+  local.get 1
+  local.get 2
+  v128.store64_lane 3 align=4 0
+ )
+
+ ;; CHECK:      (func $memory-copy (type $i32_i32_i64_i64_=>_none) (param $0 i32) (param $1 i32) (param $2 i64) (param $3 i64)
+ ;; CHECK-NEXT:  (memory.copy $mem $mem
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (memory.copy $mem $mem-i32
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:   (i32.const 3)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (memory.copy $mem-i64 $mem-i64
+ ;; CHECK-NEXT:   (local.get $2)
+ ;; CHECK-NEXT:   (local.get $3)
+ ;; CHECK-NEXT:   (i64.const 4)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $memory-copy (param i32 i32 i64 i64)
+  local.get 0
+  local.get 1
+  i32.const 2
+  memory.copy
+  local.get 0
+  local.get 1
+  i32.const 3
+  memory.copy 0 $mem-i32
+  local.get 2
+  local.get 3
+  i64.const 4
+  memory.copy $mem-i64 3
+ )
+
+ ;; CHECK:      (func $memory-fill (type $i32_i64_=>_none) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT:  (memory.fill $mem
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (i32.const 1)
+ ;; CHECK-NEXT:   (i32.const 2)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (memory.fill $mem
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (i32.const 3)
+ ;; CHECK-NEXT:   (i32.const 4)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (memory.fill $mem-i64
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:   (i32.const 5)
+ ;; CHECK-NEXT:   (i64.const 6)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $memory-fill (param i32 i64)
+  local.get 0
+  i32.const 1
+  i32.const 2
+  memory.fill
+  local.get 0
+  i32.const 3
+  i32.const 4
+  memory.fill 0
+  local.get 1
+  i32.const 5
+  i64.const 6
+  memory.fill $mem-i64
+ )
+
+ ;; CHECK:      (func $return-none (type $void)
+ ;; CHECK-NEXT:  (return)
+ ;; CHECK-NEXT: )
+ (func $return-none
+  return
+ )
+
+ ;; CHECK:      (func $return-one (type $i32_=>_i32) (param $0 i32) (result i32)
+ ;; CHECK-NEXT:  (return
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $return-one (param i32) (result i32)
+  local.get 0
+  return
+ )
+
+ ;; CHECK:      (func $return-two (type $i32_i64_=>_i32_i64) (param $0 i32) (param $1 i64) (result i32 i64)
+ ;; CHECK-NEXT:  (return
+ ;; CHECK-NEXT:   (tuple.make
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:    (local.get $1)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $return-two (param i32 i64) (result i32 i64)
+  local.get 0
+  local.get 1
+  return
+ )
+
+ ;; CHECK:      (func $return-two-first-unreachable (type $i64_=>_i32_i64) (param $0 i64) (result i32 i64)
+ ;; CHECK-NEXT:  (return
+ ;; CHECK-NEXT:   (tuple.make
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $return-two-first-unreachable (param i64) (result i32 i64)
+  unreachable
+  local.get 0
+  return
+ )
+
+ ;; CHECK:      (func $return-two-second-unreachable (type $i32_=>_i32_i64) (param $0 i32) (result i32 i64)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (return
+ ;; CHECK-NEXT:   (tuple.make
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:    (unreachable)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $return-two-second-unreachable (param i32) (result i32 i64)
+  local.get 0
+  unreachable
+  return
+ )
+
+ ;; CHECK:      (func $ref-is (type $anyref_=>_none) (param $0 anyref)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.is_null
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.is_func
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.is_data
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (ref.is_i31
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $ref-is (param anyref)
+  local.get 0
+  ref.is_null
+  drop
+  local.get 0
+  ref.is_func
+  drop
+  local.get 0
+  ref.is_data
+  drop
+  local.get 0
+  ref.is_i31
+  drop
+ )
+
+ ;; CHECK:      (func $ref-eq (type $eqref_eqref_=>_i32) (param $0 eqref) (param $1 eqref) (result i32)
+ ;; CHECK-NEXT:  (ref.eq
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $ref-eq (param eqref eqref) (result i32)
+  local.get 0
+  local.get 1
+  ref.eq
+ )
+
+ ;; CHECK:      (func $i31-new (type $i32_=>_i31ref) (param $0 i32) (result i31ref)
+ ;; CHECK-NEXT:  (i31.new
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $i31-new (param i32) (result i31ref)
+  local.get 0
+  i31.new
+ )
+
+ ;; CHECK:      (func $i31-get (type $i31ref_=>_none) (param $0 i31ref)
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i31.get_s
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT:  (drop
+ ;; CHECK-NEXT:   (i31.get_u
+ ;; CHECK-NEXT:    (local.get $0)
+ ;; CHECK-NEXT:   )
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $i31-get (param i31ref)
+  local.get 0
+  i31.get_s
+  drop
+  local.get 0
+  i31.get_u
+  drop
+ )
+
+ ;; CHECK:      (func $struct-new (type $i32_i64_=>_ref|$pair|) (param $0 i32) (param $1 i64) (result (ref $pair))
+ ;; CHECK-NEXT:  (struct.new $pair
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $struct-new (param i32 i64) (result (ref $pair))
+  local.get 0
+  local.get 1
+  struct.new $pair
+ )
+
+ ;; CHECK:      (func $struct-new-default (type $none_=>_ref|$pair|) (result (ref $pair))
+ ;; CHECK-NEXT:  (struct.new_default $pair)
+ ;; CHECK-NEXT: )
+ (func $struct-new-default (result (ref $pair))
+  struct.new_default $pair
+ )
+
+ ;; CHECK:      (func $struct-get-0 (type $ref|$pair|_=>_i32) (param $0 (ref $pair)) (result i32)
+ ;; CHECK-NEXT:  (struct.get $pair 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $struct-get-0 (param (ref $pair)) (result i32)
+  local.get 0
+  struct.get $pair 0
+ )
+
+ ;; CHECK:      (func $struct-get-1 (type $ref|$pair|_=>_i64) (param $0 (ref $pair)) (result i64)
+ ;; CHECK-NEXT:  (struct.get $pair 1
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $struct-get-1 (param (ref $pair)) (result i64)
+  local.get 0
+  struct.get $pair 1
+ )
+
+ ;; CHECK:      (func $struct-set-0 (type $ref|$pair|_i32_=>_none) (param $0 (ref $pair)) (param $1 i32)
+ ;; CHECK-NEXT:  (struct.set $pair 0
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $struct-set-0 (param (ref $pair) i32)
+  local.get 0
+  local.get 1
+  struct.set $pair 0
+ )
+
+ ;; CHECK:      (func $struct-set-1 (type $ref|$pair|_i64_=>_none) (param $0 (ref $pair)) (param $1 i64)
+ ;; CHECK-NEXT:  (struct.set $pair 1
+ ;; CHECK-NEXT:   (local.get $0)
+ ;; CHECK-NEXT:   (local.get $1)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $struct-set-1 (param (ref $pair) i64)
+  local.get 0
+  local.get 1
+  struct.set $pair 1
+ )
+
+ ;; CHECK:      (func $use-types (type $ref|$s0|_ref|$s1|_ref|$s2|_ref|$s3|_ref|$s4|_ref|$s5|_ref|$s6|_ref|$s7|_ref|$s8|_ref|$a0|_ref|$a1|_ref|$a2|_ref|$a3|_ref|$subvoid|_ref|$submany|_=>_none) (param $0 (ref $s0)) (param $1 (ref $s1)) (param $2 (ref $s2)) (param $3 (ref $s3)) (param $4 (ref $s4)) (param $5 (ref $s5)) (param $6 (ref $s6)) (param $7 (ref $s7)) (param $8 (ref $s8)) (param $9 (ref $a0)) (param $10 (ref $a1)) (param $11 (ref $a2)) (param $12 (ref $a3)) (param $13 (ref $subvoid)) (param $14 (ref $submany))
+ ;; CHECK-NEXT:  (nop)
+ ;; CHECK-NEXT: )
+ (func $use-types
+  (param (ref $s0))
+  (param (ref $s1))
+  (param (ref $s2))
+  (param (ref $s3))
+  (param (ref $s4))
+  (param (ref $s5))
+  (param (ref $s6))
+  (param (ref $s7))
+  (param (ref $s8))
+  (param (ref $a0))
+  (param (ref $a1))
+  (param (ref $a2))
+  (param (ref $a3))
+  (param (ref $subvoid))
+  (param (ref $submany))
+ )
+)
diff --git a/test/lld/basic_safe_stack.wat b/test/lld/basic_safe_stack.wat
index 9d10095..e82d0a5 100644
--- a/test/lld/basic_safe_stack.wat
+++ b/test/lld/basic_safe_stack.wat
@@ -2,8 +2,8 @@
  (type $none_=>_none (func))
  (type $i32_=>_none (func (param i32)))
  (type $i32_=>_i32 (func (param i32) (result i32)))
- (memory $0 2)
  (global $__stack_pointer (mut i32) (i32.const 66112))
+ (memory $0 2)
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "stackRestore" (func $stackRestore))
diff --git a/test/lld/basic_safe_stack.wat.out b/test/lld/basic_safe_stack.wat.out
index 6e1b837..d0bcd05 100644
--- a/test/lld/basic_safe_stack.wat.out
+++ b/test/lld/basic_safe_stack.wat.out
@@ -88,28 +88,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "__handle_stack_overflow"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "stackRestore",
-    "stackAlloc",
-    "main",
-    "__set_stack_limits"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 1,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/duplicate_imports.wat.out b/test/lld/duplicate_imports.wat.out
index 38a08e5..5cb68af 100644
--- a/test/lld/duplicate_imports.wat.out
+++ b/test/lld/duplicate_imports.wat.out
@@ -64,30 +64,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "puts"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main",
-    "dynCall_ffd",
-    "dynCall_fdd"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66128",
-    "__data_end" : "581"
-  },
-  "invokeFuncs": [
-    "invoke_ffd"
-  ],
-  "mainReadsParams": 1,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm.wat b/test/lld/em_asm.wat
index 0350308..a18196e 100644
--- a/test/lld/em_asm.wat
+++ b/test/lld/em_asm.wat
@@ -1,16 +1,16 @@
 (module
+ (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (type $none_=>_none (func))
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
- (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i32 i32 i32) (result i32)))
+ (global $__stack_pointer (mut i32) (i32.const 66208))
+ (global $global$1 i32 (i32.const 574))
+ (global $global$2 i32 (i32.const 665))
  (memory $0 2)
  (data $.rodata (i32.const 568) "\00ii\00i\00")
  (data $em_asm (i32.const 574) "{ Module.print(\"Hello \\\\ world\\t\\n\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (table $0 1 1 funcref)
- (global $__stack_pointer (mut i32) (i32.const 66208))
- (global $global$1 i32 (i32.const 574))
- (global $global$2 i32 (i32.const 665))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
diff --git a/test/lld/em_asm.wat.mem.mem b/test/lld/em_asm.wat.mem.mem
index 0059fdc..bc55b64 100644
Binary files a/test/lld/em_asm.wat.mem.mem and b/test/lld/em_asm.wat.mem.mem differ
diff --git a/test/lld/em_asm.wat.mem.out b/test/lld/em_asm.wat.mem.out
index 07c9aa3..6647b39 100644
--- a/test/lld/em_asm.wat.mem.out
+++ b/test/lld/em_asm.wat.mem.out
@@ -66,30 +66,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "574": "{ Module.print(\"Hello \\ world\\t\\n\"); }",
-    "614": "{ return $0 + $1; }",
-    "634": "{ Module.print(\"Got \" + $0); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm.wat.out b/test/lld/em_asm.wat.out
index 5a32e4b..8ed3d72 100644
--- a/test/lld/em_asm.wat.out
+++ b/test/lld/em_asm.wat.out
@@ -9,11 +9,13 @@
  (global $global$2 i32 (i32.const 665))
  (memory $0 2)
  (data $.rodata (i32.const 568) "\00ii\00i\00")
- (data $em_asm (i32.const 574) "")
+ (data $em_asm (i32.const 574) "{ Module.print(\"Hello \\\\ world\\t\\n\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (table $0 1 1 funcref)
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
+ (export "__start_em_asm" (global $global$1))
+ (export "__stop_em_asm" (global $global$2))
  (func $__wasm_call_ctors
   (nop)
  )
@@ -68,30 +70,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "574": "{ Module.print(\"Hello \\ world\\t\\n\"); }",
-    "614": "{ return $0 + $1; }",
-    "634": "{ Module.print(\"Got \" + $0); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm64.wat b/test/lld/em_asm64.wat
index 739dab1..d4ef385 100644
--- a/test/lld/em_asm64.wat
+++ b/test/lld/em_asm64.wat
@@ -1,16 +1,16 @@
 (module
+ (type $i64_i64_i64_=>_i32 (func (param i64 i64 i64) (result i32)))
  (type $none_=>_none (func))
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i64_=>_i32 (func (param i32 i64) (result i32)))
- (type $i64_i64_i64_=>_i32 (func (param i64 i64 i64) (result i32)))
  (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i64 i64 i64) (result i32)))
+ (global $__stack_pointer (mut i64) (i64.const 66208))
+ (global $global$1 i64 (i64.const 574))
+ (global $global$2 i64 (i64.const 658))
  (memory $0 i64 2)
  (data $.rodata (i64.const 568) "\00ii\00i\00")
  (data $em_asm (i64.const 574) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (table $0 1 1 funcref)
- (global $__stack_pointer (mut i64) (i64.const 66208))
- (global $global$1 i64 (i64.const 574))
- (global $global$2 i64 (i64.const 658))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
diff --git a/test/lld/em_asm64.wat.out b/test/lld/em_asm64.wat.out
index 8b0f181..33e5bb1 100644
--- a/test/lld/em_asm64.wat.out
+++ b/test/lld/em_asm64.wat.out
@@ -9,11 +9,13 @@
  (global $global$2 i64 (i64.const 658))
  (memory $0 i64 2)
  (data $.rodata (i64.const 568) "\00ii\00i\00")
- (data $em_asm (i64.const 574) "")
+ (data $em_asm (i64.const 574) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (table $0 1 1 funcref)
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
+ (export "__start_em_asm" (global $global$1))
+ (export "__stop_em_asm" (global $global$2))
  (func $__wasm_call_ctors
   (nop)
  )
@@ -68,31 +70,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "574": "{ Module.print(\"Hello world\"); }",
-    "607": "{ return $0 + $1; }",
-    "627": "{ Module.print(\"Got \" + $0); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-    "--enable-memory64"
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm_O0.c b/test/lld/em_asm_O0.c
index 6351f81..352a8ed 100644
--- a/test/lld/em_asm_O0.c
+++ b/test/lld/em_asm_O0.c
@@ -1,6 +1,6 @@
 #include <emscripten.h>
 
-int main(int argc, char** argv) {
+int main() {
   EM_ASM({ Module.print("Hello world"); });
   int ret = EM_ASM_INT({ return $0 + $1; }, 20, 30);
   EM_ASM({ Module.print("Got " + $0); }, 42);
diff --git a/test/lld/em_asm_O0.wat b/test/lld/em_asm_O0.wat
index f96564c..1dba1e9 100644
--- a/test/lld/em_asm_O0.wat
+++ b/test/lld/em_asm_O0.wat
@@ -1,13 +1,15 @@
 (module
+ (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
- (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i32 i32 i32) (result i32)))
- (memory $0 2)
- (data $em_asm (i32.const 568) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (global $__stack_pointer (mut i32) (i32.const 66192))
  (global $global$1 i32 (i32.const 568))
  (global $global$2 i32 (i32.const 652))
+ (memory $0 2)
+ (data $em_asm (i32.const 568) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
+ (table $0 1 1 funcref)
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
@@ -15,11 +17,11 @@
  (export "__stop_em_asm" (global $global$2))
  (func $__wasm_call_ctors
  )
- (func $main (param $0 i32) (param $1 i32) (result i32)
-  (local $2 i32)
-  (local $3 i32)
+ (func $__original_main (result i32)
+  (local $0 i32)
+  (local $1 i32)
   (global.set $__stack_pointer
-   (local.tee $2
+   (local.tee $0
     (i32.sub
      (global.get $__stack_pointer)
      (i32.const 32)
@@ -27,69 +29,72 @@
    )
   )
   (i32.store8 offset=31
-   (local.get $2)
+   (local.get $0)
    (i32.const 0)
   )
   (drop
    (call $emscripten_asm_const_int
     (i32.const 568)
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 31)
     )
     (i32.const 0)
    )
   )
   (i32.store8 offset=30
-   (local.get $2)
+   (local.get $0)
    (i32.const 0)
   )
   (i32.store16 offset=28 align=1
-   (local.get $2)
+   (local.get $0)
    (i32.const 26985)
   )
   (i64.store offset=16
-   (local.get $2)
+   (local.get $0)
    (i64.const 128849018900)
   )
-  (local.set $3
+  (local.set $1
    (call $emscripten_asm_const_int
     (i32.const 601)
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 28)
     )
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 16)
     )
    )
   )
-  (i32.store16 offset=26 align=1
-   (local.get $2)
-   (i32.const 105)
-  )
   (i32.store
-   (local.get $2)
+   (local.get $0)
    (i32.const 42)
   )
+  (i32.store16 offset=26 align=1
+   (local.get $0)
+   (i32.const 105)
+  )
   (drop
    (call $emscripten_asm_const_int
     (i32.const 621)
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 26)
     )
-    (local.get $2)
+    (local.get $0)
    )
   )
   (global.set $__stack_pointer
    (i32.add
-    (local.get $2)
+    (local.get $0)
     (i32.const 32)
    )
   )
-  (local.get $3)
+  (local.get $1)
+ )
+ (func $main (param $0 i32) (param $1 i32) (result i32)
+  (call $__original_main)
  )
  ;; custom section "producers", size 112
 )
diff --git a/test/lld/em_asm_O0.wat.out b/test/lld/em_asm_O0.wat.out
index e05e857..fb24954 100644
--- a/test/lld/em_asm_O0.wat.out
+++ b/test/lld/em_asm_O0.wat.out
@@ -1,24 +1,28 @@
 (module
  (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i32 i32 i32) (result i32)))
  (global $__stack_pointer (mut i32) (i32.const 66192))
  (global $global$1 i32 (i32.const 568))
  (global $global$2 i32 (i32.const 652))
  (memory $0 2)
- (data $em_asm (i32.const 568) "")
+ (data $em_asm (i32.const 568) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
+ (table $0 1 1 funcref)
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
+ (export "__start_em_asm" (global $global$1))
+ (export "__stop_em_asm" (global $global$2))
  (func $__wasm_call_ctors
   (nop)
  )
- (func $main (param $0 i32) (param $1 i32) (result i32)
-  (local $2 i32)
-  (local $3 i32)
+ (func $__original_main (result i32)
+  (local $0 i32)
+  (local $1 i32)
   (global.set $__stack_pointer
-   (local.tee $2
+   (local.tee $0
     (i32.sub
      (global.get $__stack_pointer)
      (i32.const 32)
@@ -26,95 +30,71 @@
    )
   )
   (i32.store8 offset=31
-   (local.get $2)
+   (local.get $0)
    (i32.const 0)
   )
   (drop
    (call $emscripten_asm_const_int
     (i32.const 568)
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 31)
     )
     (i32.const 0)
    )
   )
   (i32.store8 offset=30
-   (local.get $2)
+   (local.get $0)
    (i32.const 0)
   )
   (i32.store16 offset=28 align=1
-   (local.get $2)
+   (local.get $0)
    (i32.const 26985)
   )
   (i64.store offset=16
-   (local.get $2)
+   (local.get $0)
    (i64.const 128849018900)
   )
-  (local.set $3
+  (local.set $1
    (call $emscripten_asm_const_int
     (i32.const 601)
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 28)
     )
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 16)
     )
    )
   )
-  (i32.store16 offset=26 align=1
-   (local.get $2)
-   (i32.const 105)
-  )
   (i32.store
-   (local.get $2)
+   (local.get $0)
    (i32.const 42)
   )
+  (i32.store16 offset=26 align=1
+   (local.get $0)
+   (i32.const 105)
+  )
   (drop
    (call $emscripten_asm_const_int
     (i32.const 621)
     (i32.add
-     (local.get $2)
+     (local.get $0)
      (i32.const 26)
     )
-    (local.get $2)
+    (local.get $0)
    )
   )
   (global.set $__stack_pointer
    (i32.add
-    (local.get $2)
+    (local.get $0)
     (i32.const 32)
    )
   )
-  (local.get $3)
+  (local.get $1)
+ )
+ (func $main (param $0 i32) (param $1 i32) (result i32)
+  (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "568": "{ Module.print(\"Hello world\"); }",
-    "601": "{ return $0 + $1; }",
-    "621": "{ Module.print(\"Got \" + $0); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 1,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm_main_thread.wat.out b/test/lld/em_asm_main_thread.wat.out
index fdb75c9..6214659 100644
--- a/test/lld/em_asm_main_thread.wat.out
+++ b/test/lld/em_asm_main_thread.wat.out
@@ -13,8 +13,10 @@
  (global $global$1 i32 (i32.const 66192))
  (global $global$2 i32 (i32.const 652))
  (memory $0 2)
- (data (i32.const 568) "")
+ (data (i32.const 568) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (table $0 1 1 funcref)
+ (export "__start_em_asm" (global $0))
+ (export "__stop_em_asm" (global $1))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "__heap_base" (global $global$1))
@@ -192,32 +194,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "568": "{ Module.print(\"Hello world\"); }",
-    "601": "{ return $0 + $1; }",
-    "621": "{ Module.print(\"Got \" + $0); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int_sync_on_main_thread"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66192",
-    "__data_end" : "652"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm_pthread.wasm.out b/test/lld/em_asm_pthread.wasm.out
index 45d76f5..0bf7927 100644
--- a/test/lld/em_asm_pthread.wasm.out
+++ b/test/lld/em_asm_pthread.wasm.out
@@ -39,11 +39,6 @@
  (type $i32_i32_i32_i32_=>_f64 (func (param i32 i32 i32 i32) (result f64)))
  (type $i32_i32_i64_i32_=>_i64 (func (param i32 i32 i64 i32) (result i64)))
  (import "env" "memory" (memory $mimport$0 (shared 256 256)))
- (data "\00/home/azakai/Dev/emscripten/system/lib/pthread/library_pthread.c\00call\00_emscripten_do_dispatch_to_thread\00target_thread\00num_args+1 <= EM_QUEUED_JS_CALL_MAX_ARGS\00emscripten_run_in_main_runtime_thread_js\00q\00_emscripten_call_on_thread\00EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(q->functionEnum) <= EM_QUEUED_CALL_MAX_ARGS\00_do_call\000 && \"Invalid Emscripten pthread _do_call opcode!\"\00target\00GetQueue\00em_queued_call_malloc\00")
- (data "\01\00\00\00\d0\0fP\00\05\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\03\00\00\00\04\00\00\00x\t\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\a0\05\00\00")
- (data "")
- (data "")
- (data "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
  (import "env" "emscripten_asm_const_int" (func $fimport$0 (param i32 i32 i32) (result i32)))
  (import "env" "world" (func $fimport$1))
  (import "env" "__cxa_thread_atexit" (func $fimport$2 (param i32 i32 i32) (result i32)))
@@ -71,20 +66,29 @@
  (global $global$5 (mut i32) (i32.const 0))
  (global $global$6 (mut i32) (i32.const 0))
  (global $global$7 (mut i32) (i32.const 0))
+ (global $global$8 i32 (i32.const 1588))
+ (global $global$9 i32 (i32.const 1621))
  (global $global$10 i32 (i32.const 1432))
  (global $global$11 i32 (i32.const 1836))
  (global $global$12 i32 (i32.const 1658))
  (global $global$13 i32 (i32.const 1782))
+ (data "\00/home/azakai/Dev/emscripten/system/lib/pthread/library_pthread.c\00call\00_emscripten_do_dispatch_to_thread\00target_thread\00num_args+1 <= EM_QUEUED_JS_CALL_MAX_ARGS\00emscripten_run_in_main_runtime_thread_js\00q\00_emscripten_call_on_thread\00EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(q->functionEnum) <= EM_QUEUED_CALL_MAX_ARGS\00_do_call\000 && \"Invalid Emscripten pthread _do_call opcode!\"\00target\00GetQueue\00em_queued_call_malloc\00")
+ (data "\01\00\00\00\d0\0fP\00\05\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\03\00\00\00\04\00\00\00x\t\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\a0\05\00\00")
+ (data "()<::>{ console.log(\"World.\"); }\00(void)<::>{ PThread.initRuntime(); }\00")
+ (data "{ console.log(\"Hello.\"); }\00throw \'Canceled!\'\00{ setTimeout(function() { __emscripten_do_dispatch_to_thread($0, $1); }, 0); }\00")
+ (data "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
  (table $0 5 5 funcref)
  (elem (i32.const 1) $6 $73 $72 $74)
  (export "__wasm_call_ctors" (func $0))
  (export "main" (func $4))
+ (export "__em_js__world" (global $global$8))
  (export "__indirect_function_table" (table $0))
  (export "emscripten_tls_init" (func $5))
  (export "emscripten_get_global_libc" (func $82))
  (export "__errno_location" (func $25))
  (export "fflush" (func $80))
  (export "__emscripten_pthread_data_constructor" (func $83))
+ (export "__em_js__initPthreadsJS" (global $global$9))
  (export "pthread_self" (func $14))
  (export "__pthread_tsd_run_dtors" (func $84))
  (export "emscripten_current_thread_process_queued_calls" (func $31))
@@ -109,6 +113,8 @@
  (export "malloc" (func $60))
  (export "free" (func $62))
  (export "memalign" (func $63))
+ (export "__start_em_asm" (global $global$12))
+ (export "__stop_em_asm" (global $global$13))
  (export "dynCall_vi" (func $dynCall_vi))
  (export "dynCall_ii" (func $dynCall_ii))
  (export "dynCall_iiii" (func $dynCall_iiii))
@@ -146,27 +152,15 @@
      (i32.const 0)
      (i32.const 156)
     )
-    (block
-     (drop
-      (i32.const 1588)
-     )
-     (drop
-      (i32.const 0)
-     )
-     (drop
-      (i32.const 70)
-     )
+    (memory.init 2
+     (i32.const 1588)
+     (i32.const 0)
+     (i32.const 70)
     )
-    (block
-     (drop
-      (i32.const 1658)
-     )
-     (drop
-      (i32.const 0)
-     )
-     (drop
-      (i32.const 124)
-     )
+    (memory.init 3
+     (i32.const 1658)
+     (i32.const 0)
+     (i32.const 124)
     )
     (memory.init 4
      (i32.const 1792)
@@ -187,8 +181,8 @@
   )
   (data.drop 0)
   (data.drop 1)
-  (nop)
-  (nop)
+  (data.drop 2)
+  (data.drop 3)
   (data.drop 4)
  )
  (func $3 (result i32)
@@ -12835,87 +12829,3 @@
  )
  ;; custom section "producers", size 172
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "1658": "{ console.log(\"Hello.\"); }",
-    "1685": "throw 'Canceled!'",
-    "1703": "{ setTimeout(function() { __emscripten_do_dispatch_to_thread($0, $1); }, 0); }"
-  },
-  "emJsFuncs": {
-    "initPthreadsJS": "(void)<::>{ PThread.initRuntime(); }",
-    "world": "()<::>{ console.log(\"World.\"); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int",
-    "__cxa_thread_atexit",
-    "__clock_gettime",
-    "emscripten_get_now",
-    "emscripten_conditional_set_current_thread_status",
-    "emscripten_futex_wait",
-    "emscripten_futex_wake",
-    "__assert_fail",
-    "emscripten_set_current_thread_status",
-    "_emscripten_notify_thread_queue",
-    "emscripten_webgl_create_context",
-    "emscripten_set_canvas_element_size",
-    "pthread_create",
-    "emscripten_receive_on_main_thread_js",
-    "emscripten_resize_heap",
-    "fd_write",
-    "setTempRet0"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main",
-    "emscripten_tls_init",
-    "emscripten_get_global_libc",
-    "__errno_location",
-    "fflush",
-    "__emscripten_pthread_data_constructor",
-    "pthread_self",
-    "__pthread_tsd_run_dtors",
-    "emscripten_current_thread_process_queued_calls",
-    "emscripten_register_main_browser_thread_id",
-    "emscripten_main_browser_thread_id",
-    "_emscripten_do_dispatch_to_thread",
-    "emscripten_sync_run_in_main_thread_2",
-    "emscripten_sync_run_in_main_thread_4",
-    "emscripten_main_thread_process_queued_calls",
-    "emscripten_run_in_main_runtime_thread_js",
-    "_emscripten_call_on_thread",
-    "_emscripten_thread_init",
-    "stackSave",
-    "stackRestore",
-    "stackAlloc",
-    "emscripten_stack_init",
-    "emscripten_stack_set_limits",
-    "emscripten_stack_get_free",
-    "emscripten_stack_get_end",
-    "malloc",
-    "free",
-    "memalign",
-    "dynCall_vi",
-    "dynCall_ii",
-    "dynCall_iiii",
-    "dynCall_jiji"
-  ],
-  "namedGlobals": {
-    "_emscripten_allow_main_runtime_queued_calls" : "1432",
-    "_emscripten_main_thread_futex" : "1836"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 1,
-  "features": [
-    "--enable-threads",
-    "--enable-mutable-globals",
-    "--enable-bulk-memory",
-    "--enable-sign-ext"
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm_shared.wat b/test/lld/em_asm_shared.wat
index 39c2323..e4374aa 100644
--- a/test/lld/em_asm_shared.wat
+++ b/test/lld/em_asm_shared.wat
@@ -1,24 +1,25 @@
 (module
  (type $none_=>_none (func))
+ (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
- (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 1))
- (data $.data (global.get $__memory_base) "\00ii\00i\00{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__stack_pointer" (global $__stack_pointer (mut i32)))
  (import "env" "__memory_base" (global $__memory_base i32))
  (import "env" "__table_base" (global $__table_base i32))
- (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<>\20>::buffer (mut i32)))
- (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>\20>::buffer (mut i32)))
- (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int>\20>::buffer (mut i32)))
+ (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<>>::buffer (mut i32)))
+ (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>>::buffer (mut i32)))
+ (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int>>::buffer (mut i32)))
  (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i32 i32 i32) (result i32)))
  (global $global$0 i32 (i32.const 0))
  (global $global$1 i32 (i32.const 1))
  (global $global$2 i32 (i32.const 4))
  (global $global$3 i32 (i32.const 6))
  (global $global$4 i32 (i32.const 90))
+ (data $.data (global.get $__memory_base) "\00ii\00i\00{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
+ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
  (export "__original_main" (func $__original_main))
  (export "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" (global $global$0))
  (export "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" (global $global$1))
@@ -27,7 +28,6 @@
  (export "__start_em_asm" (global $global$3))
  (export "__stop_em_asm" (global $global$4))
  (func $__wasm_call_ctors
-  (call $__wasm_apply_data_relocs)
  )
  (func $__wasm_apply_data_relocs
  )
@@ -50,7 +50,7 @@
      )
      (i32.const 6)
     )
-    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<>\20>::buffer)
+    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<>>::buffer)
     (i32.const 0)
    )
   )
@@ -65,7 +65,7 @@
      (local.get $1)
      (i32.const 39)
     )
-    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>\20>::buffer)
+    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>>::buffer)
     (i32.add
      (local.get $0)
      (i32.const 16)
@@ -78,7 +78,7 @@
      (local.get $1)
      (i32.const 59)
     )
-    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int>\20>::buffer)
+    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int>>::buffer)
     (local.get $0)
    )
   )
@@ -93,7 +93,11 @@
  (func $main (param $0 i32) (param $1 i32) (result i32)
   (call $__original_main)
  )
- ;; custom section "dylink.0", size 6
+ ;; dylink section
+ ;;   memorysize: 90
+ ;;   memoryalignment: 0
+ ;;   tablesize: 0
+ ;;   tablealignment: 0
  ;; custom section "producers", size 112
  ;; features section: mutable-globals
 )
diff --git a/test/lld/em_asm_shared.wat.out b/test/lld/em_asm_shared.wat.out
index b1911bc..0962d35 100644
--- a/test/lld/em_asm_shared.wat.out
+++ b/test/lld/em_asm_shared.wat.out
@@ -4,28 +4,31 @@
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 1))
- (data $.data (global.get $__memory_base) "\00ii\00i\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__stack_pointer" (global $__stack_pointer (mut i32)))
  (import "env" "__memory_base" (global $__memory_base i32))
  (import "env" "__table_base" (global $__table_base i32))
- (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<>\20>::buffer (mut i32)))
- (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>\20>::buffer (mut i32)))
- (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int>\20>::buffer (mut i32)))
+ (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<>>::buffer (mut i32)))
+ (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>>::buffer (mut i32)))
+ (import "GOT.mem" "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE" (global $__em_asm_sig_builder<__em_asm_type_tuple<int>>::buffer (mut i32)))
  (import "env" "emscripten_asm_const_int" (func $emscripten_asm_const_int (param i32 i32 i32) (result i32)))
  (global $global$0 i32 (i32.const 0))
  (global $global$1 i32 (i32.const 1))
  (global $global$2 i32 (i32.const 4))
  (global $global$3 i32 (i32.const 6))
  (global $global$4 i32 (i32.const 90))
+ (data $.data (global.get $__memory_base) "\00ii\00i\00{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
+ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
  (export "__original_main" (func $__original_main))
  (export "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" (global $global$0))
  (export "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" (global $global$1))
  (export "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE" (global $global$2))
  (export "main" (func $main))
+ (export "__start_em_asm" (global $global$3))
+ (export "__stop_em_asm" (global $global$4))
  (func $__wasm_call_ctors
-  (call $__wasm_apply_data_relocs)
+  (nop)
  )
  (func $__wasm_apply_data_relocs
   (nop)
@@ -49,7 +52,7 @@
      )
      (i32.const 6)
     )
-    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<>\20>::buffer)
+    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<>>::buffer)
     (i32.const 0)
    )
   )
@@ -64,7 +67,7 @@
      (local.get $1)
      (i32.const 39)
     )
-    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>\20>::buffer)
+    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int\2c\20int>>::buffer)
     (i32.add
      (local.get $0)
      (i32.const 16)
@@ -77,7 +80,7 @@
      (local.get $1)
      (i32.const 59)
     )
-    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int>\20>::buffer)
+    (global.get $__em_asm_sig_builder<__em_asm_type_tuple<int>>::buffer)
     (local.get $0)
    )
   )
@@ -93,40 +96,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "asmConsts": {
-    "6": "{ Module.print(\"Hello world\"); }",
-    "39": "{ return $0 + $1; }",
-    "59": "{ Module.print(\"Got \" + $0); }"
-  },
-  "declares": [
-    "emscripten_asm_const_int"
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__memory_base",
-    "__table_base",
-    "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE",
-    "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE",
-    "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE"
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "__original_main",
-    "main"
-  ],
-  "namedGlobals": {
-    "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJEEE6bufferE" : "0",
-    "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiiEEE6bufferE" : "1",
-    "_ZN20__em_asm_sig_builderI19__em_asm_type_tupleIJiEEE6bufferE" : "4"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_asm_table.wat.out b/test/lld/em_asm_table.wat.out
index 4a654c1..c91f774 100644
--- a/test/lld/em_asm_table.wat.out
+++ b/test/lld/em_asm_table.wat.out
@@ -29,27 +29,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "emscripten_log",
-    "emscripten_asm_const_int"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "dynCall_vii",
-    "dynCall_iiii"
-  ],
-  "namedGlobals": {
-    "__data_end" : "1048"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/em_js_O0.wat.out b/test/lld/em_js_O0.wat.out
index d0a4feb..c4ddab7 100644
--- a/test/lld/em_js_O0.wat.out
+++ b/test/lld/em_js_O0.wat.out
@@ -1,35 +1,22 @@
 (module
+ (type $none_=>_i32 (func (result i32)))
  (import "env" "memory" (memory $0 256 256))
- (data (i32.const 1024) "(void)<::>{ out(\"no args works\"); }\00(void)<::>{ out(\"no args returning int\"); return 12; }\00(void)<::>{ out(\"no args returning double\"); return 12.25; }\00(int x)<::>{ out(\"  takes ints: \" + x);}\00(double d)<::>{ out(\"  takes doubles: \" + d);}\00(char* str)<::>{ out(\"  takes strings: \" + UTF8ToString(str)); return 7.75; }\00(int x, int y)<::>{ out(\"  takes multiple ints: \" + x + \", \" + y); return 6; }\00(int x, const char* str, double d)<::>{ out(\"  mixed arg types: \" + x + \", \" + UTF8ToString(str) + \", \" + d); return 8.125; }\00(int unused)<::>{ out(\"  ignores unused args\"); return 5.5; }\00(int x, int y)<::>{ out(\"  skips unused args: \" + y); return 6; }\00(double x, double y, double z)<::>{ out(\"  \" + x + \" + \" + z); return x + z; }\00(void)<::>{ out(\"  can use <::> separator in user code\"); return 15; }\00(void)<::>{ var x, y; x = {}; y = 3; x[y] = [1, 2, 3]; out(\"  can have commas in user code: \" + x[y]); return x[y][1]; }\00(void)<::>{ var jsString = \'\e3\81\93\e3\82\93\e3\81\ab\e3\81\a1\e3\81\af\'; var lengthBytes = lengthBytesUTF8(jsString); var stringOnWasmHeap = _malloc(lengthBytes); stringToUTF8(jsString, stringOnWasmHeap, lengthBytes+1); return stringOnWasmHeap; }\00(void)<::>{ var jsString = \'hello from js\'; var lengthBytes = jsString.length+1; var stringOnWasmHeap = _malloc(lengthBytes); stringToUTF8(jsString, stringOnWasmHeap, lengthBytes+1); return stringOnWasmHeap; }\00BEGIN\n\00    noarg_int returned: %d\n\00    noarg_double returned: %f\n\00    stringarg returned: %f\n\00string arg\00    multi_intarg returned: %d\n\00    multi_mixedarg returned: %f\n\00hello\00    unused_args returned: %d\n\00    skip_args returned: %f\n\00    add_outer returned: %f\n\00    user_separator returned: %d\n\00    user_comma returned: %d\n\00    return_str returned: %s\n\00    return_utf8_str returned: %s\n\00END\n\00\00\cc\1a\00\00\00\00\00\00\00\00\00\00\00\00\00\00T!\"\19\0d\01\02\03\11K\1c\0c\10\04\0b\1d\12\1e\'hnopqb \05\06\0f\13\14\15\1a\08\16\07($\17\18\t\n\0e\1b\1f%#\83\82}&*+<=>?CGJMXYZ[\\]^_`acdefgijklrstyz{|\00\00\00\00\00\00\00\00\00Illegal byte sequence\00Domain error\00Result not representable\00Not a tty\00Permission denied\00Operation not permitted\00No such file or directory\00No such process\00File exists\00Value too large for data type\00No space left on device\00Out of memory\00Resource busy\00Interrupted system call\00Resource temporarily unavailable\00Invalid seek\00Cross-device link\00Read-only file system\00Directory not empty\00Connection reset by peer\00Operation timed out\00Connection refused\00Host is down\00Host is unreachable\00Address in use\00Broken pipe\00I/O error\00No such device or address\00Block device required\00No such device\00Not a directory\00Is a directory\00Text file busy\00Exec format error\00Invalid argument\00Argument list too long\00Symbolic link loop\00Filename too long\00Too many open files in system\00No file descriptors available\00Bad file descriptor\00No child process\00Bad address\00File too large\00Too many links\00No locks available\00Resource deadlock would occur\00State not recoverable\00Previous owner died\00Operation canceled\00Function not implemented\00No message of desired type\00Identifier removed\00Device not a stream\00No data available\00Device timeout\00Out of streams resources\00Link has been severed\00Protocol error\00Bad message\00File descriptor in bad state\00Not a socket\00Destination address required\00Message too large\00Protocol wrong type for socket\00Protocol not available\00Protocol not supported\00Socket type not supported\00Not supported\00Protocol family not supported\00Address family not supported by protocol\00Address not available\00Network is down\00Network unreachable\00Connection reset by network\00Connection aborted\00No buffer space available\00Socket is connected\00Socket not connected\00Cannot send after socket shutdown\00Operation already in progress\00Operation in progress\00Stale file handle\00Remote I/O error\00Quota exceeded\00No medium found\00Wrong medium type\00No error information\00\00-+   0X0x\00(null)\00\00\00\00\11\00\n\00\11\11\11\00\00\00\00\05\00\00\00\00\00\00\t\00\00\00\00\0b\00\00\00\00\00\00\00\00\11\00\0f\n\11\11\11\03\n\07\00\01\13\t\0b\0b\00\00\t\06\0b\00\00\0b\00\06\11\00\00\00\11\11\11\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0b\00\00\00\00\00\00\00\00\11\00\n\n\11\11\11\00\n\00\00\02\00\t\0b\00\00\00\t\00\0b\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\0c\00\00\00\00\t\0c\00\00\00\00\00\0c\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0e\00\00\00\00\00\00\00\00\00\00\00\0d\00\00\00\04\0d\00\00\00\00\t\0e\00\00\00\00\00\0e\00\00\0e\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\00\00\00\00\0f\00\00\00\00\0f\00\00\00\00\t\10\00\00\00\00\00\10\00\00\10\00\00\12\00\00\00\12\12\12\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\12\00\00\00\12\12\12\00\00\00\00\00\00\t\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\n\00\00\00\00\n\00\00\00\00\t\0b\00\00\00\00\00\0b\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\0c\00\00\00\00\t\0c\00\00\00\00\00\0c\00\00\0c\00\000123456789ABCDEF-0X+0X 0X-0x+0x 0x\00inf\00INF\00nan\00NAN\00.\00")
- (data (i32.const 5232) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
- (data (i32.const 6860) "\05\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\03\00\00\00\04\00\00\00\88\14\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\cc\1a\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\b0\18\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
  (global $global$0 (mut i32) (i32.const 5250112))
  (global $global$1 i32 (i32.const 5250112))
  (global $global$2 i32 (i32.const 7232))
+ (data (i32.const 1024) "(void)<::>{ out(\"no args works\"); }\00(void)<::>{ out(\"no args returning int\"); return 12; }\00(void)<::>{ out(\"no args returning double\"); return 12.25; }\00(int x)<::>{ out(\"  takes ints: \" + x);}\00(double d)<::>{ out(\"  takes doubles: \" + d);}\00(char* str)<::>{ out(\"  takes strings: \" + UTF8ToString(str)); return 7.75; }\00(int x, int y)<::>{ out(\"  takes multiple ints: \" + x + \", \" + y); return 6; }\00(int x, const char* str, double d)<::>{ out(\"  mixed arg types: \" + x + \", \" + UTF8ToString(str) + \", \" + d); return 8.125; }\00(int unused)<::>{ out(\"  ignores unused args\"); return 5.5; }\00(int x, int y)<::>{ out(\"  skips unused args: \" + y); return 6; }\00(double x, double y, double z)<::>{ out(\"  \" + x + \" + \" + z); return x + z; }\00(void)<::>{ out(\"  can use <::> separator in user code\"); return 15; }\00(void)<::>{ var x, y; x = {}; y = 3; x[y] = [1, 2, 3]; out(\"  can have commas in user code: \" + x[y]); return x[y][1]; }\00(void)<::>{ var jsString = \'\e3\81\93\e3\82\93\e3\81\ab\e3\81\a1\e3\81\af\'; var lengthBytes = lengthBytesUTF8(jsString); var stringOnWasmHeap = _malloc(lengthBytes); stringToUTF8(jsString, stringOnWasmHeap, lengthBytes+1); return stringOnWasmHeap; }\00(void)<::>{ var jsString = \'hello from js\'; var lengthBytes = jsString.length+1; var stringOnWasmHeap = _malloc(lengthBytes); stringToUTF8(jsString, stringOnWasmHeap, lengthBytes+1); return stringOnWasmHeap; }\00BEGIN\n\00    noarg_int returned: %d\n\00    noarg_double returned: %f\n\00    stringarg returned: %f\n\00string arg\00    multi_intarg returned: %d\n\00    multi_mixedarg returned: %f\n\00hello\00    unused_args returned: %d\n\00    skip_args returned: %f\n\00    add_outer returned: %f\n\00    user_separator returned: %d\n\00    user_comma returned: %d\n\00    return_str returned: %s\n\00    return_utf8_str returned: %s\n\00END\n\00\00\cc\1a\00\00\00\00\00\00\00\00\00\00\00\00\00\00T!\"\19\r\01\02\03\11K\1c\0c\10\04\0b\1d\12\1e\'hnopqb \05\06\0f\13\14\15\1a\08\16\07($\17\18\t\n\0e\1b\1f%#\83\82}&*+<=>?CGJMXYZ[\\]^_`acdefgijklrstyz{|\00\00\00\00\00\00\00\00\00Illegal byte sequence\00Domain error\00Result not representable\00Not a tty\00Permission denied\00Operation not permitted\00No such file or directory\00No such process\00File exists\00Value too large for data type\00No space left on device\00Out of memory\00Resource busy\00Interrupted system call\00Resource temporarily unavailable\00Invalid seek\00Cross-device link\00Read-only file system\00Directory not empty\00Connection reset by peer\00Operation timed out\00Connection refused\00Host is down\00Host is unreachable\00Address in use\00Broken pipe\00I/O error\00No such device or address\00Block device required\00No such device\00Not a directory\00Is a directory\00Text file busy\00Exec format error\00Invalid argument\00Argument list too long\00Symbolic link loop\00Filename too long\00Too many open files in system\00No file descriptors available\00Bad file descriptor\00No child process\00Bad address\00File too large\00Too many links\00No locks available\00Resource deadlock would occur\00State not recoverable\00Previous owner died\00Operation canceled\00Function not implemented\00No message of desired type\00Identifier removed\00Device not a stream\00No data available\00Device timeout\00Out of streams resources\00Link has been severed\00Protocol error\00Bad message\00File descriptor in bad state\00Not a socket\00Destination address required\00Message too large\00Protocol wrong type for socket\00Protocol not available\00Protocol not supported\00Socket type not supported\00Not supported\00Protocol family not supported\00Address family not supported by protocol\00Address not available\00Network is down\00Network unreachable\00Connection reset by network\00Connection aborted\00No buffer space available\00Socket is connected\00Socket not connected\00Cannot send after socket shutdown\00Operation already in progress\00Operation in progress\00Stale file handle\00Remote I/O error\00Quota exceeded\00No medium found\00Wrong medium type\00No error information\00\00-+   0X0x\00(null)\00\00\00\00\11\00\n\00\11\11\11\00\00\00\00\05\00\00\00\00\00\00\t\00\00\00\00\0b\00\00\00\00\00\00\00\00\11\00\0f\n\11\11\11\03\n\07\00\01\13\t\0b\0b\00\00\t\06\0b\00\00\0b\00\06\11\00\00\00\11\11\11\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0b\00\00\00\00\00\00\00\00\11\00\n\n\11\11\11\00\n\00\00\02\00\t\0b\00\00\00\t\00\0b\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\0c\00\00\00\00\t\0c\00\00\00\00\00\0c\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0e\00\00\00\00\00\00\00\00\00\00\00\r\00\00\00\04\r\00\00\00\00\t\0e\00\00\00\00\00\0e\00\00\0e\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\00\00\00\00\0f\00\00\00\00\0f\00\00\00\00\t\10\00\00\00\00\00\10\00\00\10\00\00\12\00\00\00\12\12\12\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\12\00\00\00\12\12\12\00\00\00\00\00\00\t\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\n\00\00\00\00\n\00\00\00\00\t\0b\00\00\00\00\00\0b\00\00\0b\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\00\00\00\00\00\00\00\0c\00\00\00\00\0c\00\00\00\00\t\0c\00\00\00\00\00\0c\00\00\0c\00\000123456789ABCDEF-0X+0X 0X-0x+0x 0x\00inf\00INF\00nan\00NAN\00.\00")
+ (data (i32.const 5232) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
+ (data (i32.const 6860) "\05\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\03\00\00\00\04\00\00\00\88\14\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\cc\1a\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\b0\18\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
+ (export "__em_js__noarg" (func $__em_js__noarg))
  (export "__heap_base" (global $global$1))
  (export "__data_end" (global $global$2))
+ (func $__em_js__noarg (result i32)
+  (local $0 i32)
+  (local.set $0
+   (i32.const 1024)
+  )
+  (return
+   (local.get $0)
+  )
+ )
 )
-(;
---BEGIN METADATA --
-{
-  "emJsFuncs": {
-    "noarg": "(void)<::>{ out(\"no args works\"); }"
-  },
-  "declares": [
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-  ],
-  "namedGlobals": {
-    "__heap_base" : "5250112",
-    "__data_end" : "7232"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/hello_world.passive.wat.out b/test/lld/hello_world.passive.wat.out
index 1c96749..4cd5ed3 100644
--- a/test/lld/hello_world.passive.wat.out
+++ b/test/lld/hello_world.passive.wat.out
@@ -37,27 +37,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "puts"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66128",
-    "__data_end" : "581"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/hello_world.wat b/test/lld/hello_world.wat
index 71f464a..335bcb8 100644
--- a/test/lld/hello_world.wat
+++ b/test/lld/hello_world.wat
@@ -1,13 +1,13 @@
 (module
+ (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $none_=>_none (func))
  (type $none_=>_i32 (func (result i32)))
- (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "puts" (func $puts (param i32) (result i32)))
+ (global $__stack_pointer (mut i32) (i32.const 66128))
  (memory $0 2)
  (data $.rodata (i32.const 568) "Hello, world\00")
  (table $0 1 1 funcref)
- (global $__stack_pointer (mut i32) (i32.const 66128))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
diff --git a/test/lld/hello_world.wat.mem.out b/test/lld/hello_world.wat.mem.out
index f55fbcf..c482a74 100644
--- a/test/lld/hello_world.wat.mem.out
+++ b/test/lld/hello_world.wat.mem.out
@@ -25,25 +25,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "puts"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/hello_world.wat.out b/test/lld/hello_world.wat.out
index de4d967..8081048 100644
--- a/test/lld/hello_world.wat.out
+++ b/test/lld/hello_world.wat.out
@@ -26,25 +26,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "puts"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/init.wat b/test/lld/init.wat
index 3d088e7..ea81a12 100644
--- a/test/lld/init.wat
+++ b/test/lld/init.wat
@@ -2,9 +2,9 @@
  (type $none_=>_none (func))
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
+ (global $__stack_pointer (mut i32) (i32.const 66112))
  (memory $0 2)
  (table $0 1 1 funcref)
- (global $__stack_pointer (mut i32) (i32.const 66112))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
diff --git a/test/lld/init.wat.out b/test/lld/init.wat.out
index 7525cf1..68388ee 100644
--- a/test/lld/init.wat.out
+++ b/test/lld/init.wat.out
@@ -38,24 +38,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/longjmp.wat b/test/lld/longjmp.wat
index f20134c..122b6bb 100644
--- a/test/lld/longjmp.wat
+++ b/test/lld/longjmp.wat
@@ -1,13 +1,13 @@
 (module
- (type $i32_=>_none (func (param i32)))
  (type $none_=>_i32 (func (result i32)))
- (type $none_=>_none (func))
+ (type $i32_=>_none (func (param i32)))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
  (type $i32_i32_=>_none (func (param i32 i32)))
  (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
- (type $i32_=>_i32 (func (param i32) (result i32)))
- (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
- (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
+ (type $none_=>_none (func))
+ (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "malloc" (func $malloc (param i32) (result i32)))
  (import "env" "saveSetjmp" (func $saveSetjmp (param i32 i32 i32 i32) (result i32)))
  (import "env" "getTempRet0" (func $getTempRet0 (result i32)))
@@ -16,10 +16,10 @@
  (import "env" "testSetjmp" (func $testSetjmp (param i32 i32 i32) (result i32)))
  (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
  (import "env" "free" (func $free (param i32)))
+ (global $__stack_pointer (mut i32) (i32.const 66112))
  (memory $0 2)
  (table $0 2 2 funcref)
  (elem (i32.const 1) $emscripten_longjmp)
- (global $__stack_pointer (mut i32) (i32.const 66112))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
diff --git a/test/lld/longjmp.wat.out b/test/lld/longjmp.wat.out
index bef99c9..3bb1c6d 100644
--- a/test/lld/longjmp.wat.out
+++ b/test/lld/longjmp.wat.out
@@ -141,33 +141,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "malloc",
-    "saveSetjmp",
-    "getTempRet0",
-    "emscripten_longjmp",
-    "testSetjmp",
-    "setTempRet0",
-    "free"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main",
-    "dynCall_vii"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-    "invoke_vii"
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/main_module.wat.out b/test/lld/main_module.wat.out
index fb1b131..1761b6d 100644
--- a/test/lld/main_module.wat.out
+++ b/test/lld/main_module.wat.out
@@ -3,7 +3,6 @@
  (type $0 (func (param i32) (result i32)))
  (type $2 (func (result i32)))
  (import "env" "memory" (memory $0 0))
- (data (global.get $gimport$2) "Hello, world\00\00\00\00\00\00\00\00\00\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$1 0 funcref))
  (import "env" "__stack_pointer" (global $sp (mut i32)))
  (import "env" "__memory_base" (global $gimport$2 i32))
@@ -15,6 +14,7 @@
  (global $global$0 i32 (i32.const 16))
  (global $global$1 i32 (i32.const 20))
  (global $global i32 (i32.const 42))
+ (data (global.get $gimport$2) "Hello, world\00\00\00\00\00\00\00\00\00\00\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "_Z13print_messagev" (func $print_message\28\29))
  (export "ptr_puts" (global $global$0))
@@ -53,34 +53,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "puts"
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__memory_base",
-    "__table_base",
-    "external_var",
-    "puts",
-    "_Z13print_messagev"
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "_Z13print_messagev"
-  ],
-  "namedGlobals": {
-    "ptr_puts" : "16",
-    "ptr_local_func" : "20",
-    "__data_end" : "42"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/main_module_table.wat.out b/test/lld/main_module_table.wat.out
index dd37f93..368df93 100644
--- a/test/lld/main_module_table.wat.out
+++ b/test/lld/main_module_table.wat.out
@@ -9,26 +9,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__stdio_write"
-  ],
-  "exports": [
-    "__stdio_write"
-  ],
-  "namedGlobals": {
-    "__data_end" : "42"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/main_module_table_2.wat.out b/test/lld/main_module_table_2.wat.out
index 37a1628..7d4046d 100644
--- a/test/lld/main_module_table_2.wat.out
+++ b/test/lld/main_module_table_2.wat.out
@@ -10,26 +10,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__stdio_write"
-  ],
-  "exports": [
-    "__stdio_write"
-  ],
-  "namedGlobals": {
-    "__data_end" : "42"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/main_module_table_3.wat.out b/test/lld/main_module_table_3.wat.out
index 8153a27..72a29a7 100644
--- a/test/lld/main_module_table_3.wat.out
+++ b/test/lld/main_module_table_3.wat.out
@@ -11,26 +11,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__stdio_write"
-  ],
-  "exports": [
-    "__stdio_write"
-  ],
-  "namedGlobals": {
-    "__data_end" : "42"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/main_module_table_4.wat.out b/test/lld/main_module_table_4.wat.out
index e09e422..09008d4 100644
--- a/test/lld/main_module_table_4.wat.out
+++ b/test/lld/main_module_table_4.wat.out
@@ -12,27 +12,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__stdio_write",
-    "__table_base"
-  ],
-  "exports": [
-    "__stdio_write"
-  ],
-  "namedGlobals": {
-    "__data_end" : "42"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/main_module_table_5.wat.out b/test/lld/main_module_table_5.wat.out
index bf543f9..3620dc0 100644
--- a/test/lld/main_module_table_5.wat.out
+++ b/test/lld/main_module_table_5.wat.out
@@ -25,28 +25,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__stdio_write",
-    "__table_base"
-  ],
-  "exports": [
-    "__stdio_write",
-    "dynCall_v"
-  ],
-  "namedGlobals": {
-    "__data_end" : "42"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/no-emit-metadata.wat b/test/lld/no-emit-metadata.wat
deleted file mode 100644
index 2ccbf3f..0000000
--- a/test/lld/no-emit-metadata.wat
+++ /dev/null
@@ -1,20 +0,0 @@
-(module
- (memory $0 2)
- (table $0 1 1 funcref)
- (elem (i32.const 0) $foo)
- (global $global$0 (mut i32) (i32.const 66112))
- (global $global$1 i32 (i32.const 66112))
- (global $global$2 i32 (i32.const 576))
- (export "memory" (memory $0))
- (export "main" (func $main))
- (export "__heap_base" (global $global$1))
- (export "__data_end" (global $global$2))
- (func $__original_main (result i32)
-  (nop)
- )
- (func $main (param $0 i32) (param $1 i32) (result i32)
-  (call $__original_main)
- )
- (func $foo (result i32))
-)
-
diff --git a/test/lld/no-emit-metadata.wat.out b/test/lld/no-emit-metadata.wat.out
deleted file mode 100644
index 281eba9..0000000
--- a/test/lld/no-emit-metadata.wat.out
+++ /dev/null
@@ -1,30 +0,0 @@
-(module
- (type $none_=>_i32 (func (result i32)))
- (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
- (type $i32_=>_i32 (func (param i32) (result i32)))
- (global $global$0 (mut i32) (i32.const 66112))
- (global $global$1 i32 (i32.const 66112))
- (global $global$2 i32 (i32.const 576))
- (memory $0 2)
- (table $0 1 1 funcref)
- (elem (i32.const 0) $foo)
- (export "memory" (memory $0))
- (export "main" (func $main))
- (export "__heap_base" (global $global$1))
- (export "__data_end" (global $global$2))
- (export "dynCall_i" (func $dynCall_i))
- (func $__original_main (result i32)
-  (nop)
- )
- (func $main (param $0 i32) (param $1 i32) (result i32)
-  (call $__original_main)
- )
- (func $foo (result i32)
-  (nop)
- )
- (func $dynCall_i (param $fptr i32) (result i32)
-  (call_indirect (type $none_=>_i32)
-   (local.get $fptr)
-  )
- )
-)
diff --git a/test/lld/recursive.wat b/test/lld/recursive.wat
index 96be1d1..c9d30aa 100644
--- a/test/lld/recursive.wat
+++ b/test/lld/recursive.wat
@@ -3,10 +3,10 @@
  (type $none_=>_none (func))
  (type $none_=>_i32 (func (result i32)))
  (import "env" "iprintf" (func $iprintf (param i32 i32) (result i32)))
+ (global $__stack_pointer (mut i32) (i32.const 66128))
  (memory $0 2)
  (data $.rodata (i32.const 568) "%d:%d\n\00Result: %d\n\00")
  (table $0 1 1 funcref)
- (global $__stack_pointer (mut i32) (i32.const 66128))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
diff --git a/test/lld/recursive.wat.out b/test/lld/recursive.wat.out
index 5ffbf82..ae7945a 100644
--- a/test/lld/recursive.wat.out
+++ b/test/lld/recursive.wat.out
@@ -83,25 +83,3 @@
   (call $__original_main)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "iprintf"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/recursive_safe_stack.wat.out b/test/lld/recursive_safe_stack.wat.out
index d6cd8f6..f5808ee 100644
--- a/test/lld/recursive_safe_stack.wat.out
+++ b/test/lld/recursive_safe_stack.wat.out
@@ -181,29 +181,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "printf",
-    "__handle_stack_overflow"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main",
-    "__set_stack_limits"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66128",
-    "__data_end" : "587"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/reserved_func_ptr.wat b/test/lld/reserved_func_ptr.wat
index cee65a3..38a7b86 100644
--- a/test/lld/reserved_func_ptr.wat
+++ b/test/lld/reserved_func_ptr.wat
@@ -2,18 +2,18 @@
  (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
  (type $none_=>_none (func))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
- (type $i32_=>_none (func (param i32)))
  (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $i32_=>_none (func (param i32)))
  (type $f32_f32_i32_=>_f32 (func (param f32 f32 i32) (result f32)))
  (type $f64_i32_=>_f64 (func (param f64 i32) (result f64)))
  (import "env" "_Z4atoiPKc" (func $atoi\28char\20const*\29 (param i32) (result i32)))
+ (global $__stack_pointer (mut i32) (i32.const 66112))
  (memory $0 2)
  (table $0 3 3 funcref)
  (elem (i32.const 1) $address_taken_func\28int\2c\20int\2c\20int\29 $address_taken_func2\28int\2c\20int\2c\20int\29)
- (global $__stack_pointer (mut i32) (i32.const 66112))
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
- (export "main" (func $main))
+ (export "__main_argc_argv" (func $main))
  (func $__wasm_call_ctors
  )
  (func $address_taken_func\28int\2c\20int\2c\20int\29 (param $0 i32) (param $1 i32) (param $2 i32)
diff --git a/test/lld/reserved_func_ptr.wat.out b/test/lld/reserved_func_ptr.wat.out
index a661989..d20ecbc 100644
--- a/test/lld/reserved_func_ptr.wat.out
+++ b/test/lld/reserved_func_ptr.wat.out
@@ -14,7 +14,7 @@
  (elem (i32.const 1) $address_taken_func\28int\2c\20int\2c\20int\29 $address_taken_func2\28int\2c\20int\2c\20int\29)
  (export "memory" (memory $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
- (export "main" (func $main))
+ (export "__main_argc_argv" (func $main))
  (export "dynCall_viii" (func $dynCall_viii))
  (func $__wasm_call_ctors
   (nop)
@@ -118,26 +118,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "_Z4atoiPKc"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main",
-    "dynCall_viii"
-  ],
-  "namedGlobals": {
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 1,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/safe_stack_standalone-wasm.wat.out b/test/lld/safe_stack_standalone-wasm.wat.out
index b5bd4a8..876e9fa 100644
--- a/test/lld/safe_stack_standalone-wasm.wat.out
+++ b/test/lld/safe_stack_standalone-wasm.wat.out
@@ -171,27 +171,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "printf"
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "main",
-    "__set_stack_limits"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66128",
-    "__data_end" : "587"
-  },
-  "invokeFuncs": [
-  ],
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/shared.wat b/test/lld/shared.wat
index 5a403aa..5b7574b 100644
--- a/test/lld/shared.wat
+++ b/test/lld/shared.wat
@@ -1,9 +1,8 @@
 (module
  (type $none_=>_none (func))
- (type $none_=>_i32 (func (result i32)))
  (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $none_=>_i32 (func (result i32)))
  (import "env" "memory" (memory $mimport$0 1))
- (data $.data (global.get $__memory_base) "Hello, world\00\00\00\00\00\00\00\00\00\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__memory_base" (global $__memory_base i32))
  (import "env" "__table_base" (global $__table_base i32))
@@ -13,25 +12,26 @@
  (import "env" "puts" (func $puts (param i32) (result i32)))
  (global $global$0 i32 (i32.const 16))
  (global $global$1 i32 (i32.const 20))
+ (data $.data (global.get $__memory_base) "Hello, world\00\00\00\00\00\00\00\00\00\00\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
+ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
  (export "_Z13print_messagev" (func $print_message\28\29))
  (export "ptr_puts" (global $global$0))
  (export "ptr_local_func" (global $global$1))
  (func $__wasm_call_ctors
-  (call $__wasm_apply_data_relocs)
  )
  (func $__wasm_apply_data_relocs
   (i32.store
    (i32.add
-    (global.get $__memory_base)
     (i32.const 16)
+    (global.get $__memory_base)
    )
    (global.get $puts)
   )
   (i32.store
    (i32.add
-    (global.get $__memory_base)
     (i32.const 20)
+    (global.get $__memory_base)
    )
    (global.get $print_message\28\29)
   )
@@ -49,7 +49,11 @@
    (global.get $external_var)
   )
  )
- ;; custom section "dylink.0", size 6
+ ;; dylink section
+ ;;   memorysize: 24
+ ;;   memoryalignment: 2
+ ;;   tablesize: 0
+ ;;   tablealignment: 0
  ;; custom section "producers", size 112
  ;; features section: mutable-globals
 )
diff --git a/test/lld/shared.wat.out b/test/lld/shared.wat.out
index 209c3db..1a496e4 100644
--- a/test/lld/shared.wat.out
+++ b/test/lld/shared.wat.out
@@ -3,7 +3,6 @@
  (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $none_=>_i32 (func (result i32)))
  (import "env" "memory" (memory $mimport$0 1))
- (data $.data (global.get $__memory_base) "Hello, world\00\00\00\00\00\00\00\00\00\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__memory_base" (global $__memory_base i32))
  (import "env" "__table_base" (global $__table_base i32))
@@ -13,25 +12,27 @@
  (import "env" "puts" (func $puts (param i32) (result i32)))
  (global $global$0 i32 (i32.const 16))
  (global $global$1 i32 (i32.const 20))
+ (data $.data (global.get $__memory_base) "Hello, world\00\00\00\00\00\00\00\00\00\00\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
+ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
  (export "_Z13print_messagev" (func $print_message\28\29))
  (export "ptr_puts" (global $global$0))
  (export "ptr_local_func" (global $global$1))
  (func $__wasm_call_ctors
-  (call $__wasm_apply_data_relocs)
+  (nop)
  )
  (func $__wasm_apply_data_relocs
   (i32.store
    (i32.add
-    (global.get $__memory_base)
     (i32.const 16)
+    (global.get $__memory_base)
    )
    (global.get $puts)
   )
   (i32.store
    (i32.add
-    (global.get $__memory_base)
     (i32.const 20)
+    (global.get $__memory_base)
    )
    (global.get $print_message\28\29)
   )
@@ -50,32 +51,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "puts"
-  ],
-  "globalImports": [
-    "__memory_base",
-    "__table_base",
-    "external_var",
-    "puts",
-    "_Z13print_messagev"
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "_Z13print_messagev"
-  ],
-  "namedGlobals": {
-    "ptr_puts" : "16",
-    "ptr_local_func" : "20"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/shared_add_to_table.wasm.out b/test/lld/shared_add_to_table.wasm.out
index 82d4165..91be038 100644
--- a/test/lld/shared_add_to_table.wasm.out
+++ b/test/lld/shared_add_to_table.wasm.out
@@ -4,7 +4,6 @@
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 0))
- (data (global.get $gimport$1) "*\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__stack_pointer" (global $gimport$0 (mut i32)))
  (import "env" "__memory_base" (global $gimport$1 i32))
@@ -16,6 +15,7 @@
  (import "env" "_Z16waka_func_theirsi" (func $waka_func_theirs\28int\29 (param i32) (result i32)))
  (global $global$0 i32 (i32.const 0))
  (global $global$1 i32 (i32.const 0))
+ (data (global.get $gimport$1) "*\00\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "__wasm_apply_relocs" (func $__wasm_apply_relocs))
  (export "_Z14waka_func_minei" (func $waka_func_mine\28int\29))
@@ -68,37 +68,3 @@
  ;;   tablealignment: 0
  ;; custom section "producers", size 157
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "_Z16waka_func_theirsi"
-  ],
-  "globalImports": [
-    "__stack_pointer",
-    "__memory_base",
-    "__table_base",
-    "_Z16waka_func_theirsi",
-    "_Z14waka_func_minei",
-    "waka_mine",
-    "waka_others"
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "__wasm_apply_relocs",
-    "_Z14waka_func_minei",
-    "__original_main",
-    "main"
-  ],
-  "namedGlobals": {
-    "waka_mine" : "0",
-    "__dso_handle" : "0"
-  },
-  "invokeFuncs": [
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/shared_longjmp.wat b/test/lld/shared_longjmp.wat
index c7d6577..d217e6d 100644
--- a/test/lld/shared_longjmp.wat
+++ b/test/lld/shared_longjmp.wat
@@ -1,14 +1,13 @@
 (module
  (type $none_=>_none (func))
  (type $i32_=>_none (func (param i32)))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
+ (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_none (func (param i32 i32)))
  (type $i32_i32_i32_=>_none (func (param i32 i32 i32)))
- (type $none_=>_i32 (func (result i32)))
- (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
- (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 1))
- (data $.bss (global.get $__memory_base) "\00\00\00\00\00\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__memory_base" (global $__memory_base i32))
  (import "env" "__table_base" (global $__table_base i32))
@@ -25,12 +24,13 @@
  (import "env" "free" (func $free (param i32)))
  (global $global$0 i32 (i32.const 0))
  (global $global$1 i32 (i32.const 4))
+ (data $.bss (global.get $__memory_base) "\00\00\00\00\00\00\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
+ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
  (export "_start" (func $_start))
  (export "__THREW__" (global $global$0))
  (export "__threwValue" (global $global$1))
  (func $__wasm_call_ctors
-  (call $__wasm_apply_data_relocs)
  )
  (func $__wasm_apply_data_relocs
  )
@@ -140,7 +140,11 @@
   )
   (unreachable)
  )
- ;; custom section "dylink.0", size 6
+ ;; dylink section
+ ;;   memorysize: 8
+ ;;   memoryalignment: 2
+ ;;   tablesize: 0
+ ;;   tablealignment: 0
  ;; custom section "producers", size 112
  ;; features section: mutable-globals
 )
diff --git a/test/lld/shared_longjmp.wat.out b/test/lld/shared_longjmp.wat.out
index 9515c96..4497142 100644
--- a/test/lld/shared_longjmp.wat.out
+++ b/test/lld/shared_longjmp.wat.out
@@ -8,7 +8,6 @@
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_i32_=>_i32 (func (param i32 i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 1))
- (data $.bss (global.get $__memory_base) "\00\00\00\00\00\00\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 0 funcref))
  (import "env" "__memory_base" (global $__memory_base i32))
  (import "env" "__table_base" (global $__table_base i32))
@@ -25,13 +24,15 @@
  (import "env" "free" (func $free (param i32)))
  (global $global$0 i32 (i32.const 0))
  (global $global$1 i32 (i32.const 4))
+ (data $.bss (global.get $__memory_base) "\00\00\00\00\00\00\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
+ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
  (export "_start" (func $_start))
  (export "__THREW__" (global $global$0))
  (export "__threwValue" (global $global$1))
  (export "dynCall_vii" (func $dynCall_vii))
  (func $__wasm_call_ctors
-  (call $__wasm_apply_data_relocs)
+  (nop)
  )
  (func $__wasm_apply_data_relocs
   (nop)
@@ -150,40 +151,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-    "malloc",
-    "saveSetjmp",
-    "getTempRet0",
-    "emscripten_longjmp",
-    "testSetjmp",
-    "setTempRet0",
-    "free"
-  ],
-  "globalImports": [
-    "__memory_base",
-    "__table_base",
-    "__THREW__",
-    "emscripten_longjmp",
-    "__threwValue"
-  ],
-  "exports": [
-    "__wasm_call_ctors",
-    "_start",
-    "dynCall_vii"
-  ],
-  "namedGlobals": {
-    "__THREW__" : "0",
-    "__threwValue" : "4"
-  },
-  "invokeFuncs": [
-    "invoke_vii"
-  ],
-  "mainReadsParams": 0,
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/standalone-wasm-with-start.wat.out b/test/lld/standalone-wasm-with-start.wat.out
index b5bbb22..e158a74 100644
--- a/test/lld/standalone-wasm-with-start.wat.out
+++ b/test/lld/standalone-wasm-with-start.wat.out
@@ -17,24 +17,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "_start"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66112",
-    "__data_end" : "576"
-  },
-  "invokeFuncs": [
-  ],
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/standalone-wasm.wat.out b/test/lld/standalone-wasm.wat.out
index 54519e8..dfce73e 100644
--- a/test/lld/standalone-wasm.wat.out
+++ b/test/lld/standalone-wasm.wat.out
@@ -21,24 +21,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "main"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66112",
-    "__data_end" : "576"
-  },
-  "invokeFuncs": [
-  ],
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/standalone-wasm2.wat.out b/test/lld/standalone-wasm2.wat.out
index 8863dc1..bc8c94d 100644
--- a/test/lld/standalone-wasm2.wat.out
+++ b/test/lld/standalone-wasm2.wat.out
@@ -18,24 +18,3 @@
   )
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-    "main"
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66112",
-    "__data_end" : "576"
-  },
-  "invokeFuncs": [
-  ],
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/lld/standalone-wasm3.wat.out b/test/lld/standalone-wasm3.wat.out
index df6c277..bbbd38c 100644
--- a/test/lld/standalone-wasm3.wat.out
+++ b/test/lld/standalone-wasm3.wat.out
@@ -11,23 +11,3 @@
   (nop)
  )
 )
-(;
---BEGIN METADATA --
-{
-  "declares": [
-  ],
-  "globalImports": [
-  ],
-  "exports": [
-  ],
-  "namedGlobals": {
-    "__heap_base" : "66112",
-    "__data_end" : "576"
-  },
-  "invokeFuncs": [
-  ],
-  "features": [
-  ]
-}
--- END METADATA --
-;)
diff --git a/test/metadce/outside.wast.dced b/test/metadce/outside.wast.dced
index a1c74c8..246019d 100644
--- a/test/metadce/outside.wast.dced
+++ b/test/metadce/outside.wast.dced
@@ -1,13 +1,13 @@
 (module
  (type $none_=>_none (func))
  (import "env" "memory" (memory $0 256 256))
- (data (i32.const 1024) "abcd")
- (data (global.get $from_segment) "abcd")
  (import "env" "table" (table $timport$0 10 10 funcref))
  (import "env" "js_func" (func $a_js_func))
  (global $__THREW__ (mut i32) (i32.const 0))
  (global $from_segment (mut i32) (i32.const 0))
  (global $from_segment_2 (mut i32) (i32.const 0))
+ (data (i32.const 1024) "abcd")
+ (data (global.get $from_segment) "abcd")
  (elem (global.get $from_segment_2) $table_func)
  (export "wasm_func" (func $a_wasm_func))
  (func $table_func
diff --git a/test/multi-memories-atomics64.wast b/test/multi-memories-atomics64.wast
new file mode 100644
index 0000000..565a5e6
--- /dev/null
+++ b/test/multi-memories-atomics64.wast
@@ -0,0 +1,352 @@
+(module
+ (type $0 (func))
+ (memory $appMemory (shared i64 23 256))
+ (memory $dataMemory (shared i64 23 256))
+ (memory $instrumentMemory (shared i64 23 256))
+ (func $atomic-loadstore
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $dataMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $instrumentMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $dataMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $dataMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (i32.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $atomic-rmw
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.add $dataMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.add $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $instrumentMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+ )
+ (func $atomic-cmpxchg
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.cmpxchg $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.cmpxchg $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $appMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $dataMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $dataMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $atomic-wait-notify
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (memory.atomic.wait32 $dataMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $instrumentMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $appMemory offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $atomic-fence
+  (atomic.fence)
+ )
+)
+
diff --git a/test/multi-memories-atomics64.wast.from-wast b/test/multi-memories-atomics64.wast.from-wast
new file mode 100644
index 0000000..603e26b
--- /dev/null
+++ b/test/multi-memories-atomics64.wast.from-wast
@@ -0,0 +1,351 @@
+(module
+ (type $0 (func))
+ (memory $appMemory (shared i64 23 256))
+ (memory $dataMemory (shared i64 23 256))
+ (memory $instrumentMemory (shared i64 23 256))
+ (func $atomic-loadstore
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $dataMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $instrumentMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $dataMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $dataMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (i32.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $atomic-rmw
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.add $dataMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.add $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $instrumentMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+ )
+ (func $atomic-cmpxchg
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.cmpxchg $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.cmpxchg $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $appMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $dataMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $dataMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $atomic-wait-notify
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (memory.atomic.wait32 $dataMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $instrumentMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $appMemory offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $atomic-fence
+  (atomic.fence)
+ )
+)
diff --git a/test/multi-memories-atomics64.wast.fromBinary b/test/multi-memories-atomics64.wast.fromBinary
new file mode 100644
index 0000000..565a5e6
--- /dev/null
+++ b/test/multi-memories-atomics64.wast.fromBinary
@@ -0,0 +1,352 @@
+(module
+ (type $0 (func))
+ (memory $appMemory (shared i64 23 256))
+ (memory $dataMemory (shared i64 23 256))
+ (memory $instrumentMemory (shared i64 23 256))
+ (func $atomic-loadstore
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load8_u $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $dataMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $instrumentMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $dataMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $appMemory offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $dataMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $appMemory
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $instrumentMemory
+    (local.get $0)
+   )
+  )
+  (i32.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $dataMemory offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $appMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $instrumentMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $dataMemory offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $atomic-rmw
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.add $dataMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.add $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $instrumentMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $appMemory
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+ )
+ (func $atomic-cmpxchg
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.cmpxchg $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.cmpxchg $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $appMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $appMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $dataMemory offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $dataMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $atomic-wait-notify
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (memory.atomic.wait32 $dataMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $instrumentMemory
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $appMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $instrumentMemory offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $appMemory offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $dataMemory offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $instrumentMemory
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $appMemory offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $atomic-fence
+  (atomic.fence)
+ )
+)
+
diff --git a/test/multi-memories-atomics64.wast.fromBinary.noDebugInfo b/test/multi-memories-atomics64.wast.fromBinary.noDebugInfo
new file mode 100644
index 0000000..7128772
--- /dev/null
+++ b/test/multi-memories-atomics64.wast.fromBinary.noDebugInfo
@@ -0,0 +1,352 @@
+(module
+ (type $none_=>_none (func))
+ (memory $0 (shared i64 23 256))
+ (memory $1 (shared i64 23 256))
+ (memory $2 (shared i64 23 256))
+ (func $0
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.load8_u $0 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load8_u $0 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $1 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load16_u $2 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $1 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i32.atomic.load $0 offset=4
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $0
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load8_u $1
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $0
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load16_u $0
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $2
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load32_u $0
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $0
+    (local.get $0)
+   )
+  )
+  (drop
+   (i64.atomic.load $2
+    (local.get $0)
+   )
+  )
+  (i32.atomic.store $0 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store $0 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $2 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store8 $1 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $0 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i32.atomic.store16 $1 offset=4
+   (local.get $0)
+   (local.get $2)
+  )
+  (i64.atomic.store $0 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store $0 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $1 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store8 $2 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $0 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store16 $0 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $2 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+  (i64.atomic.store32 $1 offset=4
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $1
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.add $1 offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.add $2 offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $0 offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.add_u $0 offset=4
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw16.and_u $2
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $0
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.or_u $0
+    (local.get $0)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $0
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.xchg_u $1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+ )
+ (func $2
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (i32.atomic.rmw.cmpxchg $0 offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw.cmpxchg $2 offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $0
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i32.atomic.rmw8.cmpxchg_u $0
+    (local.get $0)
+    (local.get $2)
+    (local.get $2)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $0 offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw.cmpxchg $1 offset=4
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $2
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (i64.atomic.rmw32.cmpxchg_u $1
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $3
+  (local $0 i64)
+  (local $1 i64)
+  (local $2 i32)
+  (drop
+   (memory.atomic.wait32 $1
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $2
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $0 offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait32 $2 offset=4
+    (local.get $0)
+    (local.get $2)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.notify $1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $1
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $0 offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.notify $1 offset=24
+    (local.get $0)
+    (local.get $2)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $2
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $2
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $0 offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+  (drop
+   (memory.atomic.wait64 $0 offset=16
+    (local.get $0)
+    (local.get $1)
+    (local.get $1)
+   )
+  )
+ )
+ (func $4
+  (atomic.fence)
+ )
+)
+
diff --git a/test/multi-memories-basics.wast b/test/multi-memories-basics.wast
new file mode 100644
index 0000000..62b9265
--- /dev/null
+++ b/test/multi-memories-basics.wast
@@ -0,0 +1,117 @@
+(module
+ (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
+ (import "env" "memory" (memory $importedMemory 1 1))
+ (memory $memory1 1 500)
+ (memory $memory2 1 800)
+ (memory $memory3 1 400)
+ (data (i32.const 0) "abcd")
+ (func $memory.fill
+  (memory.fill $memory2
+   (i32.const 0)
+   (i32.const 1)
+   (i32.const 2)
+  )
+ )
+ (func $memory.copy
+  (memory.copy $memory2 $memory3
+   (i32.const 512)
+   (i32.const 0)
+   (i32.const 12)
+  )
+ )
+ (func $memory.init
+  (memory.init $memory1 0
+   (i32.const 0)
+   (i32.const 0)
+   (i32.const 45)
+  )
+ )
+ (func $memory.grow (result i32)
+  (memory.grow $memory3
+   (i32.const 10)
+  )
+ )
+ (func $memory.size (result i32)
+  (memory.size $memory3)
+ )
+ (func $loads
+  (drop
+   (i32.load $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $memory2
+    (i32.const 12)
+   )
+  )
+ )
+ (func $stores
+  (i32.store $memory1
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store $memory1
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store16 $memory2
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store16 $importedMemory
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store8 $memory3
+   (i32.const 23)
+   (i32.const 120)
+  )
+  (i32.store8 $memory3
+   (i32.const 23)
+   (i32.const 120)
+  )
+ )
+)
+
diff --git a/test/multi-memories-basics.wast.from-wast b/test/multi-memories-basics.wast.from-wast
new file mode 100644
index 0000000..7998b62
--- /dev/null
+++ b/test/multi-memories-basics.wast.from-wast
@@ -0,0 +1,116 @@
+(module
+ (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
+ (import "env" "memory" (memory $importedMemory 1 1))
+ (memory $memory1 1 500)
+ (memory $memory2 1 800)
+ (memory $memory3 1 400)
+ (data (i32.const 0) "abcd")
+ (func $memory.fill
+  (memory.fill $memory2
+   (i32.const 0)
+   (i32.const 1)
+   (i32.const 2)
+  )
+ )
+ (func $memory.copy
+  (memory.copy $memory2 $memory3
+   (i32.const 512)
+   (i32.const 0)
+   (i32.const 12)
+  )
+ )
+ (func $memory.init
+  (memory.init $memory1 0
+   (i32.const 0)
+   (i32.const 0)
+   (i32.const 45)
+  )
+ )
+ (func $memory.grow (result i32)
+  (memory.grow $memory3
+   (i32.const 10)
+  )
+ )
+ (func $memory.size (result i32)
+  (memory.size $memory3)
+ )
+ (func $loads
+  (drop
+   (i32.load $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $memory2
+    (i32.const 12)
+   )
+  )
+ )
+ (func $stores
+  (i32.store $memory1
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store $memory1
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store16 $memory2
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store16 $importedMemory
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store8 $memory3
+   (i32.const 23)
+   (i32.const 120)
+  )
+  (i32.store8 $memory3
+   (i32.const 23)
+   (i32.const 120)
+  )
+ )
+)
diff --git a/test/multi-memories-basics.wast.fromBinary b/test/multi-memories-basics.wast.fromBinary
new file mode 100644
index 0000000..62b9265
--- /dev/null
+++ b/test/multi-memories-basics.wast.fromBinary
@@ -0,0 +1,117 @@
+(module
+ (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
+ (import "env" "memory" (memory $importedMemory 1 1))
+ (memory $memory1 1 500)
+ (memory $memory2 1 800)
+ (memory $memory3 1 400)
+ (data (i32.const 0) "abcd")
+ (func $memory.fill
+  (memory.fill $memory2
+   (i32.const 0)
+   (i32.const 1)
+   (i32.const 2)
+  )
+ )
+ (func $memory.copy
+  (memory.copy $memory2 $memory3
+   (i32.const 512)
+   (i32.const 0)
+   (i32.const 12)
+  )
+ )
+ (func $memory.init
+  (memory.init $memory1 0
+   (i32.const 0)
+   (i32.const 0)
+   (i32.const 45)
+  )
+ )
+ (func $memory.grow (result i32)
+  (memory.grow $memory3
+   (i32.const 10)
+  )
+ )
+ (func $memory.size (result i32)
+  (memory.size $memory3)
+ )
+ (func $loads
+  (drop
+   (i32.load $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $memory3
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $memory1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $memory2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $memory2
+    (i32.const 12)
+   )
+  )
+ )
+ (func $stores
+  (i32.store $memory1
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store $memory1
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store16 $memory2
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store16 $importedMemory
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store8 $memory3
+   (i32.const 23)
+   (i32.const 120)
+  )
+  (i32.store8 $memory3
+   (i32.const 23)
+   (i32.const 120)
+  )
+ )
+)
+
diff --git a/test/multi-memories-basics.wast.fromBinary.noDebugInfo b/test/multi-memories-basics.wast.fromBinary.noDebugInfo
new file mode 100644
index 0000000..061c094
--- /dev/null
+++ b/test/multi-memories-basics.wast.fromBinary.noDebugInfo
@@ -0,0 +1,117 @@
+(module
+ (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
+ (import "env" "memory" (memory $mimport$0 1 1))
+ (memory $0 1 500)
+ (memory $1 1 800)
+ (memory $2 1 400)
+ (data (i32.const 0) "abcd")
+ (func $0
+  (memory.fill $1
+   (i32.const 0)
+   (i32.const 1)
+   (i32.const 2)
+  )
+ )
+ (func $1
+  (memory.copy $1 $2
+   (i32.const 512)
+   (i32.const 0)
+   (i32.const 12)
+  )
+ )
+ (func $2
+  (memory.init $0 0
+   (i32.const 0)
+   (i32.const 0)
+   (i32.const 45)
+  )
+ )
+ (func $3 (result i32)
+  (memory.grow $2
+   (i32.const 10)
+  )
+ )
+ (func $4 (result i32)
+  (memory.size $2)
+ )
+ (func $5
+  (drop
+   (i32.load $0
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load $2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_s $1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_s $2
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $0
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load16_u $0
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $1
+    (i32.const 12)
+   )
+  )
+  (drop
+   (i32.load8_u $1
+    (i32.const 12)
+   )
+  )
+ )
+ (func $6
+  (i32.store $0
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store $0
+   (i32.const 12)
+   (i32.const 115)
+  )
+  (i32.store16 $1
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store16 $mimport$0
+   (i32.const 20)
+   (i32.const 31353)
+  )
+  (i32.store8 $2
+   (i32.const 23)
+   (i32.const 120)
+  )
+  (i32.store8 $2
+   (i32.const 23)
+   (i32.const 120)
+  )
+ )
+)
+
diff --git a/test/multi-memories-simd.wast b/test/multi-memories-simd.wast
new file mode 100644
index 0000000..48cde73
--- /dev/null
+++ b/test/multi-memories-simd.wast
@@ -0,0 +1,320 @@
+(module
+ (type $i32_=>_v128 (func (param i32) (result v128)))
+ (type $i32_v128_=>_none (func (param i32 v128)))
+ (type $i32_v128_=>_v128 (func (param i32 v128) (result v128)))
+ (memory $memorya 1 1)
+ (memory $memoryb 1 1)
+ (memory $memoryc 1 1)
+ (memory $memoryd 1 1)
+ (func $v128.load (param $0 i32) (result v128)
+  (v128.load $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load2 (param $0 i32) (result v128)
+  (v128.load $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_s (param $0 i32) (result v128)
+  (v128.load8x8_s $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_s2 (param $0 i32) (result v128)
+  (v128.load8x8_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_u (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_u2 (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_s (param $0 i32) (result v128)
+  (v128.load16x4_s $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_s2 (param $0 i32) (result v128)
+  (v128.load16x4_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_u (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_u2 (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_s (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_s2 (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_u (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_u2 (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load8_splat (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8_splat2 (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load16_splat (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16_splat2 (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_splat (param $0 i32) (result v128)
+  (v128.load32_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_splat2 (param $0 i32) (result v128)
+  (v128.load32_splat $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_splat (param $0 i32) (result v128)
+  (v128.load64_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_splat2 (param $0 i32) (result v128)
+  (v128.load64_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.store (param $0 i32) (param $1 v128)
+  (v128.store $memorya
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store2 (param $0 i32) (param $1 v128)
+  (v128.store $memoryb
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load8_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load8_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load16_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load16_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memorya align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryc offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memorya offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store8_lane (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store8_lane2 (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store16_lane (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store16_lane2 (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store32_lane (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store32_lane2 (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_zero (param $0 i32) (result v128)
+  (v128.load32_zero $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_zero2 (param $0 i32) (result v128)
+  (v128.load32_zero $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_zero (param $0 i32) (result v128)
+  (v128.load64_zero $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_zero2 (param $0 i32) (result v128)
+  (v128.load64_zero $memoryc
+   (local.get $0)
+  )
+ )
+)
+
diff --git a/test/multi-memories-simd.wast.from-wast b/test/multi-memories-simd.wast.from-wast
new file mode 100644
index 0000000..918d7fd
--- /dev/null
+++ b/test/multi-memories-simd.wast.from-wast
@@ -0,0 +1,319 @@
+(module
+ (type $i32_=>_v128 (func (param i32) (result v128)))
+ (type $i32_v128_=>_none (func (param i32 v128)))
+ (type $i32_v128_=>_v128 (func (param i32 v128) (result v128)))
+ (memory $memorya 1 1)
+ (memory $memoryb 1 1)
+ (memory $memoryc 1 1)
+ (memory $memoryd 1 1)
+ (func $v128.load (param $0 i32) (result v128)
+  (v128.load $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load2 (param $0 i32) (result v128)
+  (v128.load $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_s (param $0 i32) (result v128)
+  (v128.load8x8_s $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_s2 (param $0 i32) (result v128)
+  (v128.load8x8_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_u (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_u2 (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_s (param $0 i32) (result v128)
+  (v128.load16x4_s $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_s2 (param $0 i32) (result v128)
+  (v128.load16x4_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_u (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_u2 (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_s (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_s2 (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_u (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_u2 (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load8_splat (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8_splat2 (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load16_splat (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16_splat2 (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_splat (param $0 i32) (result v128)
+  (v128.load32_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_splat2 (param $0 i32) (result v128)
+  (v128.load32_splat $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_splat (param $0 i32) (result v128)
+  (v128.load64_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_splat2 (param $0 i32) (result v128)
+  (v128.load64_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.store (param $0 i32) (param $1 v128)
+  (v128.store $memorya
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store2 (param $0 i32) (param $1 v128)
+  (v128.store $memoryb
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load8_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load8_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load16_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load16_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memorya align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryc offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memorya offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store8_lane (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store8_lane2 (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store16_lane (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store16_lane2 (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store32_lane (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store32_lane2 (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_zero (param $0 i32) (result v128)
+  (v128.load32_zero $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_zero2 (param $0 i32) (result v128)
+  (v128.load32_zero $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_zero (param $0 i32) (result v128)
+  (v128.load64_zero $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_zero2 (param $0 i32) (result v128)
+  (v128.load64_zero $memoryc
+   (local.get $0)
+  )
+ )
+)
diff --git a/test/multi-memories-simd.wast.fromBinary b/test/multi-memories-simd.wast.fromBinary
new file mode 100644
index 0000000..48cde73
--- /dev/null
+++ b/test/multi-memories-simd.wast.fromBinary
@@ -0,0 +1,320 @@
+(module
+ (type $i32_=>_v128 (func (param i32) (result v128)))
+ (type $i32_v128_=>_none (func (param i32 v128)))
+ (type $i32_v128_=>_v128 (func (param i32 v128) (result v128)))
+ (memory $memorya 1 1)
+ (memory $memoryb 1 1)
+ (memory $memoryc 1 1)
+ (memory $memoryd 1 1)
+ (func $v128.load (param $0 i32) (result v128)
+  (v128.load $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load2 (param $0 i32) (result v128)
+  (v128.load $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_s (param $0 i32) (result v128)
+  (v128.load8x8_s $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_s2 (param $0 i32) (result v128)
+  (v128.load8x8_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_u (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load8x8_u2 (param $0 i32) (result v128)
+  (v128.load8x8_u $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_s (param $0 i32) (result v128)
+  (v128.load16x4_s $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_s2 (param $0 i32) (result v128)
+  (v128.load16x4_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_u (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16x4_u2 (param $0 i32) (result v128)
+  (v128.load16x4_u $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_s (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_s2 (param $0 i32) (result v128)
+  (v128.load32x2_s $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_u (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32x2_u2 (param $0 i32) (result v128)
+  (v128.load32x2_u $memoryc
+   (local.get $0)
+  )
+ )
+ (func $v128.load8_splat (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load8_splat2 (param $0 i32) (result v128)
+  (v128.load8_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load16_splat (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load16_splat2 (param $0 i32) (result v128)
+  (v128.load16_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_splat (param $0 i32) (result v128)
+  (v128.load32_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_splat2 (param $0 i32) (result v128)
+  (v128.load32_splat $memoryd
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_splat (param $0 i32) (result v128)
+  (v128.load64_splat $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_splat2 (param $0 i32) (result v128)
+  (v128.load64_splat $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.store (param $0 i32) (param $1 v128)
+  (v128.store $memorya
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store2 (param $0 i32) (param $1 v128)
+  (v128.store $memoryb
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load8_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load8_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load16_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load16_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memorya align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryc offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryb offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align_offset (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memorya offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load64_lane_align_offset2 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $memoryd offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store8_lane (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store8_lane2 (param $0 i32) (param $1 v128)
+  (v128.store8_lane $memoryd 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store16_lane (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memorya 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store16_lane2 (param $0 i32) (param $1 v128)
+  (v128.store16_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store32_lane (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store32_lane2 (param $0 i32) (param $1 v128)
+  (v128.store32_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryc 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memorya offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align_offset (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryb offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.store64_lane_align_offset2 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $memoryd offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $v128.load32_zero (param $0 i32) (result v128)
+  (v128.load32_zero $memorya
+   (local.get $0)
+  )
+ )
+ (func $v128.load32_zero2 (param $0 i32) (result v128)
+  (v128.load32_zero $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_zero (param $0 i32) (result v128)
+  (v128.load64_zero $memoryb
+   (local.get $0)
+  )
+ )
+ (func $v128.load64_zero2 (param $0 i32) (result v128)
+  (v128.load64_zero $memoryc
+   (local.get $0)
+  )
+ )
+)
+
diff --git a/test/multi-memories-simd.wast.fromBinary.noDebugInfo b/test/multi-memories-simd.wast.fromBinary.noDebugInfo
new file mode 100644
index 0000000..583f585
--- /dev/null
+++ b/test/multi-memories-simd.wast.fromBinary.noDebugInfo
@@ -0,0 +1,320 @@
+(module
+ (type $i32_=>_v128 (func (param i32) (result v128)))
+ (type $i32_v128_=>_none (func (param i32 v128)))
+ (type $i32_v128_=>_v128 (func (param i32 v128) (result v128)))
+ (memory $0 1 1)
+ (memory $1 1 1)
+ (memory $2 1 1)
+ (memory $3 1 1)
+ (func $0 (param $0 i32) (result v128)
+  (v128.load $0
+   (local.get $0)
+  )
+ )
+ (func $1 (param $0 i32) (result v128)
+  (v128.load $1
+   (local.get $0)
+  )
+ )
+ (func $2 (param $0 i32) (result v128)
+  (v128.load8x8_s $2
+   (local.get $0)
+  )
+ )
+ (func $3 (param $0 i32) (result v128)
+  (v128.load8x8_s $1
+   (local.get $0)
+  )
+ )
+ (func $4 (param $0 i32) (result v128)
+  (v128.load8x8_u $3
+   (local.get $0)
+  )
+ )
+ (func $5 (param $0 i32) (result v128)
+  (v128.load8x8_u $3
+   (local.get $0)
+  )
+ )
+ (func $6 (param $0 i32) (result v128)
+  (v128.load16x4_s $0
+   (local.get $0)
+  )
+ )
+ (func $7 (param $0 i32) (result v128)
+  (v128.load16x4_s $1
+   (local.get $0)
+  )
+ )
+ (func $8 (param $0 i32) (result v128)
+  (v128.load16x4_u $0
+   (local.get $0)
+  )
+ )
+ (func $9 (param $0 i32) (result v128)
+  (v128.load16x4_u $0
+   (local.get $0)
+  )
+ )
+ (func $10 (param $0 i32) (result v128)
+  (v128.load32x2_s $2
+   (local.get $0)
+  )
+ )
+ (func $11 (param $0 i32) (result v128)
+  (v128.load32x2_s $1
+   (local.get $0)
+  )
+ )
+ (func $12 (param $0 i32) (result v128)
+  (v128.load32x2_u $1
+   (local.get $0)
+  )
+ )
+ (func $13 (param $0 i32) (result v128)
+  (v128.load32x2_u $2
+   (local.get $0)
+  )
+ )
+ (func $14 (param $0 i32) (result v128)
+  (v128.load8_splat $1
+   (local.get $0)
+  )
+ )
+ (func $15 (param $0 i32) (result v128)
+  (v128.load8_splat $1
+   (local.get $0)
+  )
+ )
+ (func $16 (param $0 i32) (result v128)
+  (v128.load16_splat $0
+   (local.get $0)
+  )
+ )
+ (func $17 (param $0 i32) (result v128)
+  (v128.load16_splat $0
+   (local.get $0)
+  )
+ )
+ (func $18 (param $0 i32) (result v128)
+  (v128.load32_splat $1
+   (local.get $0)
+  )
+ )
+ (func $19 (param $0 i32) (result v128)
+  (v128.load32_splat $3
+   (local.get $0)
+  )
+ )
+ (func $20 (param $0 i32) (result v128)
+  (v128.load64_splat $1
+   (local.get $0)
+  )
+ )
+ (func $21 (param $0 i32) (result v128)
+  (v128.load64_splat $0
+   (local.get $0)
+  )
+ )
+ (func $22 (param $0 i32) (param $1 v128)
+  (v128.store $0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $23 (param $0 i32) (param $1 v128)
+  (v128.store $1
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $24 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $0 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $25 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load8_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $26 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $27 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load16_lane $3 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $28 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $0 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $29 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load32_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $30 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $3 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $31 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $32 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $0 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $33 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $1 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $34 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $2 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $35 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $1 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $36 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $0 offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $37 (param $0 i32) (param $1 v128) (result v128)
+  (v128.load64_lane $3 offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $38 (param $0 i32) (param $1 v128)
+  (v128.store8_lane $0 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $39 (param $0 i32) (param $1 v128)
+  (v128.store8_lane $3 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $40 (param $0 i32) (param $1 v128)
+  (v128.store16_lane $0 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $41 (param $0 i32) (param $1 v128)
+  (v128.store16_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $42 (param $0 i32) (param $1 v128)
+  (v128.store32_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $43 (param $0 i32) (param $1 v128)
+  (v128.store32_lane $2 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $44 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $2 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $45 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $46 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $1 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $47 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $0 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $48 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $3 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $49 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $0 offset=32 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $50 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $1 offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $51 (param $0 i32) (param $1 v128)
+  (v128.store64_lane $3 offset=32 align=1 0
+   (local.get $0)
+   (local.get $1)
+  )
+ )
+ (func $52 (param $0 i32) (result v128)
+  (v128.load32_zero $0
+   (local.get $0)
+  )
+ )
+ (func $53 (param $0 i32) (result v128)
+  (v128.load32_zero $1
+   (local.get $0)
+  )
+ )
+ (func $54 (param $0 i32) (result v128)
+  (v128.load64_zero $1
+   (local.get $0)
+  )
+ )
+ (func $55 (param $0 i32) (result v128)
+  (v128.load64_zero $2
+   (local.get $0)
+  )
+ )
+)
+
diff --git a/test/multi-table.wast.from-wast b/test/multi-table.wast.from-wast
index 545bb9c..ece9212 100644
--- a/test/multi-table.wast.from-wast
+++ b/test/multi-table.wast.from-wast
@@ -5,16 +5,16 @@
  (global $g2 i32 (i32.const 0))
  (table $t2 3 3 funcref)
  (table $t3 4 4 funcref)
- (table $textern 0 anyref)
+ (table $textern 0 externref)
  (table $tspecial 5 5 (ref null $none_=>_none))
  (elem $0 (table $t1) (i32.const 0) func $f)
  (elem $1 (table $t2) (i32.const 0) func $f)
  (elem $activeNonZeroOffset (table $t2) (i32.const 1) func $f $g)
- (elem $e3-1 (table $t3) (global.get $g2) funcref (ref.func $f) (ref.null func))
+ (elem $e3-1 (table $t3) (global.get $g2) funcref (ref.func $f) (ref.null nofunc))
  (elem $e3-2 (table $t3) (i32.const 2) func $f $g)
  (elem $passive-1 func $f $g)
- (elem $passive-2 funcref (ref.func $f) (ref.func $g) (ref.null func))
- (elem $passive-3 (ref null $none_=>_none) (ref.func $f) (ref.func $g) (ref.null $none_=>_none) (global.get $g1))
+ (elem $passive-2 funcref (ref.func $f) (ref.func $g) (ref.null nofunc))
+ (elem $passive-3 (ref null $none_=>_none) (ref.func $f) (ref.func $g) (ref.null nofunc) (global.get $g1))
  (elem $empty func)
  (elem $especial (table $tspecial) (i32.const 0) (ref null $none_=>_none) (ref.func $f) (ref.func $h))
  (func $f
diff --git a/test/multi-table.wast.fromBinary b/test/multi-table.wast.fromBinary
index 1057066..b70e78a 100644
--- a/test/multi-table.wast.fromBinary
+++ b/test/multi-table.wast.fromBinary
@@ -5,16 +5,16 @@
  (global $g2 i32 (i32.const 0))
  (table $t2 3 3 funcref)
  (table $t3 4 4 funcref)
- (table $textern 0 anyref)
+ (table $textern 0 externref)
  (table $tspecial 5 5 (ref null $none_=>_none))
  (elem $0 (table $t1) (i32.const 0) func $f)
  (elem $1 (table $t2) (i32.const 0) func $f)
  (elem $activeNonZeroOffset (table $t2) (i32.const 1) func $f $g)
- (elem $e3-1 (table $t3) (global.get $g2) funcref (ref.func $f) (ref.null func))
+ (elem $e3-1 (table $t3) (global.get $g2) funcref (ref.func $f) (ref.null nofunc))
  (elem $e3-2 (table $t3) (i32.const 2) func $f $g)
  (elem $passive-1 func $f $g)
- (elem $passive-2 funcref (ref.func $f) (ref.func $g) (ref.null func))
- (elem $passive-3 (ref null $none_=>_none) (ref.func $f) (ref.func $g) (ref.null $none_=>_none) (global.get $g1))
+ (elem $passive-2 funcref (ref.func $f) (ref.func $g) (ref.null nofunc))
+ (elem $passive-3 (ref null $none_=>_none) (ref.func $f) (ref.func $g) (ref.null nofunc) (global.get $g1))
  (elem $empty func)
  (elem $especial (table $tspecial) (i32.const 0) (ref null $none_=>_none) (ref.func $f) (ref.func $h))
  (func $f
diff --git a/test/multi-table.wast.fromBinary.noDebugInfo b/test/multi-table.wast.fromBinary.noDebugInfo
index 174d2a9..cf93805 100644
--- a/test/multi-table.wast.fromBinary.noDebugInfo
+++ b/test/multi-table.wast.fromBinary.noDebugInfo
@@ -5,16 +5,16 @@
  (global $global$1 i32 (i32.const 0))
  (table $0 3 3 funcref)
  (table $1 4 4 funcref)
- (table $2 0 anyref)
+ (table $2 0 externref)
  (table $3 5 5 (ref null $none_=>_none))
  (elem $0 (table $timport$0) (i32.const 0) func $0)
  (elem $1 (table $0) (i32.const 0) func $0)
  (elem $2 (table $0) (i32.const 1) func $0 $1)
- (elem $3 (table $1) (global.get $global$1) funcref (ref.func $0) (ref.null func))
+ (elem $3 (table $1) (global.get $global$1) funcref (ref.func $0) (ref.null nofunc))
  (elem $4 (table $1) (i32.const 2) func $0 $1)
  (elem $5 func $0 $1)
- (elem $6 funcref (ref.func $0) (ref.func $1) (ref.null func))
- (elem $7 (ref null $none_=>_none) (ref.func $0) (ref.func $1) (ref.null $none_=>_none) (global.get $global$0))
+ (elem $6 funcref (ref.func $0) (ref.func $1) (ref.null nofunc))
+ (elem $7 (ref null $none_=>_none) (ref.func $0) (ref.func $1) (ref.null nofunc) (global.get $global$0))
  (elem $8 func)
  (elem $9 (table $3) (i32.const 0) (ref null $none_=>_none) (ref.func $0) (ref.func $2))
  (func $0
diff --git a/test/multivalue.wast b/test/multivalue.wast
index 8b0c566..347278b 100644
--- a/test/multivalue.wast
+++ b/test/multivalue.wast
@@ -79,7 +79,7 @@
  )
  (func $drop-block
   (drop
-   (block (result i32 i64)
+   (block $block (result i32 i64)
     (tuple.make
      (i32.const 42)
      (i64.const 42)
@@ -164,4 +164,4 @@
    )
   )
  )
-)
\ No newline at end of file
+)
diff --git a/test/multivalue.wast.from-wast b/test/multivalue.wast.from-wast
index c6aa020..a09eb88 100644
--- a/test/multivalue.wast.from-wast
+++ b/test/multivalue.wast.from-wast
@@ -3,7 +3,7 @@
  (type $none_=>_none (func))
  (type $none_=>_i64 (func (result i64)))
  (type $none_=>_f32_i64_i32 (func (result f32 i64 i32)))
- (type $none_=>_i32_i64_anyref (func (result i32 i64 anyref)))
+ (type $none_=>_i32_i64_externref (func (result i32 i64 externref)))
  (type $none_=>_i32_i64_f32 (func (result i32 i64 f32)))
  (type $none_=>_i32 (func (result i32)))
  (type $none_=>_f32 (func (result f32)))
@@ -105,12 +105,10 @@
   )
  )
  (func $mv-return-in-block (result i32 i64)
-  (block $block (result i32 i64)
-   (return
-    (tuple.make
-     (i32.const 42)
-     (i64.const 42)
-    )
+  (return
+   (tuple.make
+    (i32.const 42)
+    (i64.const 42)
    )
   )
  )
@@ -135,18 +133,18 @@
    )
   )
  )
- (func $mv-if (result i32 i64 anyref)
-  (if (result i32 i64 anyref)
+ (func $mv-if (result i32 i64 externref)
+  (if (result i32 i64 externref)
    (i32.const 1)
    (tuple.make
     (i32.const 42)
     (i64.const 42)
-    (ref.null any)
+    (ref.null noextern)
    )
    (tuple.make
     (i32.const 42)
     (i64.const 42)
-    (ref.null any)
+    (ref.null noextern)
    )
   )
  )
diff --git a/test/multivalue.wast.fromBinary b/test/multivalue.wast.fromBinary
index cc900e0..28e24a2 100644
--- a/test/multivalue.wast.fromBinary
+++ b/test/multivalue.wast.fromBinary
@@ -1,7 +1,7 @@
 (module
  (type $none_=>_i32_i64 (func (result i32 i64)))
  (type $none_=>_none (func))
- (type $none_=>_i32_i64_anyref (func (result i32 i64 anyref)))
+ (type $none_=>_i32_i64_externref (func (result i32 i64 externref)))
  (type $none_=>_i64 (func (result i64)))
  (type $none_=>_f32_i64_i32 (func (result f32 i64 i32)))
  (type $none_=>_i32_i64_f32 (func (result i32 i64 f32)))
@@ -389,20 +389,20 @@
    )
   )
  )
- (func $mv-if (result i32 i64 anyref)
-  (local $0 (i32 i64 anyref))
+ (func $mv-if (result i32 i64 externref)
+  (local $0 (i32 i64 externref))
   (local.set $0
-   (if (result i32 i64 anyref)
+   (if (result i32 i64 externref)
     (i32.const 1)
     (tuple.make
      (i32.const 42)
      (i64.const 42)
-     (ref.null any)
+     (ref.null noextern)
     )
     (tuple.make
      (i32.const 42)
      (i64.const 42)
-     (ref.null any)
+     (ref.null noextern)
     )
    )
   )
diff --git a/test/multivalue.wast.fromBinary.noDebugInfo b/test/multivalue.wast.fromBinary.noDebugInfo
index daf0a74..1929027 100644
--- a/test/multivalue.wast.fromBinary.noDebugInfo
+++ b/test/multivalue.wast.fromBinary.noDebugInfo
@@ -1,7 +1,7 @@
 (module
  (type $none_=>_i32_i64 (func (result i32 i64)))
  (type $none_=>_none (func))
- (type $none_=>_i32_i64_anyref (func (result i32 i64 anyref)))
+ (type $none_=>_i32_i64_externref (func (result i32 i64 externref)))
  (type $none_=>_i64 (func (result i64)))
  (type $none_=>_f32_i64_i32 (func (result f32 i64 i32)))
  (type $none_=>_i32_i64_f32 (func (result i32 i64 f32)))
@@ -389,20 +389,20 @@
    )
   )
  )
- (func $14 (result i32 i64 anyref)
-  (local $0 (i32 i64 anyref))
+ (func $14 (result i32 i64 externref)
+  (local $0 (i32 i64 externref))
   (local.set $0
-   (if (result i32 i64 anyref)
+   (if (result i32 i64 externref)
     (i32.const 1)
     (tuple.make
      (i32.const 42)
      (i64.const 42)
-     (ref.null any)
+     (ref.null noextern)
     )
     (tuple.make
      (i32.const 42)
      (i64.const 42)
-     (ref.null any)
+     (ref.null noextern)
     )
    )
   )
diff --git a/test/passes/O3_low-memory-unused_metrics.txt b/test/passes/O3_low-memory-unused_metrics.txt
index 617b03a..3bdce29 100644
--- a/test/passes/O3_low-memory-unused_metrics.txt
+++ b/test/passes/O3_low-memory-unused_metrics.txt
@@ -3,6 +3,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 10      
+ [memories]     : 0       
  [memory-data]  : 0       
  [table-data]   : 0       
  [tables]       : 0       
diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt
index a7c327d..a38193d 100644
--- a/test/passes/Oz_fuzz-exec_all-features.txt
+++ b/test/passes/Oz_fuzz-exec_all-features.txt
@@ -9,18 +9,8 @@
 [LoggingExternalInterface logging 128]
 [LoggingExternalInterface logging -128]
 [LoggingExternalInterface logging 42]
-[fuzz-exec] calling rtts
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
 [fuzz-exec] calling br_on_cast
 [LoggingExternalInterface logging 3]
-[trap unreachable]
 [fuzz-exec] calling br_on_failed_cast-1
 [LoggingExternalInterface logging 1]
 [fuzz-exec] calling br_on_failed_cast-2
@@ -44,12 +34,10 @@
 [fuzz-exec] calling ref-as-func-of-data
 [trap not a func]
 [fuzz-exec] calling ref-as-func-of-func
-[fuzz-exec] calling rtt-and-cast-on-func
+[fuzz-exec] calling cast-on-func
 [LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 2]
 [LoggingExternalInterface logging 1337]
-[LoggingExternalInterface logging 3]
+[LoggingExternalInterface logging 1]
 [trap cast error]
 [fuzz-exec] calling array-alloc-failure
 [host limit allocation failure]
@@ -63,10 +51,6 @@
 [LoggingExternalInterface logging 99]
 [LoggingExternalInterface logging 0]
 [LoggingExternalInterface logging 10]
-[fuzz-exec] calling rtt_Fresh
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
 [fuzz-exec] calling array.init
 [LoggingExternalInterface logging 2]
 [LoggingExternalInterface logging 42]
@@ -79,49 +63,46 @@
 [LoggingExternalInterface logging 0]
 [LoggingExternalInterface logging 1]
 [LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 0]
+[LoggingExternalInterface logging 1]
 [fuzz-exec] calling static-br_on_cast
 [LoggingExternalInterface logging 3]
 [fuzz-exec] calling static-br_on_cast_fail
 [LoggingExternalInterface logging -2]
 (module
- (type $void_func (func))
  (type $bytes (array (mut i8)))
+ (type $void_func (func))
  (type $struct (struct (field (mut i32))))
- (type $extendedstruct (struct (field (mut i32)) (field f64)))
  (type $i32_=>_none (func (param i32)))
+ (type $extendedstruct (struct (field (mut i32)) (field f64)))
  (type $anyref_=>_none (func (param anyref)))
  (type $int_func (func (result i32)))
  (import "fuzzing-support" "log-i32" (func $log (param i32)))
- (global $rtt (mut (rtt $extendedstruct)) (rtt.canon $extendedstruct))
  (export "structs" (func $0))
  (export "arrays" (func $1))
- (export "rtts" (func $2))
- (export "br_on_cast" (func $3))
- (export "br_on_failed_cast-1" (func $4))
- (export "br_on_failed_cast-2" (func $5))
- (export "cast-null-anyref-to-gc" (func $6))
- (export "br_on_data" (func $8))
- (export "br_on_non_data-null" (func $9))
- (export "br_on_non_data-data" (func $10))
- (export "br_on_non_data-other" (func $9))
- (export "br-on_non_null" (func $9))
- (export "br-on_non_null-2" (func $13))
- (export "ref-as-data-of-func" (func $14))
- (export "ref-as-data-of-data" (func $9))
- (export "ref-as-func-of-data" (func $14))
- (export "ref-as-func-of-func" (func $9))
- (export "rtt-and-cast-on-func" (func $19))
- (export "array-alloc-failure" (func $9))
- (export "init-array-packed" (func $21))
- (export "cast-func-to-struct" (func $14))
- (export "array-copy" (func $24))
- (export "rtt_Fresh" (func $25))
- (export "array.init" (func $26))
- (export "array.init-packed" (func $27))
- (export "static-casts" (func $28))
- (export "static-br_on_cast" (func $29))
- (export "static-br_on_cast_fail" (func $30))
+ (export "br_on_cast" (func $2))
+ (export "br_on_failed_cast-1" (func $3))
+ (export "br_on_failed_cast-2" (func $4))
+ (export "cast-null-anyref-to-gc" (func $5))
+ (export "br_on_data" (func $7))
+ (export "br_on_non_data-null" (func $8))
+ (export "br_on_non_data-data" (func $9))
+ (export "br_on_non_data-other" (func $8))
+ (export "br-on_non_null" (func $8))
+ (export "br-on_non_null-2" (func $12))
+ (export "ref-as-data-of-func" (func $13))
+ (export "ref-as-data-of-data" (func $8))
+ (export "ref-as-func-of-data" (func $13))
+ (export "ref-as-func-of-func" (func $8))
+ (export "cast-on-func" (func $18))
+ (export "array-alloc-failure" (func $8))
+ (export "init-array-packed" (func $20))
+ (export "cast-func-to-struct" (func $13))
+ (export "array-copy" (func $23))
+ (export "array.init" (func $24))
+ (export "array.init-packed" (func $25))
+ (export "static-casts" (func $26))
+ (export "static-br_on_cast" (func $2))
+ (export "static-br_on_cast_fail" (func $28))
  (func $0 (; has Stack IR ;)
   (local $0 i32)
   (call $log
@@ -140,14 +121,13 @@
   )
  )
  (func $1 (; has Stack IR ;)
-  (local $0 (ref null $bytes))
+  (local $0 (ref $bytes))
   (call $log
-   (array.len $bytes
+   (array.len
     (local.tee $0
-     (array.new_with_rtt $bytes
+     (array.new $bytes
       (i32.const 42)
       (i32.const 50)
-      (rtt.canon $bytes)
      )
     )
    )
@@ -183,63 +163,33 @@
   )
  )
  (func $2 (; has Stack IR ;)
-  (call $log
-   (i32.const 1)
-  )
-  (call $log
-   (i32.const 0)
-  )
-  (call $log
-   (i32.const 0)
-  )
-  (call $log
-   (i32.const 1)
-  )
-  (call $log
-   (i32.const 0)
-  )
-  (call $log
-   (i32.const 1)
-  )
-  (call $log
-   (i32.const 0)
-  )
-  (call $log
-   (i32.const 1)
-  )
- )
- (func $3 (; has Stack IR ;)
   (call $log
    (i32.const 3)
   )
-  (unreachable)
  )
- (func $4 (; has Stack IR ;)
-  (local $0 (ref null $struct))
+ (func $3 (; has Stack IR ;)
+  (local $0 (ref $struct))
   (local.set $0
-   (struct.new_default_with_rtt $struct
-    (rtt.canon $struct)
-   )
+   (struct.new_default $struct)
   )
   (drop
-   (block $any (result anyref)
+   (block $any (result (ref null $struct))
     (call $log
      (i32.const 1)
     )
     (drop
-     (br_on_cast_fail $any
+     (br_on_cast_static_fail $any $extendedstruct
       (local.get $0)
-      (rtt.canon $extendedstruct)
      )
     )
     (call $log
      (i32.const 999)
     )
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
- (func $5 (; has Stack IR ;)
+ (func $4 (; has Stack IR ;)
   (call $log
    (i32.const 1)
   )
@@ -247,14 +197,14 @@
    (i32.const 999)
   )
  )
- (func $6 (; has Stack IR ;)
+ (func $5 (; has Stack IR ;)
   (call $log
    (i32.const 0)
   )
  )
- (func $8 (; has Stack IR ;) (param $0 anyref)
+ (func $7 (; has Stack IR ;) (param $0 anyref)
   (drop
-   (block $data (result dataref)
+   (block $data (result (ref data))
     (drop
      (br_on_data $data
       (local.get $0)
@@ -263,21 +213,19 @@
     (call $log
      (i32.const 1)
     )
-    (struct.new_default_with_rtt $struct
-     (rtt.canon $struct)
-    )
+    (struct.new_default $struct)
    )
   )
  )
- (func $9 (; has Stack IR ;)
+ (func $8 (; has Stack IR ;)
   (nop)
  )
- (func $10 (; has Stack IR ;)
+ (func $9 (; has Stack IR ;)
   (call $log
    (i32.const 1)
   )
  )
- (func $13 (; has Stack IR ;)
+ (func $12 (; has Stack IR ;)
   (drop
    (block
     (call $log
@@ -287,47 +235,39 @@
    )
   )
  )
- (func $14 (; has Stack IR ;)
+ (func $13 (; has Stack IR ;)
   (drop
    (unreachable)
   )
  )
- (func $19 (; has Stack IR ;)
+ (func $18 (; has Stack IR ;)
   (call $log
    (i32.const 0)
   )
-  (call $log
-   (i32.const 1)
-  )
-  (call $log
-   (i32.const 2)
-  )
   (call $log
    (i32.const 1337)
   )
   (call $log
-   (i32.const 3)
+   (i32.const 1)
   )
   (unreachable)
  )
- (func $21 (; has Stack IR ;) (result i32)
+ (func $20 (; has Stack IR ;) (result i32)
   (array.get_u $bytes
-   (array.new_with_rtt $bytes
+   (array.new $bytes
     (i32.const -43)
     (i32.const 50)
-    (rtt.canon $bytes)
    )
    (i32.const 10)
   )
  )
- (func $24 (; has Stack IR ;)
-  (local $0 (ref null $bytes))
-  (local $1 (ref null $bytes))
+ (func $23 (; has Stack IR ;)
+  (local $0 (ref $bytes))
+  (local $1 (ref $bytes))
   (array.set $bytes
    (local.tee $1
-    (array.new_default_with_rtt $bytes
+    (array.new_default $bytes
      (i32.const 200)
-     (rtt.canon $bytes)
     )
    )
    (i32.const 42)
@@ -336,10 +276,9 @@
   (call $log
    (array.get_u $bytes
     (local.tee $0
-     (array.new_with_rtt $bytes
+     (array.new $bytes
       (i32.const 10)
       (i32.const 100)
-      (rtt.canon $bytes)
      )
     )
     (i32.const 10)
@@ -377,36 +316,14 @@
    )
   )
  )
- (func $25 (; has Stack IR ;)
-  (call $log
-   (i32.const 1)
-  )
-  (call $log
-   (i32.const 0)
-  )
-  (global.set $rtt
-   (rtt.fresh_sub $extendedstruct
-    (rtt.canon $struct)
-   )
-  )
-  (call $log
-   (ref.test
-    (struct.new_default_with_rtt $extendedstruct
-     (global.get $rtt)
-    )
-    (global.get $rtt)
-   )
-  )
- )
- (func $26 (; has Stack IR ;)
-  (local $0 (ref null $bytes))
+ (func $24 (; has Stack IR ;)
+  (local $0 (ref $bytes))
   (call $log
-   (array.len $bytes
+   (array.len
     (local.tee $0
-     (array.init $bytes
+     (array.init_static $bytes
       (i32.const 42)
       (i32.const 50)
-      (rtt.canon $bytes)
      )
     )
    )
@@ -424,18 +341,17 @@
    )
   )
  )
- (func $27 (; has Stack IR ;)
+ (func $25 (; has Stack IR ;)
   (call $log
    (array.get_u $bytes
-    (array.init $bytes
+    (array.init_static $bytes
      (i32.const -11512)
-     (rtt.canon $bytes)
     )
     (i32.const 0)
    )
   )
  )
- (func $28 (; has Stack IR ;)
+ (func $26 (; has Stack IR ;)
   (call $log
    (i32.const 1)
   )
@@ -455,12 +371,7 @@
    (i32.const 1)
   )
  )
- (func $29 (; has Stack IR ;)
-  (call $log
-   (i32.const 3)
-  )
- )
- (func $30 (; has Stack IR ;)
+ (func $28 (; has Stack IR ;)
   (call $log
    (i32.const -2)
   )
@@ -477,18 +388,8 @@
 [LoggingExternalInterface logging 128]
 [LoggingExternalInterface logging -128]
 [LoggingExternalInterface logging 42]
-[fuzz-exec] calling rtts
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
 [fuzz-exec] calling br_on_cast
 [LoggingExternalInterface logging 3]
-[trap unreachable]
 [fuzz-exec] calling br_on_failed_cast-1
 [LoggingExternalInterface logging 1]
 [fuzz-exec] calling br_on_failed_cast-2
@@ -512,12 +413,10 @@
 [fuzz-exec] calling ref-as-func-of-data
 [trap unreachable]
 [fuzz-exec] calling ref-as-func-of-func
-[fuzz-exec] calling rtt-and-cast-on-func
+[fuzz-exec] calling cast-on-func
 [LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 2]
 [LoggingExternalInterface logging 1337]
-[LoggingExternalInterface logging 3]
+[LoggingExternalInterface logging 1]
 [trap unreachable]
 [fuzz-exec] calling array-alloc-failure
 [fuzz-exec] calling init-array-packed
@@ -530,10 +429,6 @@
 [LoggingExternalInterface logging 99]
 [LoggingExternalInterface logging 0]
 [LoggingExternalInterface logging 10]
-[fuzz-exec] calling rtt_Fresh
-[LoggingExternalInterface logging 1]
-[LoggingExternalInterface logging 0]
-[LoggingExternalInterface logging 1]
 [fuzz-exec] calling array.init
 [LoggingExternalInterface logging 2]
 [LoggingExternalInterface logging 42]
diff --git a/test/passes/Oz_fuzz-exec_all-features.wast b/test/passes/Oz_fuzz-exec_all-features.wast
index 7ab9116..94bc2aa 100644
--- a/test/passes/Oz_fuzz-exec_all-features.wast
+++ b/test/passes/Oz_fuzz-exec_all-features.wast
@@ -8,15 +8,11 @@
 
  (import "fuzzing-support" "log-i32" (func $log (param i32)))
 
- (global $rtt (mut (rtt $extendedstruct)) (rtt.canon $extendedstruct))
-
  (func "structs"
   (local $x (ref null $struct))
   (local $y (ref null $struct))
   (local.set $x
-   (struct.new_default_with_rtt $struct
-    (rtt.canon $struct)
-   )
+   (struct.new_default $struct)
   )
   ;; The value is initialized to 0
   ;; Note: We cannot optimize these to constants without either immutability or
@@ -49,10 +45,9 @@
  (func "arrays"
   (local $x (ref null $bytes))
   (local.set $x
-   (array.new_with_rtt $bytes
+   (array.new $bytes
     (i32.const 42) ;; value to splat into the array
     (i32.const 50) ;; size
-    (rtt.canon $bytes)
    )
   )
   ;; The length should be 50
@@ -77,78 +72,11 @@
    (array.get_s $bytes (local.get $x) (i32.const 20))
   )
  )
- (func "rtts"
-  (local $any anyref)
-  ;; Casting null returns null.
-  (call $log (ref.is_null
-   (ref.cast (ref.null $struct) (rtt.canon $struct))
-  ))
-  ;; Testing null returns 0.
-  (call $log
-   (ref.test (ref.null $struct) (rtt.canon $struct))
-  )
-  ;; Testing something completely wrong (struct vs array) returns 0.
-  (call $log
-   (ref.test
-    (array.new_with_rtt $bytes
-     (i32.const 20)
-     (i32.const 10)
-     (rtt.canon $bytes)
-    )
-    (rtt.canon $struct)
-   )
-  )
-  ;; Testing a thing with the same RTT returns 1.
-  (call $log
-   (ref.test
-    (struct.new_default_with_rtt $struct
-     (rtt.canon $struct)
-    )
-    (rtt.canon $struct)
-   )
-  )
-  ;; A bad downcast returns 0: we create a struct, which is not a extendedstruct.
-  (call $log
-   (ref.test
-    (struct.new_default_with_rtt $struct
-     (rtt.canon $struct)
-    )
-    (rtt.canon $extendedstruct)
-   )
-  )
-  ;; Create a extendedstruct with RTT y, and upcast statically to anyref.
-  (local.set $any
-   (struct.new_default_with_rtt $extendedstruct
-    (rtt.sub $extendedstruct (rtt.canon $struct))
-   )
-  )
-  ;; Casting to y, the exact same RTT, works.
-  (call $log
-   (ref.test
-    (local.get $any)
-    (rtt.sub $extendedstruct (rtt.canon $struct))
-   )
-  )
-  ;; Casting to z, another RTT of the same data type, fails.
-  (call $log
-   (ref.test
-    (local.get $any)
-    (rtt.canon $extendedstruct)
-   )
-  )
-  ;; Casting to x, the parent of y, works.
-  (call $log
-   (ref.test
-    (local.get $any)
-    (rtt.canon $struct)
-   )
-  )
- )
  (func "br_on_cast"
   (local $any anyref)
   ;; create a simple $struct, store it in an anyref
   (local.set $any
-   (struct.new_default_with_rtt $struct (rtt.canon $struct))
+   (struct.new_default $struct)
   )
   (drop
    (block $block (result ($ref $struct))
@@ -156,12 +84,11 @@
      (block $extendedblock (result (ref $extendedstruct))
       (drop
        ;; second, try to cast our simple $struct to what it is, which will work
-       (br_on_cast $block
+       (br_on_cast_static $block $struct
         ;; first, try to cast our simple $struct to an extended, which will fail
-        (br_on_cast $extendedblock
-         (local.get $any) (rtt.canon $extendedstruct)
+        (br_on_cast_static $extendedblock $extendedstruct
+         (local.get $any)
         )
-        (rtt.canon $struct)
        )
       )
       (call $log (i32.const -1)) ;; we should never get here
@@ -173,20 +100,12 @@
    )
   )
   (call $log (i32.const 3)) ;; we should get here
-  (drop
-   (block $never (result (ref $extendedstruct))
-    ;; an untaken br_on_cast, with unreachable rtt - so we cannot use the
-    ;; RTT in binaryen IR to find the cast type.
-    (br_on_cast $never (ref.null $struct) (unreachable))
-    (unreachable)
-   )
-  )
  )
  (func "br_on_failed_cast-1"
   (local $any anyref)
   ;; create a simple $struct, store it in an anyref
   (local.set $any
-   (struct.new_default_with_rtt $struct (rtt.canon $struct))
+   (struct.new_default $struct)
   )
   (drop
    (block $any (result (ref null any))
@@ -194,9 +113,8 @@
     (drop
      ;; try to cast our simple $struct to an extended, which will fail, and
      ;; so we will branch, skipping the next logging.
-     (br_on_cast_fail $any
+     (br_on_cast_static_fail $any $extendedstruct
       (local.get $any)
-      (rtt.canon $extendedstruct)
      )
     )
     (call $log (i32.const 999)) ;; we should skip this
@@ -208,7 +126,7 @@
   (local $any anyref)
   ;; create an $extendedstruct, store it in an anyref
   (local.set $any
-   (struct.new_default_with_rtt $extendedstruct (rtt.canon $extendedstruct))
+   (struct.new_default $extendedstruct)
   )
   (drop
    (block $any (result (ref null any))
@@ -216,9 +134,8 @@
     (drop
      ;; try to cast our simple $struct to an extended, which will succeed, and
      ;; so we will continue to the next logging.
-     (br_on_cast_fail $any
+     (br_on_cast_static_fail $any $extendedstruct
       (local.get $any)
-      (rtt.canon $extendedstruct)
      )
     )
     (call $log (i32.const 999))
@@ -231,16 +148,13 @@
   ;; array or a struct, so our casting code should not assume it is. it is ok
   ;; to try to cast it, and the result should be 0.
   (call $log
-   (ref.test
+   (ref.test_static $struct
     (ref.null any)
-    (rtt.canon $struct)
    )
   )
  )
  (func $get_data (result dataref)
-  (struct.new_default_with_rtt $struct
-   (rtt.canon $struct)
-  )
+  (struct.new_default $struct)
  )
  (func "br_on_data" (param $x anyref)
   (local $y anyref)
@@ -272,9 +186,7 @@
   (local $x anyref)
   ;; set x to valid data
   (local.set $x
-   (struct.new_default_with_rtt $struct
-    (rtt.canon $struct)
-   )
+   (struct.new_default $struct)
   )
   (drop
    (block $any (result anyref)
@@ -291,14 +203,16 @@
   (local $x anyref)
   ;; set x to something that is not null, but also not data
   (local.set $x
-   (ref.func $a-void-func)
+   (i31.new
+    (i32.const 0)
+   )
   )
   (drop
    (block $any (result anyref)
     (drop
      (br_on_non_data $any (local.get $x))
     )
-    ;; $x refers to a function, so we will branch, and not log
+    ;; $x refers to an i31, so we will branch, and not log
     (call $log (i32.const 1))
     (ref.null any)
    )
@@ -307,8 +221,8 @@
  (func "br-on_non_null"
   (drop
    (block $non-null (result (ref any))
-    (br_on_non_null $non-null (ref.func $a-void-func))
-    ;; $x refers to a function, which is not null, so we will branch, and not
+    (br_on_non_null $non-null (i31.new (i32.const 0)))
+    ;; $x refers to an i31, which is not null, so we will branch, and not
     ;; log
     (call $log (i32.const 1))
     (unreachable)
@@ -336,9 +250,7 @@
  (func "ref-as-data-of-data"
   (drop
    (ref.as_data
-    (struct.new_default_with_rtt $struct
-     (rtt.canon $struct)
-    )
+    (struct.new_default $struct)
    )
   )
  )
@@ -346,9 +258,7 @@
   (drop
    ;; This should trap.
    (ref.as_func
-    (struct.new_default_with_rtt $struct
-     (rtt.canon $struct)
-    )
+    (struct.new_default $struct)
    )
   )
  )
@@ -362,43 +272,33 @@
  (func $a-void-func
   (call $log (i32.const 1337))
  )
- (func "rtt-and-cast-on-func"
+ (func "cast-on-func"
   (call $log (i32.const 0))
-  (drop
-   (rtt.canon $void_func)
-  )
-  (call $log (i32.const 1))
-  (drop
-   (rtt.canon $int_func)
-  )
-  (call $log (i32.const 2))
   ;; a valid cast
-  (call_ref
-   (ref.cast (ref.func $a-void-func) (rtt.canon $void_func))
+  (call_ref $void_func
+   (ref.cast_static $void_func (ref.func $a-void-func))
   )
-  (call $log (i32.const 3))
+  (call $log (i32.const 1))
   ;; an invalid cast
-  (drop (call_ref
-   (ref.cast (ref.func $a-void-func) (rtt.canon $int_func))
+  (drop (call_ref $int_func
+   (ref.cast_static $int_func (ref.func $a-void-func))
   ))
   ;; will never be reached
-  (call $log (i32.const 4))
+  (call $log (i32.const 2))
  )
  (func "array-alloc-failure"
   (drop
-   (array.new_default_with_rtt $bytes
+   (array.new_default $bytes
     (i32.const -1) ;; un-allocatable size (4GB * sizeof(Literal))
-    (rtt.canon $bytes)
    )
   )
  )
  (func "init-array-packed" (result i32)
   (local $x (ref null $bytes))
   (local.set $x
-   (array.new_with_rtt $bytes
+   (array.new $bytes
     (i32.const -43) ;; initialize the i8 values with a negative i32
     (i32.const 50)
-    (rtt.canon $bytes)
    )
   )
   ;; read the value, which should be -43 & 255 ==> 213
@@ -413,9 +313,8 @@
  (func "cast-func-to-struct"
   (drop
    ;; An impossible cast of a function to a struct, which should fail.
-   (ref.cast
+   (ref.cast_static $struct
     (ref.func $call-target)
-    (rtt.canon $struct)
    )
   )
  )
@@ -424,17 +323,15 @@
   (local $y (ref null $bytes))
   ;; Create an array of 10's, of size 100.
   (local.set $x
-   (array.new_with_rtt $bytes
+   (array.new $bytes
     (i32.const 10)
     (i32.const 100)
-    (rtt.canon $bytes)
    )
   )
   ;; Create an array of zeros of size 200, and also set one index there.
   (local.set $y
-   (array.new_default_with_rtt $bytes
+   (array.new_default $bytes
     (i32.const 200)
-    (rtt.canon $bytes)
    )
   )
   (array.set $bytes
@@ -467,55 +364,12 @@
    (array.get_u $bytes (local.get $x) (i32.const 12))
   )
  )
- (func "rtt_Fresh"
-  ;; Casting to the same sequence of rtt.subs works.
-  (call $log
-   (ref.test
-    (struct.new_default_with_rtt $extendedstruct
-     (rtt.sub $extendedstruct
-      (rtt.canon $struct)
-     )
-    )
-    (rtt.sub $extendedstruct
-     (rtt.canon $struct)
-    )
-   )
-  )
-  ;; But not with fresh!
-  (call $log
-   (ref.test
-    (struct.new_default_with_rtt $extendedstruct
-     (rtt.sub $extendedstruct
-      (rtt.canon $struct)
-     )
-    )
-    (rtt.fresh_sub $extendedstruct
-     (rtt.canon $struct)
-    )
-   )
-  )
-  ;; Casts with fresh succeed, if we use the same fresh rtt.
-  (global.set $rtt
-   (rtt.fresh_sub $extendedstruct
-    (rtt.canon $struct)
-   )
-  )
-  (call $log
-   (ref.test
-    (struct.new_default_with_rtt $extendedstruct
-     (global.get $rtt)
-    )
-    (global.get $rtt)
-   )
-  )
- )
  (func "array.init"
   (local $x (ref null $bytes))
   (local.set $x
-   (array.init $bytes
+   (array.init_static $bytes
     (i32.const 42) ;; first value
     (i32.const 50) ;; second value
-    (rtt.canon $bytes)
    )
   )
   ;; The length should be 2
@@ -534,9 +388,8 @@
  (func "array.init-packed"
   (local $x (ref null $bytes))
   (local.set $x
-   (array.init $bytes
+   (array.init_static $bytes
     (i32.const -11512)
-    (rtt.canon $bytes)
    )
   )
   ;; The value should be be -11512 & 255 => 8
@@ -574,9 +427,7 @@
     (struct.new_default $struct)
    )
   )
-  ;; Casting to a supertype does not work because the canonical RTT for the
-  ;; subtype is not a sub-rtt of the canonical RTT of the supertype in
-  ;; structural mode.
+  ;; Casting to a supertype works.
   (call $log
    (ref.test_static $struct
     (struct.new_default $extendedstruct)
@@ -641,9 +492,8 @@
   ;; opts the unused value is removed so there is no trap, and a value is
   ;; returned, which should not confuse the fuzzer.
   (drop
-   (array.new_default_with_rtt $[mut:i8]
+   (array.new_default $[mut:i8]
     (i32.const -1)
-    (rtt.canon $[mut:i8])
    )
   )
   (i32.const 0)
diff --git a/test/passes/converge_O3_metrics.bin.txt b/test/passes/converge_O3_metrics.bin.txt
index 8b17e64..a5a0fc3 100644
--- a/test/passes/converge_O3_metrics.bin.txt
+++ b/test/passes/converge_O3_metrics.bin.txt
@@ -3,6 +3,7 @@ total
  [funcs]        : 6       
  [globals]      : 1       
  [imports]      : 3       
+ [memories]     : 0       
  [memory-data]  : 28      
  [table-data]   : 429     
  [tables]       : 0       
@@ -31,17 +32,17 @@ total
  (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $none_=>_i32 (func (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
+ (import "env" "table" (table $timport$0 478 478 funcref))
+ (import "env" "___syscall146" (func $import$0 (param i32 i32) (result i32)))
+ (global $global$0 (mut i32) (i32.const 1))
  (data (i32.const 2948) "\03")
  (data (i32.const 6828) "\04")
- (data (i32.const 7028) "\0d\00\00\00\06")
+ (data (i32.const 7028) "\r\00\00\00\06")
  (data (i32.const 10888) "hello, world!")
  (data (i32.const 18100) "\b8\1a")
  (data (i32.const 18128) ",I")
  (data (i32.const 18732) "D\1b")
  (data (i32.const 18764) "`\0b")
- (import "env" "table" (table $timport$0 478 478 funcref))
- (import "env" "___syscall146" (func $import$0 (param i32 i32) (result i32)))
- (global $global$0 (mut i32) (i32.const 1))
  (elem (i32.const 0) $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $___stdout_write $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $__ZNSt3__211__stdoutbufIcE6xsputnEPKci $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $__ZNSt3__211__stdoutbufIcE8overflowEi)
  (export "_main" (func $_main))
  (export "_malloc" (func $_malloc))
@@ -229,6 +230,7 @@ total
  [funcs]        : 6       
  [globals]      : 0             -1
  [imports]      : 3       
+ [memories]     : 0       
  [memory-data]  : 28      
  [table-data]   : 429     
  [tables]       : 0       
@@ -256,16 +258,16 @@ total
  (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $none_=>_i32 (func (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
+ (import "env" "table" (table $timport$0 478 478 funcref))
+ (import "env" "___syscall146" (func $import$0 (param i32 i32) (result i32)))
  (data (i32.const 2948) "\03")
  (data (i32.const 6828) "\04")
- (data (i32.const 7028) "\0d\00\00\00\06")
+ (data (i32.const 7028) "\r\00\00\00\06")
  (data (i32.const 10888) "hello, world!")
  (data (i32.const 18100) "\b8\1a")
  (data (i32.const 18128) ",I")
  (data (i32.const 18732) "D\1b")
  (data (i32.const 18764) "`\0b")
- (import "env" "table" (table $timport$0 478 478 funcref))
- (import "env" "___syscall146" (func $import$0 (param i32 i32) (result i32)))
  (elem (i32.const 0) $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $___stdout_write $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $__ZNSt3__211__stdoutbufIcE6xsputnEPKci $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $__ZNSt3__211__stdoutbufIcE8overflowEi)
  (export "_main" (func $_main))
  (export "_malloc" (func $_malloc))
@@ -448,6 +450,7 @@ total
  [funcs]        : 6       
  [globals]      : 0       
  [imports]      : 3       
+ [memories]     : 0       
  [memory-data]  : 28      
  [table-data]   : 429     
  [tables]       : 0       
@@ -475,16 +478,16 @@ total
  (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $none_=>_i32 (func (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
+ (import "env" "table" (table $timport$0 478 478 funcref))
+ (import "env" "___syscall146" (func $import$0 (param i32 i32) (result i32)))
  (data (i32.const 2948) "\03")
  (data (i32.const 6828) "\04")
- (data (i32.const 7028) "\0d\00\00\00\06")
+ (data (i32.const 7028) "\r\00\00\00\06")
  (data (i32.const 10888) "hello, world!")
  (data (i32.const 18100) "\b8\1a")
  (data (i32.const 18128) ",I")
  (data (i32.const 18732) "D\1b")
  (data (i32.const 18764) "`\0b")
- (import "env" "table" (table $timport$0 478 478 funcref))
- (import "env" "___syscall146" (func $import$0 (param i32 i32) (result i32)))
  (elem (i32.const 0) $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $___stdout_write $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $__ZNSt3__211__stdoutbufIcE6xsputnEPKci $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $b0 $__ZNSt3__211__stdoutbufIcE8overflowEi)
  (export "_main" (func $_main))
  (export "_malloc" (func $_malloc))
diff --git a/test/passes/duplicate-function-elimination_all-features.txt b/test/passes/duplicate-function-elimination_all-features.txt
index f714d48..2ad1bdc 100644
--- a/test/passes/duplicate-function-elimination_all-features.txt
+++ b/test/passes/duplicate-function-elimination_all-features.txt
@@ -27,7 +27,7 @@
   (unreachable)
  )
  (func $2 (result i32)
-  (call_ref
+  (call_ref $func
    (global.get $global$0)
   )
  )
diff --git a/test/passes/duplicate-function-elimination_all-features.wast b/test/passes/duplicate-function-elimination_all-features.wast
index 1d04e87..df8d26b 100644
--- a/test/passes/duplicate-function-elimination_all-features.wast
+++ b/test/passes/duplicate-function-elimination_all-features.wast
@@ -34,7 +34,7 @@
   (unreachable)
  )
  (func "export" (result i32)
-  (call_ref
+  (call_ref $func
    (global.get $global$0)
   )
  )
diff --git a/test/passes/duplicate-function-elimination_optimize-level=1.txt b/test/passes/duplicate-function-elimination_optimize-level=1.txt
index c5f9b26..bd20505 100644
--- a/test/passes/duplicate-function-elimination_optimize-level=1.txt
+++ b/test/passes/duplicate-function-elimination_optimize-level=1.txt
@@ -212,7 +212,7 @@
  (memory $0 0)
  (func $keep2
   (block $foo
-   (block $block
+   (block
     (drop
      (i32.const 0)
     )
@@ -222,7 +222,7 @@
  )
  (func $other
   (block $bar
-   (block $block
+   (block
     (drop
      (i32.const 1)
     )
diff --git a/test/passes/duplicate-function-elimination_optimize-level=2.txt b/test/passes/duplicate-function-elimination_optimize-level=2.txt
index 0ab205f..9a360fa 100644
--- a/test/passes/duplicate-function-elimination_optimize-level=2.txt
+++ b/test/passes/duplicate-function-elimination_optimize-level=2.txt
@@ -209,7 +209,7 @@
  (memory $0 0)
  (func $keep2
   (block $foo
-   (block $block
+   (block
     (drop
      (i32.const 0)
     )
@@ -219,7 +219,7 @@
  )
  (func $other
   (block $bar
-   (block $block
+   (block
     (drop
      (i32.const 1)
     )
diff --git a/test/passes/dwarf_with_exceptions.bin.txt b/test/passes/dwarf_with_exceptions.bin.txt
index 3878ba5..d207962 100644
--- a/test/passes/dwarf_with_exceptions.bin.txt
+++ b/test/passes/dwarf_with_exceptions.bin.txt
@@ -119,7 +119,7 @@
  ;; custom section ".debug_line", size 109
  ;; custom section ".debug_str", size 178
  ;; custom section "producers", size 134
- ;; features section: exception-handling
+ ;; features section: mutable-globals, sign-ext, exception-handling
 )
 DWARF debug info
 ================
@@ -540,5 +540,5 @@ file_names[  1]:
  ;; custom section ".debug_line", size 162
  ;; custom section ".debug_str", size 178
  ;; custom section "producers", size 134
- ;; features section: exception-handling
+ ;; features section: mutable-globals, sign-ext, exception-handling
 )
diff --git a/test/passes/fannkuch0_dwarf.bin.txt b/test/passes/fannkuch0_dwarf.bin.txt
index 8fb6f82..aab7c22 100644
--- a/test/passes/fannkuch0_dwarf.bin.txt
+++ b/test/passes/fannkuch0_dwarf.bin.txt
@@ -5196,7 +5196,6 @@ file_names[  3]:
  (type $i32_=>_none (func (param i32)))
  (type $none_=>_none (func))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "Wrong argument.\n\00Pfannkuchen(%d) = %d.\n\00%d\00\n\00")
  (import "env" "__indirect_function_table" (table $timport$0 1 funcref))
  (import "env" "malloc" (func $malloc (param i32) (result i32)))
  (import "env" "free" (func $free (param i32)))
@@ -5204,6 +5203,7 @@ file_names[  3]:
  (import "env" "printf" (func $printf (param i32 i32) (result i32)))
  (global $global$0 (mut i32) (i32.const 5243952))
  (global $global$1 i32 (i32.const 1069))
+ (data (i32.const 1024) "Wrong argument.\n\00Pfannkuchen(%d) = %d.\n\00%d\00\n\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
  (export "__data_end" (global $global$1))
diff --git a/test/passes/fannkuch3_dwarf.bin.txt b/test/passes/fannkuch3_dwarf.bin.txt
index 3cfa212..3b98476 100644
--- a/test/passes/fannkuch3_dwarf.bin.txt
+++ b/test/passes/fannkuch3_dwarf.bin.txt
@@ -4796,7 +4796,6 @@ file_names[  4]:
  (type $i32_=>_none (func (param i32)))
  (type $none_=>_none (func))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "Pfannkuchen(%d) = %d.\n\00%d\00Wrong argument.\00")
  (import "env" "__indirect_function_table" (table $timport$0 1 funcref))
  (import "env" "malloc" (func $malloc (param i32) (result i32)))
  (import "env" "memcpy" (func $memcpy (param i32 i32 i32) (result i32)))
@@ -4807,6 +4806,7 @@ file_names[  4]:
  (import "env" "putchar" (func $putchar (param i32) (result i32)))
  (global $global$0 (mut i32) (i32.const 5243952))
  (global $global$1 i32 (i32.const 1066))
+ (data (i32.const 1024) "Pfannkuchen(%d) = %d.\n\00%d\00Wrong argument.\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
  (export "__data_end" (global $global$1))
diff --git a/test/passes/fannkuch3_manyopts_dwarf.bin.txt b/test/passes/fannkuch3_manyopts_dwarf.bin.txt
index 00fed4b..b9750d2 100644
--- a/test/passes/fannkuch3_manyopts_dwarf.bin.txt
+++ b/test/passes/fannkuch3_manyopts_dwarf.bin.txt
@@ -4704,7 +4704,6 @@ file_names[  4]:
  (type $i32_=>_none (func (param i32)))
  (type $none_=>_none (func))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "Pfannkuchen(%d) = %d.\n\00%d\00Wrong argument.\00")
  (import "env" "malloc" (func $malloc (param i32) (result i32)))
  (import "env" "memcpy" (func $memcpy (param i32 i32 i32) (result i32)))
  (import "env" "free" (func $free (param i32)))
@@ -4714,6 +4713,7 @@ file_names[  4]:
  (import "env" "putchar" (func $putchar (param i32) (result i32)))
  (global $global$0 (mut i32) (i32.const 5243952))
  (global $global$1 i32 (i32.const 1066))
+ (data (i32.const 1024) "Pfannkuchen(%d) = %d.\n\00%d\00Wrong argument.\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
  (export "__data_end" (global $global$1))
diff --git a/test/passes/func-metrics.txt b/test/passes/func-metrics.txt
index a8b6885..9471e49 100644
--- a/test/passes/func-metrics.txt
+++ b/test/passes/func-metrics.txt
@@ -3,6 +3,7 @@ global
  [funcs]        : 3       
  [globals]      : 1       
  [imports]      : 0       
+ [memories]     : 1       
  [memory-data]  : 9       
  [table-data]   : 3       
  [tables]       : 1       
@@ -96,6 +97,7 @@ global
  [funcs]        : 0       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 0       
@@ -106,6 +108,7 @@ global
  [funcs]        : 3       
  [globals]      : 0       
  [imports]      : 1       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 0       
@@ -182,6 +185,7 @@ global
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 1       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 0       
@@ -215,6 +219,7 @@ global
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 1       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 0       
@@ -244,6 +249,7 @@ global
  [funcs]        : 1       
  [globals]      : 1       
  [imports]      : 1       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 1       
diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt
index 4c481cd..cf94c2f 100644
--- a/test/passes/fuzz_metrics_noprint.bin.txt
+++ b/test/passes/fuzz_metrics_noprint.bin.txt
@@ -1,32 +1,33 @@
 total
- [exports]      : 39      
- [funcs]        : 50      
+ [exports]      : 15      
+ [funcs]        : 21      
  [globals]      : 7       
  [imports]      : 4       
+ [memories]     : 1       
  [memory-data]  : 4       
- [table-data]   : 17      
+ [table-data]   : 7       
  [tables]       : 1       
  [tags]         : 0       
- [total]        : 3442    
- [vars]         : 108     
- Binary         : 299     
- Block          : 495     
- Break          : 110     
- Call           : 193     
- CallIndirect   : 29      
- Const          : 650     
- Drop           : 48      
- GlobalGet      : 295     
- GlobalSet      : 131     
- If             : 183     
- Load           : 79      
- LocalGet       : 199     
- LocalSet       : 153     
- Loop           : 71      
- Nop            : 45      
- RefFunc        : 17      
- Return         : 155     
- Select         : 25      
- Store          : 29      
- Unary          : 235     
+ [total]        : 2012    
+ [vars]         : 60      
+ Binary         : 195     
+ Block          : 269     
+ Break          : 80      
+ Call           : 67      
+ CallIndirect   : 14      
+ Const          : 373     
+ Drop           : 12      
+ GlobalGet      : 164     
+ GlobalSet      : 65      
+ If             : 110     
+ Load           : 47      
+ LocalGet       : 176     
+ LocalSet       : 116     
+ Loop           : 39      
+ Nop            : 24      
+ RefFunc        : 7       
+ Return         : 74      
+ Select         : 18      
+ Store          : 26      
+ Unary          : 135     
  Unreachable    : 1       
diff --git a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt
index e94bc35..e37b828 100644
--- a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt
+++ b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt
@@ -34,21 +34,21 @@
   (local $temp f64)
   block $topmost (result f64)
    i32.const 8
-   f64.load
+   f64.load $0
    i32.const 16
-   f64.load
+   f64.load $0
    f64.add
    i32.const 16
-   f64.load
+   f64.load $0
    f64.neg
    f64.add
    i32.const 8
-   f64.load
+   f64.load $0
    f64.neg
    f64.add
    local.set $temp
    i32.const 24
-   i32.load
+   i32.load $0
    i32.const 0
    i32.gt_s
    if
@@ -56,7 +56,7 @@
     br $topmost
    end
    i32.const 32
-   f64.load
+   f64.load $0
    f64.const 0
    f64.gt
    if
@@ -323,7 +323,7 @@
  (func $i64-store32 (param $0 i32) (param $1 i64)
   local.get $0
   local.get $1
-  i64.store32
+  i64.store32 $0
  )
  (func $return-unreachable (result i32)
   i32.const 1
@@ -1084,7 +1084,7 @@
  )
  (func $unreachable-block (; has Stack IR ;) (result i32)
   (f64.abs
-   (block $block
+   (block
     (drop
      (i32.const 1)
     )
@@ -1095,18 +1095,16 @@
   )
  )
  (func $unreachable-block-toplevel (; has Stack IR ;) (result i32)
-  (block $block
-   (drop
-    (i32.const 1)
-   )
-   (return
-    (i32.const 2)
-   )
+  (drop
+   (i32.const 1)
+  )
+  (return
+   (i32.const 2)
   )
  )
  (func $unreachable-block0 (; has Stack IR ;) (result i32)
   (f64.abs
-   (block $block
+   (block
     (return
      (i32.const 2)
     )
@@ -1114,10 +1112,8 @@
   )
  )
  (func $unreachable-block0-toplevel (; has Stack IR ;) (result i32)
-  (block $block
-   (return
-    (i32.const 2)
-   )
+  (return
+   (i32.const 2)
   )
  )
  (func $unreachable-block-with-br (; has Stack IR ;) (result i32)
@@ -1235,10 +1231,8 @@
  (func $unreachable-if-arm (; has Stack IR ;)
   (if
    (i32.const 1)
-   (block $block
-    (nop)
-   )
-   (block $block12
+   (nop)
+   (block
     (unreachable)
     (drop
      (i32.const 1)
@@ -1552,7 +1546,7 @@
   (local $temp1 i32)
   (if
    (i32.const 0)
-   (block $block
+   (block
     (local.set $temp1
      (call $local-to-stack-multi-4
       (i32.const 0)
@@ -1562,7 +1556,7 @@
      (local.get $temp1)
     )
    )
-   (block $block13
+   (block
     (local.set $temp1
      (call $local-to-stack-multi-4
       (i32.const 1)
@@ -1581,7 +1575,7 @@
     (i32.const 0)
    )
    (i32.eqz
-    (block $block (result i32)
+    (block (result i32)
      (local.set $temp
       (call $remove-block
        (i32.const 1)
diff --git a/test/passes/ignore_missing_func_dwarf.bin.txt b/test/passes/ignore_missing_func_dwarf.bin.txt
index c1ff4d7..2ac6414 100644
--- a/test/passes/ignore_missing_func_dwarf.bin.txt
+++ b/test/passes/ignore_missing_func_dwarf.bin.txt
@@ -4,11 +4,11 @@
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "\nvoid used(int x) {\n  x++;\n  x--;\n  return x;\n}\n\nvoid unused(int x) {\n  x >>= 1;\n  x <<= 1;\n  return x;\n}\n\nint main() {\n  return used(42);\n}\n\00")
- (data (i32.const 1168) "\00\04\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 1 funcref))
  (global $global$0 (mut i32) (i32.const 5244064))
  (global $global$1 i32 (i32.const 1172))
+ (data (i32.const 1024) "\nvoid used(int x) {\n  x++;\n  x--;\n  return x;\n}\n\nvoid unused(int x) {\n  x >>= 1;\n  x <<= 1;\n  return x;\n}\n\nint main() {\n  return used(42);\n}\n\00")
+ (data (i32.const 1168) "\00\04\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
  (export "__data_end" (global $global$1))
@@ -827,11 +827,11 @@ file_names[  1]:
  (type $none_=>_i32 (func (result i32)))
  (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "\nvoid used(int x) {\n  x++;\n  x--;\n  return x;\n}\n\nvoid unused(int x) {\n  x >>= 1;\n  x <<= 1;\n  return x;\n}\n\nint main() {\n  return used(42);\n}\n\00")
- (data (i32.const 1168) "\00\04\00\00")
  (import "env" "__indirect_function_table" (table $timport$0 1 funcref))
  (global $global$0 (mut i32) (i32.const 5244064))
  (global $global$1 i32 (i32.const 1172))
+ (data (i32.const 1024) "\nvoid used(int x) {\n  x++;\n  x--;\n  return x;\n}\n\nvoid unused(int x) {\n  x >>= 1;\n  x <<= 1;\n  return x;\n}\n\nint main() {\n  return used(42);\n}\n\00")
+ (data (i32.const 1168) "\00\04\00\00")
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
  (export "main" (func $main))
  (export "__data_end" (global $global$1))
diff --git a/test/passes/inlined_to_start_dwarf.bin.txt b/test/passes/inlined_to_start_dwarf.bin.txt
index 8eff2c8..44b4cf2 100644
--- a/test/passes/inlined_to_start_dwarf.bin.txt
+++ b/test/passes/inlined_to_start_dwarf.bin.txt
@@ -415,9 +415,9 @@ file_names[  1]:
  (type $i32_=>_none (func (param i32)))
  (type $i32_=>_i32 (func (param i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "\00\00\00\00")
  (global $global$0 (mut i32) (i32.const 5243920))
  (global $global$1 i32 (i32.const 1028))
+ (data (i32.const 1024) "\00\00\00\00")
  (table $0 1 1 funcref)
  (export "__indirect_function_table" (table $0))
  (export "__wasm_call_ctors" (func $__wasm_call_ctors))
diff --git a/test/passes/legalize-js-interface-minimally.txt b/test/passes/legalize-js-interface-minimally.txt
deleted file mode 100644
index 2234344..0000000
--- a/test/passes/legalize-js-interface-minimally.txt
+++ /dev/null
@@ -1,59 +0,0 @@
-(module
- (type $none_=>_i64 (func (result i64)))
- (type $i32_=>_none (func (param i32)))
- (type $none_=>_i32 (func (result i32)))
- (type $i64_=>_none (func (param i64)))
- (type $i32_i32_=>_none (func (param i32 i32)))
- (import "env" "imported" (func $imported (result i64)))
- (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
- (import "env" "invoke_vj" (func $legalimport$invoke_vj (param i32 i32)))
- (export "func" (func $func))
- (export "dynCall_foo" (func $legalstub$dyn))
- (func $func (result i64)
-  (drop
-   (call $imported)
-  )
-  (call $legalfunc$invoke_vj
-   (i64.const 0)
-  )
-  (unreachable)
- )
- (func $dyn (result i64)
-  (drop
-   (call $imported)
-  )
-  (unreachable)
- )
- (func $legalstub$dyn (result i32)
-  (local $0 i64)
-  (local.set $0
-   (call $dyn)
-  )
-  (call $setTempRet0
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $0)
-     (i64.const 32)
-    )
-   )
-  )
-  (i32.wrap_i64
-   (local.get $0)
-  )
- )
- (func $legalfunc$invoke_vj (param $0 i64)
-  (call $legalimport$invoke_vj
-   (i32.wrap_i64
-    (local.get $0)
-   )
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $0)
-     (i64.const 32)
-    )
-   )
-  )
- )
-)
-(module
-)
diff --git a/test/passes/legalize-js-interface-minimally.wast b/test/passes/legalize-js-interface-minimally.wast
deleted file mode 100644
index e820734..0000000
--- a/test/passes/legalize-js-interface-minimally.wast
+++ /dev/null
@@ -1,17 +0,0 @@
-(module
-  (import "env" "imported" (func $imported (result i64)))
-  (import "env" "invoke_vj" (func $invoke_vj (param i64)))
-  (export "func" (func $func))
-  (export "dynCall_foo" (func $dyn))
-  (func $func (result i64)
-    (drop (call $imported))
-    (call $invoke_vj (i64.const 0))
-    (unreachable)
-  )
-  (func $dyn (result i64)
-    (drop (call $imported))
-    (unreachable)
-  )
-)
-(module)
-
diff --git a/test/passes/legalize-js-interface_all-features.txt b/test/passes/legalize-js-interface_all-features.txt
deleted file mode 100644
index b1c57b0..0000000
--- a/test/passes/legalize-js-interface_all-features.txt
+++ /dev/null
@@ -1,150 +0,0 @@
-(module
- (type $none_=>_i32 (func (result i32)))
- (type $none_=>_i64 (func (result i64)))
- (type $i32_i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32 i32)))
- (type $none_=>_none (func))
- (type $i32_=>_none (func (param i32)))
- (type $i32_i64_i64_=>_none (func (param i32 i64 i64)))
- (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
- (import "env" "getTempRet0" (func $getTempRet0 (result i32)))
- (import "env" "imported" (func $legalimport$imported (result i32)))
- (import "env" "other" (func $legalimport$other (param i32 i32 i32 i32 i32)))
- (import "env" "ref-func-arg" (func $legalimport$ref-func-arg (result i32)))
- (elem declare func $legalfunc$ref-func-arg)
- (export "func" (func $legalstub$func))
- (export "ref-func-test" (func $ref-func-test))
- (export "imported" (func $legalstub$imported))
- (export "imported_again" (func $legalstub$imported))
- (export "other" (func $legalstub$other))
- (func $func (result i64)
-  (drop
-   (call $legalfunc$imported)
-  )
-  (call $legalfunc$other
-   (i32.const 0)
-   (i64.const 0)
-   (i64.const 0)
-  )
-  (unreachable)
- )
- (func $ref-func-test
-  (drop
-   (call $legalfunc$ref-func-arg)
-  )
-  (drop
-   (ref.func $legalfunc$ref-func-arg)
-  )
- )
- (func $legalstub$func (result i32)
-  (local $0 i64)
-  (local.set $0
-   (call $func)
-  )
-  (call $setTempRet0
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $0)
-     (i64.const 32)
-    )
-   )
-  )
-  (i32.wrap_i64
-   (local.get $0)
-  )
- )
- (func $legalstub$imported (result i32)
-  (local $0 i64)
-  (local.set $0
-   (call $legalfunc$imported)
-  )
-  (call $setTempRet0
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $0)
-     (i64.const 32)
-    )
-   )
-  )
-  (i32.wrap_i64
-   (local.get $0)
-  )
- )
- (func $legalstub$other (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32)
-  (call $legalfunc$other
-   (local.get $0)
-   (i64.or
-    (i64.extend_i32_u
-     (local.get $1)
-    )
-    (i64.shl
-     (i64.extend_i32_u
-      (local.get $2)
-     )
-     (i64.const 32)
-    )
-   )
-   (i64.or
-    (i64.extend_i32_u
-     (local.get $3)
-    )
-    (i64.shl
-     (i64.extend_i32_u
-      (local.get $4)
-     )
-     (i64.const 32)
-    )
-   )
-  )
- )
- (func $legalfunc$imported (result i64)
-  (i64.or
-   (i64.extend_i32_u
-    (call $legalimport$imported)
-   )
-   (i64.shl
-    (i64.extend_i32_u
-     (call $getTempRet0)
-    )
-    (i64.const 32)
-   )
-  )
- )
- (func $legalfunc$other (param $0 i32) (param $1 i64) (param $2 i64)
-  (call $legalimport$other
-   (local.get $0)
-   (i32.wrap_i64
-    (local.get $1)
-   )
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $1)
-     (i64.const 32)
-    )
-   )
-   (i32.wrap_i64
-    (local.get $2)
-   )
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $2)
-     (i64.const 32)
-    )
-   )
-  )
- )
- (func $legalfunc$ref-func-arg (result i64)
-  (i64.or
-   (i64.extend_i32_u
-    (call $legalimport$ref-func-arg)
-   )
-   (i64.shl
-    (i64.extend_i32_u
-     (call $getTempRet0)
-    )
-    (i64.const 32)
-   )
-  )
- )
-)
-(module
-)
diff --git a/test/passes/legalize-js-interface_all-features.wast b/test/passes/legalize-js-interface_all-features.wast
deleted file mode 100644
index 7f4352f..0000000
--- a/test/passes/legalize-js-interface_all-features.wast
+++ /dev/null
@@ -1,30 +0,0 @@
-(module
-  (import "env" "imported" (func $imported (result i64)))
-  (import "env" "other" (func $other (param i32) (param i64) (param i64)))
-  (import "env" "ref-func-arg" (func $ref-func-arg (result i64)))
-  (export "func" (func $func))
-  (export "ref-func-test" (func $ref-func-test))
-  (export "imported" (func $imported))
-  (export "imported_again" (func $imported))
-  (export "other" (func $other))
-  (func $func (result i64)
-    (drop (call $imported))
-    (call $other
-      (i32.const 0)
-      (i64.const 0)
-      (i64.const 0)
-    )
-    (unreachable)
-  )
-
-  ;; ref.func must also be updated.
-  (func $ref-func-test
-    (drop
-      (call $ref-func-arg)
-    )
-    (drop
-      (ref.func $ref-func-arg)
-    )
-  )
-)
-(module)
diff --git a/test/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.txt b/test/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.txt
deleted file mode 100644
index 1327a43..0000000
--- a/test/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-(module
- (type $none_=>_i64 (func (result i64)))
- (type $i32_=>_none (func (param i32)))
- (type $none_=>_i32 (func (result i32)))
- (import "env" "setTempRet0" (func $setTempRet0 (param i32)))
- (export "func" (func $legalstub$func))
- (export "orig$func" (func $func))
- (func $func (result i64)
-  (unreachable)
- )
- (func $legalstub$func (result i32)
-  (local $0 i64)
-  (local.set $0
-   (call $func)
-  )
-  (call $setTempRet0
-   (i32.wrap_i64
-    (i64.shr_u
-     (local.get $0)
-     (i64.const 32)
-    )
-   )
-  )
-  (i32.wrap_i64
-   (local.get $0)
-  )
- )
-)
diff --git a/test/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.wast b/test/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.wast
deleted file mode 100644
index 4b55fa9..0000000
--- a/test/passes/legalize-js-interface_pass-arg=legalize-js-interface-export-originals.wast
+++ /dev/null
@@ -1,7 +0,0 @@
-(module
-  (export "func" (func $func))
-  (func $func (result i64)
-    (unreachable)
-  )
-)
-
diff --git a/test/passes/licm.txt b/test/passes/licm.txt
index 0ad7af7..ef1c203 100644
--- a/test/passes/licm.txt
+++ b/test/passes/licm.txt
@@ -403,7 +403,7 @@
  )
  (func $nested-blocks
   (loop $loop
-   (block $block
+   (block
     (nop)
    )
    (block $x
@@ -423,7 +423,7 @@
  )
  (func $nested-unhoistable-blocks
   (loop $loop
-   (block $block
+   (block
     (call $nested-unhoistable-blocks)
    )
    (block $x
diff --git a/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.txt b/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.txt
index 5357782..243102f 100644
--- a/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.txt
+++ b/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.txt
@@ -1,7 +1,10 @@
 (module
  (type $none_=>_none (func))
+ (import "env" "__memory_base" (global $__memory_base i64))
+ (import "env" "__memory_base32" (global $__memory_base32 i32))
  (memory $0 1 1)
  (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00")
+ (data (global.get $__memory_base32) "foo")
  (func $func_1
   (local $0 i64)
   (drop
diff --git a/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.wast b/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.wast
index c59da35..7cb8959 100644
--- a/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.wast
+++ b/test/passes/memory64-lowering_enable-memory64_enable-bulk-memory_enable-threads.wast
@@ -1,6 +1,8 @@
 (module
+ (import "env" "__memory_base" (global $__memory_base i64))
  (memory $0 i64 1 1)
  (data (i64.const 0) "\00\00\00\00\00\00\00\00\00\00")
+ (data (global.get $__memory_base) "foo")
  (func $func_1
   (local i64)
   (drop (i32.load (i64.const 4)))
diff --git a/test/passes/merge-blocks.txt b/test/passes/merge-blocks.txt
index 70ecce6..fa82ac0 100644
--- a/test/passes/merge-blocks.txt
+++ b/test/passes/merge-blocks.txt
@@ -5,72 +5,62 @@
  (type $none_=>_f32 (func (result f32)))
  (global $global$0 (mut i32) (i32.const 10))
  (func $drop-block
-  (block $block
-   (drop
-    (i32.const 0)
-   )
+  (drop
+   (i32.const 0)
   )
  )
  (func $drop-block-br
-  (block $block
-   (drop
-    (block $x (result i32)
-     (br $x
-      (i32.const 1)
-     )
-     (i32.const 0)
+  (drop
+   (block $x (result i32)
+    (br $x
+     (i32.const 1)
     )
+    (i32.const 0)
    )
   )
  )
  (func $drop-block-br-if
-  (block $block
-   (drop
-    (i32.const 1)
+  (drop
+   (i32.const 1)
+  )
+  (block $x
+   (br_if $x
+    (i32.const 2)
    )
-   (block $x
-    (br_if $x
-     (i32.const 2)
-    )
-    (drop
-     (i32.const 0)
-    )
+   (drop
+    (i32.const 0)
    )
   )
  )
  (func $undroppable-block-br-if (param $0 i32)
-  (block $block
-   (drop
-    (block $x (result i32)
-     (call $undroppable-block-br-if
-      (br_if $x
-       (i32.const 1)
-       (i32.const 2)
-      )
+  (drop
+   (block $x (result i32)
+    (call $undroppable-block-br-if
+     (br_if $x
+      (i32.const 1)
+      (i32.const 2)
      )
-     (i32.const 0)
     )
+    (i32.const 0)
    )
   )
  )
  (func $drop-block-nested-br-if
-  (block $block
-   (block $x
-    (if
-     (i32.const 100)
-     (block $block0
-      (drop
-       (i32.const 1)
-      )
-      (br_if $x
-       (i32.const 2)
-      )
-      (nop)
+  (block $x
+   (if
+    (i32.const 100)
+    (block
+     (drop
+      (i32.const 1)
      )
+     (br_if $x
+      (i32.const 2)
+     )
+     (nop)
     )
-    (drop
-     (i32.const 0)
-    )
+   )
+   (drop
+    (i32.const 0)
    )
   )
  )
@@ -100,21 +90,19 @@
   )
  )
  (func $br-goes-away-label2-becomes-unreachable
-  (block $block
-   (drop
-    (block $label$1 (result i32)
-     (block $label$2
-      (drop
-       (br_if $label$1
-        (unreachable)
-        (i32.eqz
-         (br $label$2)
-        )
+  (drop
+   (block $label$1 (result i32)
+    (block $label$2
+     (drop
+      (br_if $label$1
+       (unreachable)
+       (i32.eqz
+        (br $label$2)
        )
       )
      )
-     (i32.const 1)
     )
+    (i32.const 1)
    )
   )
  )
@@ -133,7 +121,7 @@
   (block $label
    (if
     (i32.const 1)
-    (block $block
+    (block
      (drop
       (i32.const 2)
      )
@@ -148,7 +136,7 @@
   (block $label
    (if
     (br $label)
-    (block $block
+    (block
      (drop
       (i32.const 2)
      )
@@ -250,7 +238,7 @@
   (if
    (i32.const 1)
    (nop)
-   (block $block
+   (block
     (drop
      (loop $label$3 (result i64)
       (br_if $label$3
diff --git a/test/passes/merge-locals_all-features.txt b/test/passes/merge-locals_all-features.txt
index d3e9cc0..cf11edc 100644
--- a/test/passes/merge-locals_all-features.txt
+++ b/test/passes/merge-locals_all-features.txt
@@ -333,7 +333,7 @@
    (drop
     (local.get $x)
    )
-   (block $block
+   (block
     (if
      (i32.const 1)
      (local.set $x
@@ -389,11 +389,11 @@
         )
        )
        (i32.const 0)
-       (block $block (result i32)
+       (block (result i32)
         (local.set $var$3
          (if (result i32)
           (i32.const 0)
-          (block $block13 (result i32)
+          (block (result i32)
            (block $label$7
             (block $label$8
              (local.set $var$0
@@ -403,7 +403,7 @@
            )
            (local.get $var$3)
           )
-          (block $block14 (result i32)
+          (block (result i32)
            (if
             (i32.eqz
              (global.get $global$0)
@@ -459,8 +459,8 @@
  )
  (func $subtype-test
   (local $0 anyref)
-  (local $1 funcref)
-  (local $2 funcref)
+  (local $1 i31ref)
+  (local $2 i31ref)
   (local.set $0
    (local.get $1)
   )
diff --git a/test/passes/merge-locals_all-features.wast b/test/passes/merge-locals_all-features.wast
index 1cdac26..1b73c9a 100644
--- a/test/passes/merge-locals_all-features.wast
+++ b/test/passes/merge-locals_all-features.wast
@@ -377,8 +377,8 @@
  )
  (func $subtype-test
   (local $0 anyref)
-  (local $1 funcref)
-  (local $2 funcref)
+  (local $1 (ref null i31))
+  (local $2 (ref null i31))
   (local.set $0
    (local.get $1)
   )
diff --git a/test/passes/metrics_all-features.txt b/test/passes/metrics_all-features.txt
index 255e809..bd3368b 100644
--- a/test/passes/metrics_all-features.txt
+++ b/test/passes/metrics_all-features.txt
@@ -3,6 +3,7 @@ total
  [funcs]        : 1       
  [globals]      : 1       
  [imports]      : 0       
+ [memories]     : 1       
  [memory-data]  : 9       
  [table-data]   : 3       
  [tables]       : 1       
@@ -70,6 +71,7 @@ total
  [funcs]        : 0       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 0       
diff --git a/test/passes/metrics_strip-debug_metrics.bin.txt b/test/passes/metrics_strip-debug_metrics.bin.txt
index 9760353..0022a8b 100644
--- a/test/passes/metrics_strip-debug_metrics.bin.txt
+++ b/test/passes/metrics_strip-debug_metrics.bin.txt
@@ -3,6 +3,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 1       
@@ -13,6 +14,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 1       
diff --git a/test/passes/metrics_strip-producers_metrics.bin.txt b/test/passes/metrics_strip-producers_metrics.bin.txt
index 072f7c8..428401e 100644
--- a/test/passes/metrics_strip-producers_metrics.bin.txt
+++ b/test/passes/metrics_strip-producers_metrics.bin.txt
@@ -3,6 +3,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 1       
@@ -13,6 +14,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 1       
diff --git a/test/passes/optimize-instructions_fuzz-exec.txt b/test/passes/optimize-instructions_fuzz-exec.txt
index ea32b89..a774744 100644
--- a/test/passes/optimize-instructions_fuzz-exec.txt
+++ b/test/passes/optimize-instructions_fuzz-exec.txt
@@ -38,20 +38,10 @@
  (export "ignore" (func $3))
  (func $0
   (call $logf32
-   (f32.add
-    (f32.const -nan:0x7fff82)
-    (f32.neg
-     (f32.const -nan:0x7ff622)
-    )
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
-   (f32.sub
-    (f32.const -nan:0x7fff82)
-    (f32.neg
-     (f32.const -nan:0x7ff622)
-    )
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
    (f32.mul
@@ -62,10 +52,7 @@
    )
   )
   (call $logf32
-   (f32.div
-    (f32.const nan:0x7fff82)
-    (f32.const -nan:0x7ff622)
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
    (f32.copysign
@@ -76,38 +63,18 @@
    )
   )
   (call $logf32
-   (f32.min
-    (f32.const -nan:0x7fff82)
-    (f32.neg
-     (f32.const -nan:0x7ff622)
-    )
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
-   (f32.max
-    (f32.const -nan:0x7fff82)
-    (f32.neg
-     (f32.const -nan:0x7ff622)
-    )
-   )
+   (f32.const nan:0x400000)
   )
  )
  (func $1
   (call $logf64
-   (f64.add
-    (f64.const -nan:0xfffffffffff82)
-    (f64.neg
-     (f64.const -nan:0xfffffffffa622)
-    )
-   )
+   (f64.const nan:0x8000000000000)
   )
   (call $logf64
-   (f64.sub
-    (f64.const -nan:0xfffffffffff82)
-    (f64.neg
-     (f64.const -nan:0xfffffffffa622)
-    )
-   )
+   (f64.const nan:0x8000000000000)
   )
   (call $logf64
    (f64.mul
@@ -118,10 +85,7 @@
    )
   )
   (call $logf64
-   (f64.div
-    (f64.const nan:0xfffffffffff82)
-    (f64.const -nan:0xfffffffffa622)
-   )
+   (f64.const nan:0x8000000000000)
   )
   (call $logf64
    (f64.copysign
@@ -132,69 +96,38 @@
    )
   )
   (call $logf64
-   (f64.min
-    (f64.const -nan:0xfffffffffff82)
-    (f64.neg
-     (f64.const -nan:0xfffffffffa622)
-    )
-   )
+   (f64.const nan:0x8000000000000)
   )
   (call $logf64
-   (f64.max
-    (f64.const -nan:0xfffffffffff82)
-    (f64.neg
-     (f64.const -nan:0xfffffffffa622)
-    )
-   )
+   (f64.const nan:0x8000000000000)
   )
  )
  (func $2
   (call $logf32
-   (f32.add
-    (f32.neg
-     (f32.const -nan:0x7ff622)
-    )
-    (f32.const 0)
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
    (f32.add
     (f32.const -nan:0x7ff622)
-    (f32.neg
-     (f32.const 0)
-    )
+    (f32.const -0)
    )
   )
   (call $logf32
-   (f32.add
-    (f32.neg
-     (f32.const -nan:0x7ff622)
-    )
-    (f32.const -0)
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
    (f32.add
     (f32.const -nan:0x7ff622)
-    (f32.neg
-     (f32.const -0)
-    )
+    (f32.const 0)
    )
   )
   (call $logf32
-   (f32.add
-    (f32.neg
-     (f32.const nan:0x7ff622)
-    )
-    (f32.const 0)
-   )
+   (f32.const nan:0x400000)
   )
   (call $logf32
    (f32.add
     (f32.const nan:0x7ff622)
-    (f32.neg
-     (f32.const 0)
-    )
+    (f32.const -0)
    )
   )
  )
@@ -322,22 +255,15 @@
    )
   )
   (call $log
-   (i32.eq
-    (i32.const 8)
-    (i32.const -2147483648)
-   )
+   (i32.const 0)
   )
  )
  (func $shift (param $0 i32)
   (call $log
    (i32.shr_s
     (i32.shl
-     (i32.shr_s
-      (i32.shl
-       (local.get $0)
-       (i32.const 24)
-      )
-      (i32.const 24)
+     (i32.extend8_s
+      (local.get $0)
      )
      (i32.const 30)
     )
diff --git a/test/passes/pick-load-signs_all-features.txt b/test/passes/pick-load-signs_all-features.txt
index 4b08c75..8fcc78a 100644
--- a/test/passes/pick-load-signs_all-features.txt
+++ b/test/passes/pick-load-signs_all-features.txt
@@ -4,7 +4,7 @@
  (func $atomics-are-always-unsigned (result i32)
   (local $0 i32)
   (drop
-   (block $block (result i32)
+   (block (result i32)
     (local.set $0
      (i32.atomic.load16_u
       (i32.const 27)
diff --git a/test/passes/precompute-propagate_all-features.txt b/test/passes/precompute-propagate_all-features.txt
index 6001d54..edb5b1e 100644
--- a/test/passes/precompute-propagate_all-features.txt
+++ b/test/passes/precompute-propagate_all-features.txt
@@ -250,7 +250,7 @@
  )
  (func $through-fallthrough (param $x i32) (param $y i32) (result i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (nop)
     (local.tee $y
      (i32.const 7)
diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt
index 70c7904..ea582d8 100644
--- a/test/passes/precompute_all-features.txt
+++ b/test/passes/precompute_all-features.txt
@@ -5,7 +5,7 @@
  (type $0 (func (param i32)))
  (type $none_=>_v128 (func (result v128)))
  (type $none_=>_i32_i64 (func (result i32 i64)))
- (type $none_=>_anyref (func (result anyref)))
+ (type $none_=>_externref (func (result externref)))
  (global $global i32 (i32.const 1))
  (global $global-mut (mut i32) (i32.const 2))
  (memory $0 512 512)
@@ -171,7 +171,7 @@
     (block
      (select
       (i64.const 1)
-      (block $block
+      (block
        (global.set $global-mut
         (i32.const 1)
        )
@@ -253,8 +253,8 @@
  (func $loop-precompute (result i32)
   (i32.const 1)
  )
- (func $reftype-test (result anyref)
-  (ref.null any)
+ (func $reftype-test (result externref)
+  (ref.null noextern)
  )
  (func $dummy
   (nop)
@@ -276,22 +276,22 @@
    )
   )
   (drop
-   (block $l2 (result anyref)
+   (block $l2 (result nullexternref)
     (drop
      (block $l3
       (global.set $global-mut
        (i32.const 1)
       )
       (br $l2
-       (ref.null any)
+       (ref.null noextern)
       )
      )
     )
-    (ref.null any)
+    (ref.null noextern)
    )
   )
   (drop
-   (block $l4 (result funcref)
+   (block $l4 (result (ref null $none_=>_none))
     (drop
      (block $l5
       (global.set $global-mut
@@ -302,7 +302,7 @@
       )
      )
     )
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
  )
diff --git a/test/passes/print-call-graph.txt b/test/passes/print-call-graph.txt
index df78a2c..b8c3c12 100644
--- a/test/passes/print-call-graph.txt
+++ b/test/passes/print-call-graph.txt
@@ -121,7 +121,6 @@ digraph call {
  (type $FUNCSIG$v (func))
  (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
  (import "env" "memory" (memory $0 256 256))
- (data (global.get $memoryBase) "\05\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\02\00\00\00\b0\04\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\04")
  (import "env" "table" (table $timport$0 9 9 funcref))
  (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
  (import "env" "STACK_MAX" (global $STACK_MAX$asm2wasm$import i32))
@@ -161,6 +160,7 @@ digraph call {
  (global $tempRet0 (mut i32) (i32.const 0))
  (global $tempFloat (mut f32) (f32.const 0))
  (global $f0 (mut f32) (f32.const 0))
+ (data (global.get $memoryBase) "\05\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\02\00\00\00\b0\04\00\00\00\04\00\00\00\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\n\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\04")
  (elem (i32.const 0) $b0 $___stdio_close $b1 $___stdout_write $___stdio_seek $___stdio_write $b2 $_cleanup_387 $b3)
  (export "_fflush" (func $_fflush))
  (export "_main" (func $_main))
@@ -225,7 +225,7 @@ digraph call {
    (i32.eqz
     (global.get $__THREW__)
    )
-   (block $block
+   (block
     (global.set $__THREW__
      (local.get $0)
     )
@@ -404,7 +404,7 @@ digraph call {
            (i32.load
             (i32.const 1140)
            )
-           (block $block (result i32)
+           (block (result i32)
             (call $_pthread_cleanup_push
              (i32.const 1)
              (local.get $0)
@@ -436,7 +436,7 @@ digraph call {
             )
             (local.get $3)
            )
-           (block $block0 (result i32)
+           (block (result i32)
             (i32.store
              (local.get $8)
              (i32.load
@@ -484,7 +484,7 @@ digraph call {
            )
           )
          )
-         (block $block2 (result i32)
+         (block (result i32)
           (i32.store
            (local.get $6)
            (local.tee $3
@@ -524,7 +524,7 @@ digraph call {
            (local.get $5)
            (i32.const 2)
           )
-          (block $block4 (result i32)
+          (block (result i32)
            (i32.store
             (local.get $6)
             (i32.add
@@ -542,7 +542,7 @@ digraph call {
            )
            (local.get $12)
           )
-          (block $block5 (result i32)
+          (block (result i32)
            (local.set $3
             (local.get $1)
            )
@@ -693,7 +693,7 @@ digraph call {
      )
      (i32.const 0)
     )
-    (block $block (result i32)
+    (block (result i32)
      (i32.store
       (local.get $0)
       (i32.const -1)
@@ -716,7 +716,7 @@ digraph call {
     (local.get $0)
     (i32.const -4096)
    )
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (call $___errno_location)
      (i32.sub
@@ -787,7 +787,7 @@ digraph call {
      (i32.const 64)
     )
    )
-   (block $block
+   (block
     (i32.store
      (local.get $3)
      (i32.load offset=60
@@ -832,7 +832,7 @@ digraph call {
   (block $do-once (result i32)
    (if (result i32)
     (local.get $0)
-    (block $block (result i32)
+    (block (result i32)
      (if
       (i32.le_s
        (i32.load offset=76
@@ -861,7 +861,7 @@ digraph call {
      (if (result i32)
       (local.get $2)
       (local.get $1)
-      (block $block9 (result i32)
+      (block (result i32)
        (call $_free
         (local.get $0)
        )
@@ -869,7 +869,7 @@ digraph call {
       )
      )
     )
-    (block $block10 (result i32)
+    (block (result i32)
      (local.set $0
       (if (result i32)
        (i32.load
@@ -1091,7 +1091,7 @@ digraph call {
      (local.tee $0
       (call $__ZSt15get_new_handlerv)
      )
-     (block $block
+     (block
       (call_indirect (type $FUNCSIG$v)
        (i32.add
         (i32.and
@@ -1145,7 +1145,7 @@ digraph call {
     (local.get $2)
     (i32.const 20)
    )
-   (block $block
+   (block
     (local.set $5
      (i32.or
       (i32.or
@@ -1185,7 +1185,7 @@ digraph call {
        (i32.const 3)
       )
      )
-     (block $block17
+     (block
       (local.set $3
        (i32.sub
         (i32.add
@@ -1201,7 +1201,7 @@ digraph call {
          (local.get $0)
          (local.get $3)
         )
-        (block $block19
+        (block
          (i32.store8
           (local.get $0)
           (local.get $1)
@@ -1224,7 +1224,7 @@ digraph call {
        (local.get $0)
        (local.get $6)
       )
-      (block $block21
+      (block
        (i32.store
         (local.get $0)
         (local.get $5)
@@ -1247,7 +1247,7 @@ digraph call {
      (local.get $0)
      (local.get $4)
     )
-    (block $block23
+    (block
      (i32.store8
       (local.get $0)
       (local.get $1)
@@ -1296,7 +1296,7 @@ digraph call {
      (i32.const 3)
     )
    )
-   (block $block
+   (block
     (loop $while-in
      (block $while-out
       (br_if $while-out
@@ -1348,7 +1348,7 @@ digraph call {
        (local.get $2)
        (i32.const 4)
       )
-      (block $block27
+      (block
        (i32.store
         (local.get $0)
         (i32.load
@@ -1385,7 +1385,7 @@ digraph call {
      (local.get $2)
      (i32.const 0)
     )
-    (block $block29
+    (block
      (i32.store8
       (local.get $0)
       (i32.load8_s
diff --git a/test/passes/print_g_metrics.bin.txt b/test/passes/print_g_metrics.bin.txt
index ac03563..2dbceb4 100644
--- a/test/passes/print_g_metrics.bin.txt
+++ b/test/passes/print_g_metrics.bin.txt
@@ -69,6 +69,7 @@ total
  [funcs]        : 3       
  [globals]      : 1       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 37      
diff --git a/test/passes/remove-non-js-ops.txt b/test/passes/remove-non-js-ops.txt
index 00575ca..d3fa090 100644
--- a/test/passes/remove-non-js-ops.txt
+++ b/test/passes/remove-non-js-ops.txt
@@ -17,6 +17,7 @@
  (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
  (type $i32_i32_i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32 i32 i32) (result i32)))
  (type $none_=>_i32 (func (result i32)))
+ (type $none_=>_none (func))
  (import "env" "wasm2js_scratch_load_i32" (func $wasm2js_scratch_load_i32 (param i32) (result i32)))
  (import "env" "wasm2js_scratch_store_i32" (func $wasm2js_scratch_store_i32 (param i32 i32)))
  (import "env" "wasm2js_scratch_load_f32" (func $wasm2js_scratch_load_f32 (result f32)))
@@ -30,6 +31,7 @@
  (import "env" "wasm2js_atomic_wait_i32" (func $wasm2js_atomic_wait_i32 (param i32 i32 i32 i32) (result i32)))
  (import "env" "wasm2js_atomic_rmw_i64" (func $wasm2js_atomic_rmw_i64 (param i32 i32 i32 i32 i32 i32) (result i32)))
  (import "env" "wasm2js_get_stashed_bits" (func $wasm2js_get_stashed_bits (result i32)))
+ (import "env" "wasm2js_trap" (func $wasm2js_trap))
  (global $__wasm-intrinsics-temp-i64 (mut i64) (i64.const 0))
  (memory $0 1)
  (func $copysign64 (param $0 f64) (param $1 f64) (result f64)
@@ -377,7 +379,7 @@
                 )
                )
               )
-              (block $block
+              (block
                (br_if $label$11
                 (i32.eqz
                  (local.tee $var$3
@@ -671,7 +673,7 @@
     (block $label$13
      (if
       (local.get $var$2)
-      (block $block3
+      (block
        (local.set $var$8
         (i64.add
          (local.get $var$1)
@@ -831,7 +833,7 @@
      (f32.const 0.5)
     )
    )
-   (block $block
+   (block
     (local.set $var$0
      (f32.ceil
       (local.get $var$0)
@@ -889,7 +891,7 @@
      (f64.const 0.5)
     )
    )
-   (block $block
+   (block
     (local.set $var$0
      (f64.ceil
       (local.get $var$0)
diff --git a/test/passes/remove-unused-brs_all-features.txt b/test/passes/remove-unused-brs_all-features.txt
index abfcc3e..ab3ead0 100644
--- a/test/passes/remove-unused-brs_all-features.txt
+++ b/test/passes/remove-unused-brs_all-features.txt
@@ -13,14 +13,12 @@
  (func $foo (result (ref null $struct))
   (if (result (ref null $struct))
    (i32.const 1)
-   (struct.new_with_rtt $struct
-    (array.new_default_with_rtt $vector
+   (struct.new $struct
+    (array.new_default $vector
      (i32.const 1)
-     (rtt.canon $vector)
     )
-    (rtt.canon $struct)
    )
-   (ref.null $struct)
+   (ref.null none)
   )
  )
  (func $test-prefinalize (result f64)
@@ -88,18 +86,16 @@
    (block $data (result (ref $vector))
     (drop
      (br $data
-      (array.new_default_with_rtt $vector
+      (array.new_default $vector
        (i32.const 1)
-       (rtt.canon $vector)
       )
      )
     )
     (call $log
      (i32.const 5)
     )
-    (array.new_default_with_rtt $vector
+    (array.new_default $vector
      (i32.const 2)
-     (rtt.canon $vector)
     )
    )
   )
@@ -107,7 +103,7 @@
    (i32.const 6)
   )
   (drop
-   (block $i31 (result i31ref)
+   (block $i31 (result (ref i31))
     (drop
      (br $i31
       (i31.new
@@ -140,61 +136,59 @@
  )
  (func $br_on-to-flow
   (drop
-   (block $data (result (ref null data))
+   (block $data (result nullref)
     (drop
      (ref.func $br_on-to-flow)
     )
-    (ref.null data)
+    (ref.null none)
    )
   )
   (drop
-   (block $datab (result (ref null data))
+   (block $datab (result nullref)
     (drop
      (i31.new
       (i32.const 1337)
      )
     )
-    (ref.null data)
+    (ref.null none)
    )
   )
   (drop
-   (block $func (result funcref)
+   (block $func (result nullfuncref)
     (drop
-     (array.new_default_with_rtt $vector
+     (array.new_default $vector
       (i32.const 2)
-      (rtt.canon $vector)
      )
     )
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
-   (block $funcb (result funcref)
+   (block $funcb (result nullfuncref)
     (drop
      (i31.new
       (i32.const 1337)
      )
     )
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
-   (block $i31 (result (ref null i31))
+   (block $i31 (result nullref)
     (drop
-     (array.new_default_with_rtt $vector
+     (array.new_default $vector
       (i32.const 2)
-      (rtt.canon $vector)
      )
     )
-    (ref.null i31)
+    (ref.null none)
    )
   )
   (drop
-   (block $i31b (result (ref null i31))
+   (block $i31b (result nullref)
     (drop
      (ref.func $br_on-to-flow)
     )
-    (ref.null i31)
+    (ref.null none)
    )
   )
  )
diff --git a/test/passes/remove-unused-brs_all-features.wast b/test/passes/remove-unused-brs_all-features.wast
index 9614f7f..56a6ab9 100644
--- a/test/passes/remove-unused-brs_all-features.wast
+++ b/test/passes/remove-unused-brs_all-features.wast
@@ -5,14 +5,12 @@
  (func $foo (result (ref null $struct))
   (if (result (ref null $struct))
    (i32.const 1)
-   (struct.new_with_rtt $struct
+   (struct.new $struct
     ;; regression test for computing the cost of an array.new_default, which
     ;; lacks the optional field "init"
-    (array.new_default_with_rtt $vector
+    (array.new_default $vector
      (i32.const 1)
-     (rtt.canon $vector)
     )
-    (rtt.canon $struct)
    )
    (ref.null $struct)
   )
@@ -81,16 +79,14 @@
     ;; a non-null data reference means we always take the br
     (drop
      (br_on_data $data
-      (array.new_default_with_rtt $vector
+      (array.new_default $vector
        (i32.const 1)
-       (rtt.canon $vector)
       )
      )
     )
     (call $log (i32.const 5))
-    (array.new_default_with_rtt $vector
+    (array.new_default $vector
      (i32.const 2)
-     (rtt.canon $vector)
     )
    )
   )
@@ -147,9 +143,8 @@
    (block $func (result (ref null func))
     (drop
      (br_on_func $func
-      (array.new_default_with_rtt $vector
+      (array.new_default $vector
        (i32.const 2)
-       (rtt.canon $vector)
       )
      )
     )
@@ -171,9 +166,8 @@
    (block $i31 (result (ref null i31))
     (drop
      (br_on_i31 $i31
-      (array.new_default_with_rtt $vector
+      (array.new_default $vector
        (i32.const 2)
-       (rtt.canon $vector)
       )
      )
     )
diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt
index cf8ad7e..d318029 100644
--- a/test/passes/remove-unused-brs_enable-multivalue.txt
+++ b/test/passes/remove-unused-brs_enable-multivalue.txt
@@ -19,7 +19,7 @@
  )
  (func $b1 (param $i1 i32)
   (block $topmost
-   (block $block
+   (block
     (drop
      (i32.const 0)
     )
@@ -41,7 +41,7 @@
  (func $b4 (param $i1 i32)
   (block $topmost
    (block $inner
-    (block $block
+    (block
      (drop
       (i32.const 0)
      )
@@ -52,7 +52,7 @@
  (func $b5 (param $i1 i32)
   (block $topmost
    (block $inner
-    (block $block
+    (block
      (drop
       (i32.const 0)
      )
@@ -69,7 +69,7 @@
  )
  (func $b7 (param $i1 i32)
   (block $topmost
-   (block $block
+   (block
     (drop
      (i32.const 0)
     )
@@ -100,7 +100,7 @@
  (func $b10 (param $i1 i32)
   (block $topmost
    (block $inner
-    (block $block
+    (block
      (drop
       (i32.const 0)
      )
@@ -114,7 +114,7 @@
  (func $b11 (param $i1 i32)
   (block $topmost
    (block $inner
-    (block $block
+    (block
      (drop
       (i32.const 0)
      )
@@ -133,7 +133,7 @@
      (drop
       (i32.const 12)
      )
-     (block $block
+     (block
       (drop
        (i32.const 1)
       )
@@ -144,7 +144,7 @@
     (drop
      (i32.const 27)
     )
-    (block $block0
+    (block
      (drop
       (i32.const 2)
      )
@@ -222,7 +222,7 @@
   (if
    (i32.const 18)
    (block $topmost
-    (block $block
+    (block
      (drop
       (i32.const 0)
      )
@@ -325,7 +325,7 @@
      (i32.const 1)
     )
     (block $block2
-     (block $block
+     (block
       (drop
        (i32.const 2)
       )
@@ -339,7 +339,7 @@
    (if
     (i32.const 0)
     (block $block4
-     (block $block13
+     (block
       (drop
        (i32.const 2)
       )
@@ -355,7 +355,7 @@
    )
    (if
     (block $block6 (result i32)
-     (block $block15
+     (block
       (drop
        (i32.const 2)
       )
@@ -375,14 +375,14 @@
      (i32.const 0)
     )
     (block $a18
-     (block $block19
+     (block
       (drop
        (i32.const 1)
       )
      )
     )
     (block $a20
-     (block $block21
+     (block
       (drop
        (i32.const 2)
       )
@@ -396,7 +396,7 @@
   (block $do-once$0
    (if
     (call $b13)
-    (block $block
+    (block
      (drop
       (i32.const 0)
      )
@@ -410,7 +410,7 @@
   (block $do-once$022
    (if
     (call $b13)
-    (block $block24
+    (block
      (drop
       (call $b14)
      )
@@ -424,7 +424,7 @@
   (block $do-once$025
    (if
     (i32.const 0)
-    (block $block27
+    (block
      (drop
       (call $b14)
      )
@@ -516,7 +516,7 @@
    (if
     (i32.const 0)
     (block $out49
-     (block $block
+     (block
       (call $loops)
      )
     )
@@ -560,12 +560,10 @@
    (if
     (i32.const 0)
     (block
-     (block $block61
-      (drop
-       (i32.const 1)
-      )
-      (call $loops)
+     (drop
+      (i32.const 1)
      )
+     (call $loops)
      (br $in58)
     )
     (block $out59
@@ -604,12 +602,10 @@
    (if
     (i32.const 0)
     (block
-     (block $block71
-      (drop
-       (i32.const 1)
-      )
-      (call $loops)
+     (drop
+      (i32.const 1)
      )
+     (call $loops)
      (drop
       (i32.const 102)
      )
@@ -794,7 +790,7 @@
      (if
       (local.get $x)
       (br $out
-       (block $block (result i32)
+       (block (result i32)
         (local.set $x
          (i32.const 0)
         )
@@ -824,7 +820,7 @@
        (i32.const 1)
       )
       (br $out
-       (block $block (result i32)
+       (block (result i32)
         (local.set $x
          (i32.const 0)
         )
@@ -848,7 +844,7 @@
      (if
       (local.get $x)
       (br $out
-       (block $block (result i32)
+       (block (result i32)
         (drop
          (call $if-to-br_if-value-sideeffect
           (i32.const 0)
@@ -963,7 +959,7 @@
        (i32.const 2)
       )
      )
-     (block $block (result i32)
+     (block (result i32)
       (drop
        (call $loop-if)
       )
@@ -992,7 +988,6 @@
      (br_if $shape$6$continue
       (local.get $0)
      )
-     (nop)
     )
    )
   )
@@ -1785,7 +1780,6 @@
     (drop
      (i32.const 0)
     )
-    (nop)
    )
   )
  )
@@ -2120,7 +2114,7 @@
   (if
    (i32.const 1)
    (block $label
-    (block $block
+    (block
      (drop
       (i32.const 2)
      )
@@ -2135,7 +2129,7 @@
   (block $label
    (if
     (br $label)
-    (block $block
+    (block
      (drop
       (i32.const 2)
      )
@@ -2234,7 +2228,7 @@
   (if
    (i32.const 1)
    (nop)
-   (block $block
+   (block
     (drop
      (loop $label$3 (result i64)
       (br_if $label$3
@@ -2320,7 +2314,7 @@
     (if
      (i32.const 0)
      (block $label$3
-      (block $block
+      (block
       )
      )
      (return
@@ -2604,17 +2598,15 @@
   )
  )
  (func $do-not-flow-values-through-unreachable-code (result i32)
-  (block $block
-   (unreachable)
-   (if
-    (i32.const 0)
-    (block $A
-     (return
-      (i32.const 0)
-     )
+  (unreachable)
+  (if
+   (i32.const 0)
+   (block $A
+    (return
+     (i32.const 0)
     )
-    (nop)
    )
+   (nop)
   )
  )
  (func $do-not-flow-values-through-unreachable-code-b (result i32)
@@ -2692,7 +2684,7 @@
      )
      (i32.const 3)
     )
-    (block $block (result i32)
+    (block (result i32)
      (if
       (local.get $x)
       (return
diff --git a/test/passes/remove-unused-brs_shrink-level=1.txt b/test/passes/remove-unused-brs_shrink-level=1.txt
index 22f70ff..c32b64d 100644
--- a/test/passes/remove-unused-brs_shrink-level=1.txt
+++ b/test/passes/remove-unused-brs_shrink-level=1.txt
@@ -124,7 +124,7 @@
  )
  (func $join-and-it-becomes-unreachable
   (block $label$1
-   (block $block
+   (block
     (br_if $label$1
      (i32.load8_u
       (i32.const -93487262)
diff --git a/test/passes/remove-unused-module-elements_all-features.txt b/test/passes/remove-unused-module-elements_all-features.txt
index c2806d6..ecf3031 100644
--- a/test/passes/remove-unused-module-elements_all-features.txt
+++ b/test/passes/remove-unused-module-elements_all-features.txt
@@ -104,8 +104,8 @@
 (module
  (type $none_=>_none (func))
  (import "env" "memory" (memory $0 256))
- (data (i32.const 1) "hello, world!")
  (import "env" "table" (table $timport$0 1 funcref))
+ (data (i32.const 1) "hello, world!")
  (elem (i32.const 0) $waka)
  (func $waka
   (nop)
@@ -219,10 +219,10 @@
 (module
  (type $none_=>_none (func))
  (import "env" "memory" (memory $0 256))
- (data (global.get $memoryBase) "hello, world!")
  (import "env" "table" (table $timport$0 0 funcref))
  (import "env" "memoryBase" (global $memoryBase i32))
  (import "env" "tableBase" (global $tableBase i32))
+ (data (global.get $memoryBase) "hello, world!")
  (elem (global.get $tableBase) $waka)
  (func $waka
   (nop)
diff --git a/test/passes/remove-unused-names_remove-unused-brs_vacuum.txt b/test/passes/remove-unused-names_remove-unused-brs_vacuum.txt
index 83be83c..ca85639 100644
--- a/test/passes/remove-unused-names_remove-unused-brs_vacuum.txt
+++ b/test/passes/remove-unused-names_remove-unused-brs_vacuum.txt
@@ -71,8 +71,12 @@
    (block $label$3
     (block
      (if
-      (i32.eqz
-       (local.get $var$8)
+      (local.get $var$8)
+      (loop $label$8
+       (if
+        (local.get $var$3)
+        (br $label$8)
+       )
       )
       (if
        (i32.eqz
diff --git a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt
index a767fcc..28391b5 100644
--- a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt
+++ b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt
@@ -107,8 +107,8 @@
 (module
  (type $none_=>_none (func))
  (import "env" "memory" (memory $0 256))
- (data (i32.const 1) "hello, world!")
  (import "env" "table" (table $timport$0 1 funcref))
+ (data (i32.const 1) "hello, world!")
  (elem (i32.const 0) $waka)
  (func $waka
   (nop)
@@ -222,10 +222,10 @@
 (module
  (type $none_=>_none (func))
  (import "env" "memory" (memory $0 256))
- (data (global.get $memoryBase) "hello, world!")
  (import "env" "table" (table $timport$0 0 funcref))
  (import "env" "memoryBase" (global $memoryBase i32))
  (import "env" "tableBase" (global $tableBase i32))
+ (data (global.get $memoryBase) "hello, world!")
  (elem (global.get $tableBase) $waka)
  (func $waka
   (nop)
diff --git a/test/passes/reverse_dwarf_abbrevs.bin.txt b/test/passes/reverse_dwarf_abbrevs.bin.txt
index 1bdfef2..a9874aa 100644
--- a/test/passes/reverse_dwarf_abbrevs.bin.txt
+++ b/test/passes/reverse_dwarf_abbrevs.bin.txt
@@ -121,18 +121,18 @@ file_names[  1]:
  (type $i32_i32_i64_i32_=>_i64 (func (param i32 i32 i64 i32) (result i64)))
  (type $i32_i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32 i32) (result i32)))
  (import "env" "memory" (memory $mimport$0 256 256))
- (data (i32.const 1024) "hello, world!\00\00\00\18\04")
- (data (i32.const 1048) "\05")
- (data (i32.const 1060) "\01")
- (data (i32.const 1084) "\02\00\00\00\03\00\00\00\c8\04\00\00\00\04")
- (data (i32.const 1108) "\01")
- (data (i32.const 1123) "\n\ff\ff\ff\ff")
  (import "env" "__indirect_function_table" (table $timport$0 4 funcref))
  (import "wasi_snapshot_preview1" "fd_write" (func $fimport$0 (param i32 i32 i32 i32) (result i32)))
  (import "env" "emscripten_memcpy_big" (func $fimport$1 (param i32 i32 i32) (result i32)))
  (import "env" "setTempRet0" (func $fimport$2 (param i32)))
  (global $global$0 (mut i32) (i32.const 5245136))
  (global $global$1 i32 (i32.const 2248))
+ (data (i32.const 1024) "hello, world!\00\00\00\18\04")
+ (data (i32.const 1048) "\05")
+ (data (i32.const 1060) "\01")
+ (data (i32.const 1084) "\02\00\00\00\03\00\00\00\c8\04\00\00\00\04")
+ (data (i32.const 1108) "\01")
+ (data (i32.const 1123) "\n\ff\ff\ff\ff")
  (elem (i32.const 1) $6 $5 $7)
  (export "__wasm_call_ctors" (func $0))
  (export "main" (func $2))
diff --git a/test/passes/roundtrip_typenames_features.txt b/test/passes/roundtrip_typenames_features.txt
index 852dea7..2def3c2 100644
--- a/test/passes/roundtrip_typenames_features.txt
+++ b/test/passes/roundtrip_typenames_features.txt
@@ -5,5 +5,5 @@
  (func $0 (param $0 (ref null $NamedStruct))
   (nop)
  )
- ;; features section: reference-types, gc
+ ;; features section: mutable-globals, sign-ext, reference-types, gc
 )
diff --git a/test/passes/rse_all-features.txt b/test/passes/rse_all-features.txt
index 8ad883d..6a4c182 100644
--- a/test/passes/rse_all-features.txt
+++ b/test/passes/rse_all-features.txt
@@ -399,7 +399,7 @@
    (block $label$5
     (if
      (i32.const 1)
-     (block $block
+     (block
       (local.set $x
        (i32.const 203)
       )
diff --git a/test/passes/simplify-globals_all-features.txt b/test/passes/simplify-globals_all-features.txt
index 5ad54b0..d94ac55 100644
--- a/test/passes/simplify-globals_all-features.txt
+++ b/test/passes/simplify-globals_all-features.txt
@@ -213,9 +213,9 @@
 )
 (module
  (type $none_=>_none (func))
- (import "env" "global-1" (global $g1 anyref))
- (global $g2 anyref (global.get $g1))
- (global $g3 anyref (ref.null any))
+ (import "env" "global-1" (global $g1 externref))
+ (global $g2 externref (global.get $g1))
+ (global $g3 externref (ref.null noextern))
  (func $test1
   (drop
    (global.get $g1)
@@ -226,7 +226,7 @@
  )
  (func $test2
   (drop
-   (ref.null any)
+   (ref.null noextern)
   )
  )
 )
diff --git a/test/passes/simplify-globals_all-features_fuzz-exec.txt b/test/passes/simplify-globals_all-features_fuzz-exec.txt
index d38d067..f4e2df4 100644
--- a/test/passes/simplify-globals_all-features_fuzz-exec.txt
+++ b/test/passes/simplify-globals_all-features_fuzz-exec.txt
@@ -3,7 +3,7 @@
 (module
  (type $f32_i31ref_i64_f64_funcref_=>_none (func (param f32 i31ref i64 f64 funcref)))
  (type $none_=>_funcref (func (result funcref)))
- (global $global$0 (mut funcref) (ref.null func))
+ (global $global$0 (mut funcref) (ref.null nofunc))
  (elem declare func $0)
  (export "export" (func $1))
  (func $0 (param $0 f32) (param $1 i31ref) (param $2 i64) (param $3 f64) (param $4 funcref)
diff --git a/test/passes/simplify-locals-nonesting.txt b/test/passes/simplify-locals-nonesting.txt
index 8cae72b..abc3aec 100644
--- a/test/passes/simplify-locals-nonesting.txt
+++ b/test/passes/simplify-locals-nonesting.txt
@@ -17,7 +17,7 @@
   (local $15 i32)
   (local $16 i32)
   (local $17 i32)
-  (block $block
+  (block
    (nop)
    (nop)
    (nop)
@@ -76,7 +76,7 @@
   (local $16 i32)
   (local $17 i32)
   (local $18 i32)
-  (block $block
+  (block
    (nop)
    (nop)
    (local.set $8
@@ -87,7 +87,7 @@
    )
    (if
     (local.get $8)
-    (block $block0
+    (block
      (block $block1
       (nop)
       (nop)
@@ -127,7 +127,7 @@
      )
      (unreachable)
     )
-    (block $block2
+    (block
      (unreachable)
      (unreachable)
     )
@@ -146,8 +146,8 @@
   (local $8 i32)
   (local $9 i32)
   (local $10 i32)
-  (block $block
-   (block $block3
+  (block
+   (block
     (nop)
     (local.set $2
      (i32.and
@@ -157,7 +157,7 @@
     )
     (if
      (local.get $2)
-     (block $block4
+     (block
       (nop)
       (nop)
       (local.set $x
@@ -168,7 +168,7 @@
       )
       (nop)
      )
-     (block $block5
+     (block
       (nop)
       (nop)
       (local.set $x
diff --git a/test/passes/simplify-locals-nostructure.txt b/test/passes/simplify-locals-nostructure.txt
index 2293f11..ef262f8 100644
--- a/test/passes/simplify-locals-nostructure.txt
+++ b/test/passes/simplify-locals-nostructure.txt
@@ -30,7 +30,7 @@
   )
   (nop)
   (drop
-   (block $block (result i32)
+   (block (result i32)
     (i32.const 5)
    )
   )
@@ -49,7 +49,7 @@
   (block $val
    (if
     (i32.const 10)
-    (block $block4
+    (block
      (local.set $b
       (i32.const 11)
      )
@@ -122,7 +122,7 @@
    (local.set $x
     (i32.const 2)
    )
-   (block $block
+   (block
     (nop)
     (nop)
    )
diff --git a/test/passes/simplify-locals-notee-nostructure.txt b/test/passes/simplify-locals-notee-nostructure.txt
index 5d545a7..b6ad340 100644
--- a/test/passes/simplify-locals-notee-nostructure.txt
+++ b/test/passes/simplify-locals-notee-nostructure.txt
@@ -27,7 +27,7 @@
   )
   (nop)
   (drop
-   (block $block (result i32)
+   (block (result i32)
     (i32.const 5)
    )
   )
@@ -46,7 +46,7 @@
   (block $val
    (if
     (i32.const 10)
-    (block $block4
+    (block
      (local.set $b
       (i32.const 11)
      )
diff --git a/test/passes/simplify-locals-notee.txt b/test/passes/simplify-locals-notee.txt
index 4642b97..57ef29f 100644
--- a/test/passes/simplify-locals-notee.txt
+++ b/test/passes/simplify-locals-notee.txt
@@ -27,7 +27,7 @@
   )
   (nop)
   (drop
-   (block $block (result i32)
+   (block (result i32)
     (i32.const 5)
    )
   )
@@ -50,7 +50,7 @@
    (block $val (result i32)
     (if
      (i32.const 10)
-     (block $block4
+     (block
       (nop)
       (br $val
        (i32.const 11)
diff --git a/test/passes/simplify-locals.txt b/test/passes/simplify-locals.txt
index faed220..4f2f30c 100644
--- a/test/passes/simplify-locals.txt
+++ b/test/passes/simplify-locals.txt
@@ -11,7 +11,7 @@
     (i32.const 1)
     (i32.const 1)
    )
-   (block $block (result i32)
+   (block (result i32)
     (nop)
     (nop)
     (nop)
diff --git a/test/passes/simplify-locals_all-features.txt b/test/passes/simplify-locals_all-features.txt
index 0c25ea5..fb51e2f 100644
--- a/test/passes/simplify-locals_all-features.txt
+++ b/test/passes/simplify-locals_all-features.txt
@@ -47,7 +47,7 @@
   )
   (nop)
   (drop
-   (block $block (result i32)
+   (block (result i32)
     (i32.const 5)
    )
   )
@@ -70,7 +70,7 @@
    (block $val (result i32)
     (if
      (i32.const 10)
-     (block $block4
+     (block
       (nop)
       (br $val
        (i32.const 11)
@@ -293,8 +293,8 @@
    (call $waka)
    (nop)
    (local.set $a
-    (block $block (result i32)
-     (block $block5
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 104)
@@ -308,8 +308,8 @@
    )
    (call $waka)
    (local.set $a
-    (block $block6 (result i32)
-     (block $block7
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 106)
@@ -327,8 +327,8 @@
    )
    (call $waka)
    (local.set $a
-    (block $block8 (result i32)
-     (block $block9
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 108)
@@ -350,8 +350,8 @@
    )
    (call $waka)
    (local.set $a
-    (block $block10 (result i32)
-     (block $block11
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 110)
@@ -765,7 +765,7 @@
   (local.set $x
    (loop $moar (result i32)
     (nop)
-    (block $block (result i32)
+    (block (result i32)
      (br_if $moar
       (local.get $x)
      )
@@ -775,7 +775,7 @@
   )
   (block $moar18
    (local.set $y
-    (block $block19 (result i32)
+    (block (result i32)
      (br_if $moar18
       (local.get $y)
      )
@@ -1098,7 +1098,7 @@
    )
    (if (result f64)
     (global.get $global$0)
-    (block $block
+    (block
      (global.set $global$0
       (i32.sub
        (global.get $global$0)
@@ -1269,7 +1269,7 @@
   (block $outside
    (loop $loop
     (br_if $outside
-     (block $block (result i32)
+     (block (result i32)
       (br_if $loop
        (local.get $temp)
       )
@@ -1300,7 +1300,7 @@
        (local.tee $temp
         (i32.const -1)
        )
-       (block $block (result i32)
+       (block (result i32)
         (nop)
         (i32.const 0)
        )
@@ -1357,18 +1357,14 @@
     (if (result i32)
      (i32.const 1)
      (block
-      (block $block
-       (nop)
-       (nop)
-       (br $out)
-      )
+      (nop)
+      (nop)
+      (br $out)
       (nop)
      )
      (block (result i32)
-      (block $block2
-       (nop)
-       (nop)
-      )
+      (nop)
+      (nop)
       (i32.const 4)
      )
     )
@@ -1377,30 +1373,26 @@
     (if (result i32)
      (i32.const 6)
      (block (result i32)
-      (block $block4
-       (nop)
-       (nop)
-      )
+      (nop)
+      (nop)
       (i32.const 7)
      )
      (block
-      (block $block5
-       (nop)
-       (nop)
-       (br $out)
-      )
+      (nop)
+      (nop)
+      (br $out)
       (nop)
      )
     )
    )
    (if
     (i32.const 11)
-    (block $block7
+    (block
      (nop)
      (nop)
      (br $out)
     )
-    (block $block8
+    (block
      (nop)
      (nop)
      (br $out)
@@ -1616,10 +1608,8 @@
      (i32.const 2)
     )
     (block (result i32)
-     (block $block
-      (nop)
-      (nop)
-     )
+     (nop)
+     (nop)
      (local.get $x)
     )
    )
@@ -1675,12 +1665,10 @@
        (f32.const -2048)
       )
       (block
-       (block $block
-        (call $fimport$1
-         (i32.const -25732)
-        )
-        (br $label$2)
+       (call $fimport$1
+        (i32.const -25732)
        )
+       (br $label$2)
        (nop)
       )
      )
@@ -1746,7 +1734,7 @@
  (func $memory-init-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1782,7 +1770,7 @@
  (func $memory-copy-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1818,7 +1806,7 @@
  (func $memory-fill-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1850,7 +1838,7 @@
  (func $data-drop-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1866,7 +1854,7 @@
  (func $data-drop-memory-init
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (memory.init 0
      (i32.const 0)
      (i32.const 0)
@@ -1884,10 +1872,10 @@
 (module
  (type $none_=>_anyref (func (result anyref)))
  (func $subtype-test (result anyref)
-  (local $0 anyref)
+  (local $0 eqref)
   (local $1 anyref)
   (local $2 anyref)
-  (block $block
+  (block
    (nop)
   )
   (nop)
@@ -1901,24 +1889,22 @@
  (export "foo" (func $0))
  (func $0 (result i32)
   (local $0 i32)
-  (block $block (result i32)
-   (local.set $0
-    (i32.rem_u
-     (i32.const 0)
-     (i32.const 0)
-    )
+  (local.set $0
+   (i32.rem_u
+    (i32.const 0)
+    (i32.const 0)
    )
-   (data.drop 0)
-   (local.get $0)
   )
+  (data.drop 0)
+  (local.get $0)
  )
 )
 (module
- (type $eqref_ref?|i31|_=>_i32 (func (param eqref (ref null i31)) (result i32)))
+ (type $eqref_i31ref_=>_i32 (func (param eqref i31ref) (result i32)))
  (export "test" (func $0))
- (func $0 (param $0 eqref) (param $1 (ref null i31)) (result i32)
+ (func $0 (param $0 eqref) (param $1 i31ref) (result i32)
   (local $2 eqref)
-  (local $3 (ref null i31))
+  (local $3 i31ref)
   (local.set $2
    (local.get $0)
   )
diff --git a/test/passes/simplify-locals_all-features.wast b/test/passes/simplify-locals_all-features.wast
index 7d153ef..ff70a27 100644
--- a/test/passes/simplify-locals_all-features.wast
+++ b/test/passes/simplify-locals_all-features.wast
@@ -1657,7 +1657,7 @@
 )
 (module
  (func $subtype-test (result anyref)
-  (local $0 externref)
+  (local $0 eqref)
   (local $1 anyref)
   (local $2 anyref)
   (block
diff --git a/test/passes/simplify-locals_all-features_disable-exception-handling.txt b/test/passes/simplify-locals_all-features_disable-exception-handling.txt
index 2f8825c..78e2804 100644
--- a/test/passes/simplify-locals_all-features_disable-exception-handling.txt
+++ b/test/passes/simplify-locals_all-features_disable-exception-handling.txt
@@ -47,7 +47,7 @@
   )
   (nop)
   (drop
-   (block $block (result i32)
+   (block (result i32)
     (i32.const 5)
    )
   )
@@ -70,7 +70,7 @@
    (block $val (result i32)
     (if
      (i32.const 10)
-     (block $block4
+     (block
       (nop)
       (br $val
        (i32.const 11)
@@ -287,8 +287,8 @@
    (call $waka)
    (nop)
    (local.set $a
-    (block $block (result i32)
-     (block $block5
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 104)
@@ -302,8 +302,8 @@
    )
    (call $waka)
    (local.set $a
-    (block $block6 (result i32)
-     (block $block7
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 106)
@@ -321,8 +321,8 @@
    )
    (call $waka)
    (local.set $a
-    (block $block8 (result i32)
-     (block $block9
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 108)
@@ -344,8 +344,8 @@
    )
    (call $waka)
    (local.set $a
-    (block $block10 (result i32)
-     (block $block11
+    (block (result i32)
+     (block
       (nop)
       (i32.store
        (i32.const 110)
@@ -759,7 +759,7 @@
   (local.set $x
    (loop $moar (result i32)
     (nop)
-    (block $block (result i32)
+    (block (result i32)
      (br_if $moar
       (local.get $x)
      )
@@ -769,7 +769,7 @@
   )
   (block $moar18
    (local.set $y
-    (block $block19 (result i32)
+    (block (result i32)
      (br_if $moar18
       (local.get $y)
      )
@@ -1092,7 +1092,7 @@
    )
    (if (result f64)
     (global.get $global$0)
-    (block $block
+    (block
      (global.set $global$0
       (i32.sub
        (global.get $global$0)
@@ -1263,7 +1263,7 @@
   (block $outside
    (loop $loop
     (br_if $outside
-     (block $block (result i32)
+     (block (result i32)
       (br_if $loop
        (local.get $temp)
       )
@@ -1294,7 +1294,7 @@
        (local.tee $temp
         (i32.const -1)
        )
-       (block $block (result i32)
+       (block (result i32)
         (nop)
         (i32.const 0)
        )
@@ -1351,18 +1351,14 @@
     (if (result i32)
      (i32.const 1)
      (block
-      (block $block
-       (nop)
-       (nop)
-       (br $out)
-      )
+      (nop)
+      (nop)
+      (br $out)
       (nop)
      )
      (block (result i32)
-      (block $block2
-       (nop)
-       (nop)
-      )
+      (nop)
+      (nop)
       (i32.const 4)
      )
     )
@@ -1371,30 +1367,26 @@
     (if (result i32)
      (i32.const 6)
      (block (result i32)
-      (block $block4
-       (nop)
-       (nop)
-      )
+      (nop)
+      (nop)
       (i32.const 7)
      )
      (block
-      (block $block5
-       (nop)
-       (nop)
-       (br $out)
-      )
+      (nop)
+      (nop)
+      (br $out)
       (nop)
      )
     )
    )
    (if
     (i32.const 11)
-    (block $block7
+    (block
      (nop)
      (nop)
      (br $out)
     )
-    (block $block8
+    (block
      (nop)
      (nop)
      (br $out)
@@ -1610,10 +1602,8 @@
      (i32.const 2)
     )
     (block (result i32)
-     (block $block
-      (nop)
-      (nop)
-     )
+     (nop)
+     (nop)
      (local.get $x)
     )
    )
@@ -1669,12 +1659,10 @@
        (f32.const -2048)
       )
       (block
-       (block $block
-        (call $fimport$1
-         (i32.const -25732)
-        )
-        (br $label$2)
+       (call $fimport$1
+        (i32.const -25732)
        )
+       (br $label$2)
        (nop)
       )
      )
@@ -1740,7 +1728,7 @@
  (func $memory-init-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1776,7 +1764,7 @@
  (func $memory-copy-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1812,7 +1800,7 @@
  (func $memory-fill-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1844,7 +1832,7 @@
  (func $data-drop-store
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (i32.store
      (i32.const 0)
      (i32.const 42)
@@ -1860,7 +1848,7 @@
  (func $data-drop-memory-init
   (local $x i32)
   (local.set $x
-   (block $block (result i32)
+   (block (result i32)
     (memory.init 0
      (i32.const 0)
      (i32.const 0)
@@ -1878,10 +1866,10 @@
 (module
  (type $none_=>_anyref (func (result anyref)))
  (func $subtype-test (result anyref)
-  (local $0 funcref)
+  (local $0 i31ref)
   (local $1 anyref)
   (local $2 anyref)
-  (block $block
+  (block
    (nop)
   )
   (nop)
diff --git a/test/passes/simplify-locals_all-features_disable-exception-handling.wast b/test/passes/simplify-locals_all-features_disable-exception-handling.wast
index 803d557..25b4525 100644
--- a/test/passes/simplify-locals_all-features_disable-exception-handling.wast
+++ b/test/passes/simplify-locals_all-features_disable-exception-handling.wast
@@ -1657,7 +1657,7 @@
 )
 (module
  (func $subtype-test (result anyref)
-  (local $0 funcref)
+  (local $0 (ref null i31))
   (local $1 anyref)
   (local $2 anyref)
   (block
diff --git a/test/passes/sparse_matrix_liveness.bin.txt b/test/passes/sparse_matrix_liveness.bin.txt
index b55f102..d669ee1 100644
--- a/test/passes/sparse_matrix_liveness.bin.txt
+++ b/test/passes/sparse_matrix_liveness.bin.txt
@@ -3,6 +3,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 4       
@@ -16,6 +17,7 @@ total
  [funcs]        : 1       
  [globals]      : 0       
  [imports]      : 0       
+ [memories]     : 0       
  [tables]       : 0       
  [tags]         : 0       
  [total]        : 4       
diff --git a/test/passes/spill-pointers.txt b/test/passes/spill-pointers.txt
new file mode 100644
index 0000000..dcd8b2c
--- /dev/null
+++ b/test/passes/spill-pointers.txt
@@ -0,0 +1,1293 @@
+(module
+ (type $none_=>_none (func))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $none_=>_i32 (func (result i32)))
+ (type $ii (func (param i32 i32)))
+ (type $i32_=>_none (func (param i32)))
+ (type $f64_=>_none (func (param f64)))
+ (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
+ (import "env" "segfault" (func $segfault (param i32)))
+ (global $stack_ptr (mut i32) (global.get $STACKTOP$asm2wasm$import))
+ (memory $0 10)
+ (table $0 1 1 funcref)
+ (elem (i32.const 0))
+ (func $nothing
+  (nop)
+ )
+ (func $not-alive
+  (local $x i32)
+  (local.set $x
+   (i32.const 1)
+  )
+  (call $nothing)
+ )
+ (func $spill
+  (local $x i32)
+  (local $1 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (i32.store
+     (local.get $1)
+     (local.get $x)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+ )
+ (func $ignore-non-pointers
+  (local $x i32)
+  (local $y i64)
+  (local $z f32)
+  (local $w f64)
+  (local $4 i32)
+  (local.set $4
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $4)
+    (i32.const 16)
+   )
+  )
+  (block
+   (local.set $x
+    (i32.const 1)
+   )
+   (local.set $y
+    (i64.const 1)
+   )
+   (local.set $z
+    (f32.const 1)
+   )
+   (local.set $w
+    (f64.const 1)
+   )
+   (block
+    (i32.store
+     (local.get $4)
+     (local.get $x)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+   (drop
+    (local.get $y)
+   )
+   (drop
+    (local.get $z)
+   )
+   (drop
+    (local.get $w)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $4)
+  )
+ )
+ (func $spill4
+  (local $x i32)
+  (local $y i32)
+  (local $z i32)
+  (local $w i32)
+  (local $4 i32)
+  (local.set $4
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $4)
+    (i32.const 16)
+   )
+  )
+  (block
+   (local.set $x
+    (i32.const 1)
+   )
+   (local.set $y
+    (i32.const 1)
+   )
+   (local.set $z
+    (i32.const 1)
+   )
+   (local.set $w
+    (i32.const 1)
+   )
+   (block
+    (i32.store
+     (local.get $4)
+     (local.get $x)
+    )
+    (i32.store offset=4
+     (local.get $4)
+     (local.get $y)
+    )
+    (i32.store offset=8
+     (local.get $4)
+     (local.get $z)
+    )
+    (i32.store offset=12
+     (local.get $4)
+     (local.get $w)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+   (drop
+    (local.get $y)
+   )
+   (drop
+    (local.get $z)
+   )
+   (drop
+    (local.get $w)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $4)
+  )
+ )
+ (func $spill5
+  (local $x i32)
+  (local $y i32)
+  (local $z i32)
+  (local $w i32)
+  (local $a i32)
+  (local $5 i32)
+  (local.set $5
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $5)
+    (i32.const 32)
+   )
+  )
+  (block
+   (local.set $x
+    (i32.const 1)
+   )
+   (local.set $y
+    (i32.const 1)
+   )
+   (local.set $z
+    (i32.const 1)
+   )
+   (local.set $w
+    (i32.const 1)
+   )
+   (local.set $a
+    (i32.const 1)
+   )
+   (block
+    (i32.store
+     (local.get $5)
+     (local.get $x)
+    )
+    (i32.store offset=4
+     (local.get $5)
+     (local.get $y)
+    )
+    (i32.store offset=8
+     (local.get $5)
+     (local.get $z)
+    )
+    (i32.store offset=12
+     (local.get $5)
+     (local.get $w)
+    )
+    (i32.store offset=16
+     (local.get $5)
+     (local.get $a)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+   (drop
+    (local.get $y)
+   )
+   (drop
+    (local.get $z)
+   )
+   (drop
+    (local.get $w)
+   )
+   (drop
+    (local.get $a)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $5)
+  )
+ )
+ (func $some-alive
+  (local $x i32)
+  (local $y i32)
+  (local $2 i32)
+  (local.set $2
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $2)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (i32.store
+     (local.get $2)
+     (local.get $x)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $2)
+  )
+ )
+ (func $spill-args (param $p i32) (param $q i32)
+  (local $x i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local $5 i32)
+  (local.set $3
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $3)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $4
+     (i32.const 1)
+    )
+    (local.set $5
+     (i32.const 2)
+    )
+    (i32.store offset=8
+     (local.get $3)
+     (local.get $x)
+    )
+    (call $spill-args
+     (local.get $4)
+     (local.get $5)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $3)
+  )
+ )
+ (func $spill-ret (result i32)
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (local.set $4
+   (block (result i32)
+    (block
+     (i32.store
+      (local.get $1)
+      (local.get $x)
+     )
+     (call $nothing)
+    )
+    (drop
+     (local.get $x)
+    )
+    (if
+     (i32.const 1)
+     (block
+      (local.set $2
+       (i32.const 2)
+      )
+      (global.set $stack_ptr
+       (local.get $1)
+      )
+      (return
+       (local.get $2)
+      )
+     )
+     (block
+      (local.set $3
+       (i32.const 3)
+      )
+      (global.set $stack_ptr
+       (local.get $1)
+      )
+      (return
+       (local.get $3)
+      )
+     )
+    )
+    (i32.const 4)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+  (local.get $4)
+ )
+ (func $spill-unreachable (result i32)
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (local.set $2
+   (block (result i32)
+    (block
+     (i32.store
+      (local.get $1)
+      (local.get $x)
+     )
+     (call $nothing)
+    )
+    (drop
+     (local.get $x)
+    )
+    (unreachable)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+  (local.get $2)
+ )
+ (func $spill-call-call0 (param $p i32) (result i32)
+  (unreachable)
+ )
+ (func $spill-call-call1 (param $p i32) (result i32)
+  (local $x i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local $5 i32)
+  (local.set $2
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $2)
+    (i32.const 16)
+   )
+  )
+  (local.set $5
+   (block (result i32)
+    (drop
+     (block (result i32)
+      (local.set $3
+       (block (result i32)
+        (local.set $4
+         (i32.const 1)
+        )
+        (i32.store offset=4
+         (local.get $2)
+         (local.get $x)
+        )
+        (call $spill-call-call1
+         (local.get $4)
+        )
+       )
+      )
+      (i32.store offset=4
+       (local.get $2)
+       (local.get $x)
+      )
+      (call $spill-call-call0
+       (local.get $3)
+      )
+     )
+    )
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $2)
+  )
+  (local.get $5)
+ )
+ (func $spill-call-ret (param $p i32) (result i32)
+  (local $x i32)
+  (drop
+   (call $spill-call-call0
+    (return
+     (i32.const 1)
+    )
+   )
+  )
+  (i32.const 0)
+ )
+ (func $spill-ret-call (param $p i32) (result i32)
+  (local $x i32)
+  (drop
+   (return
+    (call $spill-call-call0
+     (i32.const 1)
+    )
+   )
+  )
+  (i32.const 0)
+ )
+ (func $spill-ret-ret (result i32)
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (local.set $3
+   (block (result i32)
+    (block
+     (i32.store
+      (local.get $1)
+      (local.get $x)
+     )
+     (call $nothing)
+    )
+    (drop
+     (local.get $x)
+    )
+    (drop
+     (block
+      (global.set $stack_ptr
+       (local.get $1)
+      )
+      (return
+       (block
+        (local.set $2
+         (i32.const 1)
+        )
+        (global.set $stack_ptr
+         (local.get $1)
+        )
+        (return
+         (local.get $2)
+        )
+       )
+      )
+     )
+    )
+    (i32.const 0)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+  (local.get $3)
+ )
+ (func $spill-call-othertype (param $y f64)
+  (local $x i32)
+  (local $2 i32)
+  (local $3 f64)
+  (local.set $2
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $2)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $3
+     (f64.const 1)
+    )
+    (i32.store
+     (local.get $2)
+     (local.get $x)
+    )
+    (call $spill-call-othertype
+     (local.get $3)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $2)
+  )
+ )
+ (func $spill-call_indirect
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $2
+     (i32.const 123)
+    )
+    (local.set $3
+     (i32.const 456)
+    )
+    (local.set $4
+     (i32.const 789)
+    )
+    (i32.store
+     (local.get $1)
+     (local.get $x)
+    )
+    (call_indirect (type $ii)
+     (local.get $2)
+     (local.get $3)
+     (local.get $4)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+ )
+ (func $spill-call_import
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $2
+     (i32.const 200)
+    )
+    (i32.store
+     (local.get $1)
+     (local.get $x)
+    )
+    (call $segfault
+     (local.get $2)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+ )
+)
+(module
+ (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $ii (func (param i32 i32)))
+ (type $i32_=>_none (func (param i32)))
+ (type $f64_=>_none (func (param f64)))
+ (import "env" "segfault" (func $segfault (param i32)))
+ (global $stack_ptr (mut i32) (i32.const 1716592))
+ (memory $0 10)
+ (table $0 1 1 funcref)
+ (elem (i32.const 0))
+ (export "stackSave" (func $stack_save))
+ (func $stack_save (result i32)
+  (global.get $stack_ptr)
+ )
+ (func $nothing
+  (nop)
+ )
+ (func $not-alive
+  (local $x i32)
+  (local.set $x
+   (i32.const 1)
+  )
+  (call $nothing)
+ )
+ (func $spill
+  (local $x i32)
+  (local $1 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (i32.store
+     (local.get $1)
+     (local.get $x)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+ )
+ (func $ignore-non-pointers
+  (local $x i32)
+  (local $y i64)
+  (local $z f32)
+  (local $w f64)
+  (local $4 i32)
+  (local.set $4
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $4)
+    (i32.const 16)
+   )
+  )
+  (block
+   (local.set $x
+    (i32.const 1)
+   )
+   (local.set $y
+    (i64.const 1)
+   )
+   (local.set $z
+    (f32.const 1)
+   )
+   (local.set $w
+    (f64.const 1)
+   )
+   (block
+    (i32.store
+     (local.get $4)
+     (local.get $x)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+   (drop
+    (local.get $y)
+   )
+   (drop
+    (local.get $z)
+   )
+   (drop
+    (local.get $w)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $4)
+  )
+ )
+ (func $spill4
+  (local $x i32)
+  (local $y i32)
+  (local $z i32)
+  (local $w i32)
+  (local $4 i32)
+  (local.set $4
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $4)
+    (i32.const 16)
+   )
+  )
+  (block
+   (local.set $x
+    (i32.const 1)
+   )
+   (local.set $y
+    (i32.const 1)
+   )
+   (local.set $z
+    (i32.const 1)
+   )
+   (local.set $w
+    (i32.const 1)
+   )
+   (block
+    (i32.store
+     (local.get $4)
+     (local.get $x)
+    )
+    (i32.store offset=4
+     (local.get $4)
+     (local.get $y)
+    )
+    (i32.store offset=8
+     (local.get $4)
+     (local.get $z)
+    )
+    (i32.store offset=12
+     (local.get $4)
+     (local.get $w)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+   (drop
+    (local.get $y)
+   )
+   (drop
+    (local.get $z)
+   )
+   (drop
+    (local.get $w)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $4)
+  )
+ )
+ (func $spill5
+  (local $x i32)
+  (local $y i32)
+  (local $z i32)
+  (local $w i32)
+  (local $a i32)
+  (local $5 i32)
+  (local.set $5
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $5)
+    (i32.const 32)
+   )
+  )
+  (block
+   (local.set $x
+    (i32.const 1)
+   )
+   (local.set $y
+    (i32.const 1)
+   )
+   (local.set $z
+    (i32.const 1)
+   )
+   (local.set $w
+    (i32.const 1)
+   )
+   (local.set $a
+    (i32.const 1)
+   )
+   (block
+    (i32.store
+     (local.get $5)
+     (local.get $x)
+    )
+    (i32.store offset=4
+     (local.get $5)
+     (local.get $y)
+    )
+    (i32.store offset=8
+     (local.get $5)
+     (local.get $z)
+    )
+    (i32.store offset=12
+     (local.get $5)
+     (local.get $w)
+    )
+    (i32.store offset=16
+     (local.get $5)
+     (local.get $a)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+   (drop
+    (local.get $y)
+   )
+   (drop
+    (local.get $z)
+   )
+   (drop
+    (local.get $w)
+   )
+   (drop
+    (local.get $a)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $5)
+  )
+ )
+ (func $some-alive
+  (local $x i32)
+  (local $y i32)
+  (local $2 i32)
+  (local.set $2
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $2)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (i32.store
+     (local.get $2)
+     (local.get $x)
+    )
+    (call $nothing)
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $2)
+  )
+ )
+ (func $spill-args (param $p i32) (param $q i32)
+  (local $x i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local $5 i32)
+  (local.set $3
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $3)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $4
+     (i32.const 1)
+    )
+    (local.set $5
+     (i32.const 2)
+    )
+    (i32.store offset=8
+     (local.get $3)
+     (local.get $x)
+    )
+    (call $spill-args
+     (local.get $4)
+     (local.get $5)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $3)
+  )
+ )
+ (func $spill-ret (result i32)
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (local.set $4
+   (block (result i32)
+    (block
+     (i32.store
+      (local.get $1)
+      (local.get $x)
+     )
+     (call $nothing)
+    )
+    (drop
+     (local.get $x)
+    )
+    (if
+     (i32.const 1)
+     (block
+      (local.set $2
+       (i32.const 2)
+      )
+      (global.set $stack_ptr
+       (local.get $1)
+      )
+      (return
+       (local.get $2)
+      )
+     )
+     (block
+      (local.set $3
+       (i32.const 3)
+      )
+      (global.set $stack_ptr
+       (local.get $1)
+      )
+      (return
+       (local.get $3)
+      )
+     )
+    )
+    (i32.const 4)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+  (local.get $4)
+ )
+ (func $spill-unreachable (result i32)
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (local.set $2
+   (block (result i32)
+    (block
+     (i32.store
+      (local.get $1)
+      (local.get $x)
+     )
+     (call $nothing)
+    )
+    (drop
+     (local.get $x)
+    )
+    (unreachable)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+  (local.get $2)
+ )
+ (func $spill-call-call0 (param $p i32) (result i32)
+  (unreachable)
+ )
+ (func $spill-call-call1 (param $p i32) (result i32)
+  (local $x i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local $5 i32)
+  (local.set $2
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $2)
+    (i32.const 16)
+   )
+  )
+  (local.set $5
+   (block (result i32)
+    (drop
+     (block (result i32)
+      (local.set $3
+       (block (result i32)
+        (local.set $4
+         (i32.const 1)
+        )
+        (i32.store offset=4
+         (local.get $2)
+         (local.get $x)
+        )
+        (call $spill-call-call1
+         (local.get $4)
+        )
+       )
+      )
+      (i32.store offset=4
+       (local.get $2)
+       (local.get $x)
+      )
+      (call $spill-call-call0
+       (local.get $3)
+      )
+     )
+    )
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $2)
+  )
+  (local.get $5)
+ )
+ (func $spill-call-ret (param $p i32) (result i32)
+  (local $x i32)
+  (drop
+   (call $spill-call-call0
+    (return
+     (i32.const 1)
+    )
+   )
+  )
+  (i32.const 0)
+ )
+ (func $spill-ret-call (param $p i32) (result i32)
+  (local $x i32)
+  (drop
+   (return
+    (call $spill-call-call0
+     (i32.const 1)
+    )
+   )
+  )
+  (i32.const 0)
+ )
+ (func $spill-ret-ret (result i32)
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (local.set $3
+   (block (result i32)
+    (block
+     (i32.store
+      (local.get $1)
+      (local.get $x)
+     )
+     (call $nothing)
+    )
+    (drop
+     (local.get $x)
+    )
+    (drop
+     (block
+      (global.set $stack_ptr
+       (local.get $1)
+      )
+      (return
+       (block
+        (local.set $2
+         (i32.const 1)
+        )
+        (global.set $stack_ptr
+         (local.get $1)
+        )
+        (return
+         (local.get $2)
+        )
+       )
+      )
+     )
+    )
+    (i32.const 0)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+  (local.get $3)
+ )
+ (func $spill-call-othertype (param $y f64)
+  (local $x i32)
+  (local $2 i32)
+  (local $3 f64)
+  (local.set $2
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $2)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $3
+     (f64.const 1)
+    )
+    (i32.store
+     (local.get $2)
+     (local.get $x)
+    )
+    (call $spill-call-othertype
+     (local.get $3)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $2)
+  )
+ )
+ (func $spill-call_indirect
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local $3 i32)
+  (local $4 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $2
+     (i32.const 123)
+    )
+    (local.set $3
+     (i32.const 456)
+    )
+    (local.set $4
+     (i32.const 789)
+    )
+    (i32.store
+     (local.get $1)
+     (local.get $x)
+    )
+    (call_indirect (type $ii)
+     (local.get $2)
+     (local.get $3)
+     (local.get $4)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+ )
+ (func $spill-call_import
+  (local $x i32)
+  (local $1 i32)
+  (local $2 i32)
+  (local.set $1
+   (global.get $stack_ptr)
+  )
+  (global.set $stack_ptr
+   (i32.sub
+    (local.get $1)
+    (i32.const 16)
+   )
+  )
+  (block
+   (block
+    (local.set $2
+     (i32.const 200)
+    )
+    (i32.store
+     (local.get $1)
+     (local.get $x)
+    )
+    (call $segfault
+     (local.get $2)
+    )
+   )
+   (drop
+    (local.get $x)
+   )
+  )
+  (global.set $stack_ptr
+   (local.get $1)
+  )
+ )
+)
diff --git a/test/passes/spill-pointers.wast b/test/passes/spill-pointers.wast
new file mode 100644
index 0000000..4eb05a7
--- /dev/null
+++ b/test/passes/spill-pointers.wast
@@ -0,0 +1,338 @@
+(module
+  (memory 10)
+  (type $ii (func (param i32 i32)))
+  (table 1 1 funcref)
+  (elem (i32.const 0))
+  (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
+  (import "env" "segfault" (func $segfault (param i32)))
+  (global $stack_ptr (mut i32) (global.get $STACKTOP$asm2wasm$import))
+
+  (func $nothing
+  )
+  (func $not-alive
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (call $nothing)
+  )
+  (func $spill
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+  )
+  (func $ignore-non-pointers
+    (local $x i32)
+    (local $y i64)
+    (local $z f32)
+    (local $w f64)
+    (local.set $x (i32.const 1))
+    (local.set $y (i64.const 1))
+    (local.set $z (f32.const 1))
+    (local.set $w (f64.const 1))
+    (call $nothing)
+    (drop (local.get $x))
+    (drop (local.get $y))
+    (drop (local.get $z))
+    (drop (local.get $w))
+  )
+  (func $spill4
+    (local $x i32)
+    (local $y i32)
+    (local $z i32)
+    (local $w i32)
+    (local.set $x (i32.const 1))
+    (local.set $y (i32.const 1))
+    (local.set $z (i32.const 1))
+    (local.set $w (i32.const 1))
+    (call $nothing)
+    (drop (local.get $x))
+    (drop (local.get $y))
+    (drop (local.get $z))
+    (drop (local.get $w))
+  )
+  (func $spill5
+    (local $x i32)
+    (local $y i32)
+    (local $z i32)
+    (local $w i32)
+    (local $a i32)
+    (local.set $x (i32.const 1))
+    (local.set $y (i32.const 1))
+    (local.set $z (i32.const 1))
+    (local.set $w (i32.const 1))
+    (local.set $a (i32.const 1))
+    (call $nothing)
+    (drop (local.get $x))
+    (drop (local.get $y))
+    (drop (local.get $z))
+    (drop (local.get $w))
+    (drop (local.get $a))
+  )
+  (func $some-alive
+    (local $x i32)
+    (local $y i32)
+    (call $nothing)
+    (drop (local.get $x))
+  )
+  (func $spill-args (param $p i32) (param $q i32)
+    (local $x i32)
+    (call $spill-args (i32.const 1) (i32.const 2))
+    (drop (local.get $x))
+  )
+  (func $spill-ret (result i32)
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+    (if (i32.const 1)
+      (return (i32.const 2))
+      (return (i32.const 3))
+    )
+    (i32.const 4)
+  )
+  (func $spill-unreachable (result i32)
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+    (unreachable)
+  )
+  (func $spill-call-call0 (param $p i32) (result i32)
+    (unreachable)
+  )
+  (func $spill-call-call1 (param $p i32) (result i32)
+    (local $x i32)
+    (drop
+      (call $spill-call-call0
+        (call $spill-call-call1
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-call-ret (param $p i32) (result i32)
+    (local $x i32)
+    (drop
+      (call $spill-call-call0
+        (return
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-ret-call (param $p i32) (result i32)
+    (local $x i32)
+    (drop
+      (return
+        (call $spill-call-call0
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-ret-ret (result i32)
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+    (drop
+      (return
+        (return
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-call-othertype (param $y f64)
+    (local $x i32)
+    (call $spill-call-othertype (f64.const 1))
+    (drop (local.get $x))
+  )
+  (func $spill-call_indirect
+    (local $x i32)
+    (call_indirect (type $ii)
+      (i32.const 123)
+      (i32.const 456)
+      (i32.const 789)
+    )
+    (drop (local.get $x))
+  )
+  (func $spill-call_import
+    (local $x i32)
+    (call $segfault
+      (i32.const 200)
+    )
+    (drop (local.get $x))
+  )
+)
+
+(module
+  (memory 10)
+  (type $ii (func (param i32 i32)))
+  (table 1 1 funcref)
+  (elem (i32.const 0))
+  (global $stack_ptr (mut i32) (i32.const 1716592))
+  (export "stackSave" (func $stack_save))
+  (import "env" "segfault" (func $segfault (param i32)))
+  (func $stack_save (result i32)
+    (global.get $stack_ptr)
+  )
+
+  (func $nothing
+  )
+  (func $not-alive
+    (local $x i32)
+    (local.set $x (i32.const 1))
+    (call $nothing)
+  )
+  (func $spill
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+  )
+  (func $ignore-non-pointers
+    (local $x i32)
+    (local $y i64)
+    (local $z f32)
+    (local $w f64)
+    (local.set $x (i32.const 1))
+    (local.set $y (i64.const 1))
+    (local.set $z (f32.const 1))
+    (local.set $w (f64.const 1))
+    (call $nothing)
+    (drop (local.get $x))
+    (drop (local.get $y))
+    (drop (local.get $z))
+    (drop (local.get $w))
+  )
+  (func $spill4
+    (local $x i32)
+    (local $y i32)
+    (local $z i32)
+    (local $w i32)
+    (local.set $x (i32.const 1))
+    (local.set $y (i32.const 1))
+    (local.set $z (i32.const 1))
+    (local.set $w (i32.const 1))
+    (call $nothing)
+    (drop (local.get $x))
+    (drop (local.get $y))
+    (drop (local.get $z))
+    (drop (local.get $w))
+  )
+  (func $spill5
+    (local $x i32)
+    (local $y i32)
+    (local $z i32)
+    (local $w i32)
+    (local $a i32)
+    (local.set $x (i32.const 1))
+    (local.set $y (i32.const 1))
+    (local.set $z (i32.const 1))
+    (local.set $w (i32.const 1))
+    (local.set $a (i32.const 1))
+    (call $nothing)
+    (drop (local.get $x))
+    (drop (local.get $y))
+    (drop (local.get $z))
+    (drop (local.get $w))
+    (drop (local.get $a))
+  )
+  (func $some-alive
+    (local $x i32)
+    (local $y i32)
+    (call $nothing)
+    (drop (local.get $x))
+  )
+  (func $spill-args (param $p i32) (param $q i32)
+    (local $x i32)
+    (call $spill-args (i32.const 1) (i32.const 2))
+    (drop (local.get $x))
+  )
+  (func $spill-ret (result i32)
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+    (if (i32.const 1)
+      (return (i32.const 2))
+      (return (i32.const 3))
+    )
+    (i32.const 4)
+  )
+  (func $spill-unreachable (result i32)
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+    (unreachable)
+  )
+  (func $spill-call-call0 (param $p i32) (result i32)
+    (unreachable)
+  )
+  (func $spill-call-call1 (param $p i32) (result i32)
+    (local $x i32)
+    (drop
+      (call $spill-call-call0
+        (call $spill-call-call1
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-call-ret (param $p i32) (result i32)
+    (local $x i32)
+    (drop
+      (call $spill-call-call0
+        (return
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-ret-call (param $p i32) (result i32)
+    (local $x i32)
+    (drop
+      (return
+        (call $spill-call-call0
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-ret-ret (result i32)
+    (local $x i32)
+    (call $nothing)
+    (drop (local.get $x))
+    (drop
+      (return
+        (return
+          (i32.const 1)
+        )
+      )
+    )
+    (local.get $x)
+  )
+  (func $spill-call-othertype (param $y f64)
+    (local $x i32)
+    (call $spill-call-othertype (f64.const 1))
+    (drop (local.get $x))
+  )
+  (func $spill-call_indirect
+    (local $x i32)
+    (call_indirect (type $ii)
+      (i32.const 123)
+      (i32.const 456)
+      (i32.const 789)
+    )
+    (drop (local.get $x))
+  )
+  (func $spill-call_import
+    (local $x i32)
+    (call $segfault
+      (i32.const 200)
+    )
+    (drop (local.get $x))
+  )
+)
diff --git a/test/passes/ssa-nomerge_enable-simd.txt b/test/passes/ssa-nomerge_enable-simd.txt
index 1e2a4b9..d0a794a 100644
--- a/test/passes/ssa-nomerge_enable-simd.txt
+++ b/test/passes/ssa-nomerge_enable-simd.txt
@@ -115,7 +115,7 @@
    (local.set $x
     (i32.const 6)
    )
-   (block $block
+   (block
     (local.set $3
      (i32.const 7)
     )
@@ -131,7 +131,7 @@
  (func $if2 (param $x i32)
   (if
    (i32.const 1)
-   (block $block
+   (block
     (local.set $x
      (i32.const 1)
     )
diff --git a/test/passes/ssa_enable-threads.txt b/test/passes/ssa_enable-threads.txt
index 7fa8004..2c1b174 100644
--- a/test/passes/ssa_enable-threads.txt
+++ b/test/passes/ssa_enable-threads.txt
@@ -152,7 +152,7 @@
       (i32.const 6)
      )
     )
-    (block $block
+    (block
      (local.set $10
       (i32.const 7)
      )
@@ -177,7 +177,7 @@
   (block
    (if
     (i32.const 1)
-    (block $block
+    (block
      (local.set $1
       (local.tee $2
        (i32.const 1)
@@ -239,7 +239,7 @@
    )
    (if
     (i32.const 3)
-    (block $block
+    (block
      (local.set $2
       (local.tee $6
        (i32.const 1)
diff --git a/test/passes/ssa_fuzz-exec_enable-threads.txt b/test/passes/ssa_fuzz-exec_enable-threads.txt
index 40845e2..6334b35 100644
--- a/test/passes/ssa_fuzz-exec_enable-threads.txt
+++ b/test/passes/ssa_fuzz-exec_enable-threads.txt
@@ -54,7 +54,7 @@
            )
            (i32.const 1)
           )
-          (block $block (result i32)
+          (block (result i32)
            (loop $label$15
             (if
              (i32.const 0)
@@ -70,10 +70,8 @@
                )
               )
              )
-             (block $block4
-              (br_if $label$15
-               (i32.const 0)
-              )
+             (br_if $label$15
+              (i32.const 0)
              )
             )
             (br_if $label$15
diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt
index e93805c..8e2d730 100644
--- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt
+++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt
@@ -10,18 +10,19 @@
 --enable-multivalue
 --enable-gc
 --enable-memory64
---enable-typed-function-references
 --enable-relaxed-simd
 --enable-extended-const
+--enable-strings
+--enable-multi-memories
 (module
- (type $none_=>_v128_anyref (func (result v128 anyref)))
- (func $foo (result v128 anyref)
+ (type $none_=>_v128_externref (func (result v128 externref)))
+ (func $foo (result v128 externref)
   (tuple.make
    (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
-   (ref.null any)
+   (ref.null noextern)
   )
  )
- (func $bar (result v128 anyref)
+ (func $bar (result v128 externref)
   (return_call $foo)
  )
 )
diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
index 1170f8a..8006cb2 100644
--- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
+++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
@@ -1,36 +1,49 @@
 total
- [exports]      : 5       
- [funcs]        : 4       
+ [exports]      : 8       
+ [funcs]        : 11      
  [globals]      : 6       
  [imports]      : 5       
+ [memories]     : 1       
  [memory-data]  : 22      
- [table-data]   : 0       
+ [table-data]   : 7       
  [tables]       : 1       
- [tags]         : 1       
- [total]        : 363     
- [vars]         : 9       
+ [tags]         : 0       
+ [total]        : 748     
+ [vars]         : 19      
+ ArrayInit      : 5       
  AtomicFence    : 1       
  AtomicRMW      : 1       
- Binary         : 65      
- Block          : 36      
- Break          : 7       
- Call           : 2       
- CallRef        : 1       
- Const          : 75      
- Drop           : 1       
- GlobalGet      : 19      
- GlobalSet      : 10      
- I31New         : 1       
- If             : 13      
- Load           : 19      
- LocalGet       : 47      
- LocalSet       : 24      
- Loop           : 6       
+ Binary         : 84      
+ Block          : 90      
+ Break          : 14      
+ Call           : 19      
+ CallIndirect   : 2       
+ CallRef        : 2       
+ Const          : 176     
+ Drop           : 5       
+ GlobalGet      : 58      
+ GlobalSet      : 25      
+ I31Get         : 2       
+ I31New         : 4       
+ If             : 39      
+ Load           : 22      
+ LocalGet       : 31      
+ LocalSet       : 21      
+ Loop           : 14      
  MemoryCopy     : 1       
- Nop            : 6       
- RefFunc        : 2       
- Return         : 10      
- Select         : 2       
- Store          : 2       
- TupleMake      : 1       
- Unary          : 11      
+ MemoryFill     : 1       
+ Nop            : 17      
+ RefAs          : 4       
+ RefEq          : 2       
+ RefFunc        : 9       
+ RefIs          : 1       
+ RefNull        : 5       
+ Return         : 29      
+ SIMDExtract    : 3       
+ SIMDReplace    : 1       
+ Select         : 3       
+ Store          : 5       
+ StructNew      : 2       
+ TupleExtract   : 1       
+ TupleMake      : 6       
+ Unary          : 43      
diff --git a/test/passes/vacuum_all-features.txt b/test/passes/vacuum_all-features.txt
index b959595..f15572c 100644
--- a/test/passes/vacuum_all-features.txt
+++ b/test/passes/vacuum_all-features.txt
@@ -4,6 +4,7 @@
  (type $1 (func (param i32)))
  (type $2 (func (result f32)))
  (type $4 (func (param i32 f64 i32 i32)))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
  (type $none_=>_f64 (func (result f64)))
  (import "env" "int" (func $int (result i32)))
  (global $Int i32 (i32.const 0))
@@ -11,7 +12,7 @@
  (func $b
   (nop)
  )
- (func $l
+ (func $l (result i32)
   (local $x i32)
   (local $y i32)
   (local.set $x
@@ -23,6 +24,7 @@
   (local.set $x
    (local.get $y)
   )
+  (local.get $x)
  )
  (func $loopy (param $0 i32)
   (nop)
@@ -181,11 +183,11 @@
    )
   )
  )
- (func $if-1-block (param $x i32)
+ (func $if-1-block (param $x i32) (result i32)
   (block $out
    (if
     (local.get $x)
-    (block $block9
+    (block
      (local.set $x
       (local.get $x)
      )
@@ -193,6 +195,7 @@
     )
    )
   )
+  (local.get $x)
  )
  (func $block-resize-br-gone
   (block $out
@@ -208,16 +211,14 @@
   (nop)
  )
  (func $a
-  (block $block
-   (i32.store
-    (i32.const 1)
-    (i32.const 2)
-   )
-   (f64.div
-    (f64.const -nan:0xfffffffffa361)
-    (loop $label$1
-     (br $label$1)
-    )
+  (i32.store
+   (i32.const 1)
+   (i32.const 2)
+  )
+  (f64.div
+   (f64.const -nan:0xfffffffffa361)
+   (loop $label$1
+    (br $label$1)
    )
   )
  )
@@ -248,7 +249,7 @@
  )
  (func $load-may-have-side-effects (result i32)
   (i64.ge_s
-   (block $block (result i64)
+   (block (result i64)
     (drop
      (i64.load32_s
       (i32.const 678585719)
@@ -385,7 +386,7 @@
    (call $_deflateInit2_
     (local.get $3)
    )
-   (block $block
+   (block
     (global.set $global$1
      (local.get $3)
     )
@@ -402,7 +403,7 @@
      )
      (i32.const 1)
     )
-    (block $block1 (result i32)
+    (block (result i32)
      (i32.store
       (local.get $1)
       (i32.load offset=20
@@ -419,7 +420,7 @@
      )
      (i32.const 0)
     )
-    (block $block2 (result i32)
+    (block (result i32)
      (drop
       (call $_deflateEnd
        (local.get $3)
diff --git a/test/passes/vacuum_all-features.wast b/test/passes/vacuum_all-features.wast
index 8a6d520..edf1e01 100644
--- a/test/passes/vacuum_all-features.wast
+++ b/test/passes/vacuum_all-features.wast
@@ -64,7 +64,7 @@
       (nop)
     )
   )
-  (func $l (type $0)
+  (func $l (result i32)
     (local $x i32)
     (local $y i32)
     (drop
@@ -99,6 +99,7 @@
         (local.get $y)
       )
     )
+    (local.get $x)
   )
   (func $loopy (type $1) (param $0 i32)
     (loop $loop-in1
@@ -463,7 +464,7 @@
       )
     )
   )
-  (func $if-1-block (param $x i32)
+  (func $if-1-block (param $x i32) (result i32)
    (block $out
     (if
      (local.get $x)
@@ -480,6 +481,7 @@
      )
     )
    )
+   (local.get $x)
   )
   (func $block-resize-br-gone
     (block $out
@@ -805,9 +807,7 @@
     ;; nullable reference type and we don't have a type to put in its place, so
     ;; don't try to replace it. (later operations will remove all the body of
     ;; this function; this test verifies we don't crash along the way)
-    (struct.new_default_with_rtt $A
-     (rtt.canon $A)
-    )
+    (struct.new_default $A)
    )
   )
  )
diff --git a/test/polymorphic_stack.wast.from-wast b/test/polymorphic_stack.wast.from-wast
index 634a634..e9379f8 100644
--- a/test/polymorphic_stack.wast.from-wast
+++ b/test/polymorphic_stack.wast.from-wast
@@ -82,7 +82,7 @@
  )
  (func $untaken-break-should-have-value (result i32)
   (block $x (result i32)
-   (block $block
+   (block
     (br_if $x
      (i32.const 0)
      (unreachable)
diff --git a/test/reference-types.wast b/test/reference-types.wast
index afdb38c..64f6e24 100644
--- a/test/reference-types.wast
+++ b/test/reference-types.wast
@@ -1,51 +1,41 @@
-;; reftype :: externref | funcref
-
-;; NOTE: the subtyping relationship has been removed from the reference-types proposal but an
-;; `--enable-anyref` feature flag is present in Binaryen that we use below to test subtyping.
-;;
-;; reftype :: reftype | anyref
-;; reftype <: anyref
-
 (module
-  (type $sig_externref (func (param externref)))
+  (type $sig_eqref (func (param eqref)))
   (type $sig_funcref (func (param funcref)))
   (type $sig_anyref (func (param anyref)))
 
-  (func $take_externref (param externref))
+  (func $take_eqref (param eqref))
   (func $take_funcref (param funcref))
   (func $take_anyref (param anyref))
   (func $foo)
 
-  (table funcref (elem $take_externref $take_funcref $take_anyref))
+  (table funcref (elem $take_eqref $take_funcref $take_anyref))
   (elem declare func $ref-taken-but-not-in-table)
 
-  (import "env" "import_func" (func $import_func (param externref) (result funcref)))
-  (import "env" "import_global" (global $import_global externref))
-  (export "export_func" (func $import_func (param externref) (result funcref)))
+  (import "env" "import_func" (func $import_func (param eqref) (result funcref)))
+  (import "env" "import_global" (global $import_global eqref))
+  (export "export_func" (func $import_func (param eqref) (result funcref)))
   (export "export_global" (global $import_global))
 
   ;; Test global initializer expressions
-  (global $global_externref (mut externref) (ref.null extern))
+  (global $global_eqref (mut eqref) (ref.null eq))
   (global $global_funcref (mut funcref) (ref.null func))
   (global $global_funcref_func (mut funcref) (ref.func $foo))
   (global $global_anyref (mut anyref) (ref.null any))
 
   ;; Test subtype relationship in global initializer expressions
-  (global $global_anyref2 (mut anyref) (ref.null extern))
-  (global $global_anyref3 (mut anyref) (ref.null func))
-  (global $global_anyref4 (mut anyref) (ref.func $foo))
+  (global $global_anyref2 (mut anyref) (ref.null eq))
 
   (tag $e-i32 (param i32))
 
   (func $test
-    (local $local_externref externref)
+    (local $local_eqref eqref)
     (local $local_funcref funcref)
     (local $local_anyref anyref)
 
     ;; Test types for local.get/set
-    (local.set $local_externref (local.get $local_externref))
-    (local.set $local_externref (global.get $global_externref))
-    (local.set $local_externref (ref.null extern))
+    (local.set $local_eqref (local.get $local_eqref))
+    (local.set $local_eqref (global.get $global_eqref))
+    (local.set $local_eqref (ref.null eq))
     (local.set $local_funcref (local.get $local_funcref))
     (local.set $local_funcref (global.get $global_funcref))
     (local.set $local_funcref (ref.null func))
@@ -55,18 +45,14 @@
     (local.set $local_anyref (ref.null any))
 
     ;; Test subtype relationship for local.set
-    (local.set $local_anyref (local.get $local_externref))
-    (local.set $local_anyref (global.get $global_externref))
-    (local.set $local_anyref (ref.null extern))
-    (local.set $local_anyref (local.get $local_funcref))
-    (local.set $local_anyref (global.get $global_funcref))
-    (local.set $local_anyref (ref.null func))
-    (local.set $local_anyref (ref.func $foo))
+    (local.set $local_anyref (local.get $local_eqref))
+    (local.set $local_anyref (global.get $global_eqref))
+    (local.set $local_anyref (ref.null eq))
 
     ;; Test types for global.get/set
-    (global.set $global_externref (global.get $global_externref))
-    (global.set $global_externref (local.get $local_externref))
-    (global.set $global_externref (ref.null extern))
+    (global.set $global_eqref (global.get $global_eqref))
+    (global.set $global_eqref (local.get $local_eqref))
+    (global.set $global_eqref (ref.null eq))
     (global.set $global_funcref (global.get $global_funcref))
     (global.set $global_funcref (local.get $local_funcref))
     (global.set $global_funcref (ref.null func))
@@ -76,18 +62,14 @@
     (global.set $global_anyref (ref.null any))
 
     ;; Test subtype relationship for global.set
-    (global.set $global_anyref (global.get $global_externref))
-    (global.set $global_anyref (local.get $local_externref))
-    (global.set $global_anyref (ref.null extern))
-    (global.set $global_anyref (global.get $global_funcref))
-    (global.set $global_anyref (local.get $local_funcref))
-    (global.set $global_anyref (ref.null func))
-    (global.set $global_anyref (ref.func $foo))
+    (global.set $global_anyref (global.get $global_eqref))
+    (global.set $global_anyref (local.get $local_eqref))
+    (global.set $global_anyref (ref.null eq))
 
     ;; Test function call params
-    (call $take_externref (local.get $local_externref))
-    (call $take_externref (global.get $global_externref))
-    (call $take_externref (ref.null extern))
+    (call $take_eqref (local.get $local_eqref))
+    (call $take_eqref (global.get $global_eqref))
+    (call $take_eqref (ref.null eq))
     (call $take_funcref (local.get $local_funcref))
     (call $take_funcref (global.get $global_funcref))
     (call $take_funcref (ref.null func))
@@ -97,18 +79,14 @@
     (call $take_anyref (ref.null any))
 
     ;; Test subtype relationship for function call params
-    (call $take_anyref (local.get $local_externref))
-    (call $take_anyref (global.get $global_externref))
-    (call $take_anyref (ref.null extern))
-    (call $take_anyref (local.get $local_funcref))
-    (call $take_anyref (global.get $global_funcref))
-    (call $take_anyref (ref.null func))
-    (call $take_anyref (ref.func $foo))
+    (call $take_anyref (local.get $local_eqref))
+    (call $take_anyref (global.get $global_eqref))
+    (call $take_anyref (ref.null eq))
 
     ;; Test call_indirect params
-    (call_indirect (type $sig_externref) (local.get $local_externref) (i32.const 0))
-    (call_indirect (type $sig_externref) (global.get $global_externref) (i32.const 0))
-    (call_indirect (type $sig_externref) (ref.null extern) (i32.const 0))
+    (call_indirect (type $sig_eqref) (local.get $local_eqref) (i32.const 0))
+    (call_indirect (type $sig_eqref) (global.get $global_eqref) (i32.const 0))
+    (call_indirect (type $sig_eqref) (ref.null eq) (i32.const 0))
     (call_indirect (type $sig_funcref) (local.get $local_funcref) (i32.const 1))
     (call_indirect (type $sig_funcref) (global.get $global_funcref) (i32.const 1))
     (call_indirect (type $sig_funcref) (ref.null func) (i32.const 1))
@@ -118,28 +96,24 @@
     (call_indirect (type $sig_anyref) (ref.null any) (i32.const 3))
 
     ;; Test subtype relationship for call_indirect params
-    (call_indirect (type $sig_anyref) (local.get $local_externref) (i32.const 3))
-    (call_indirect (type $sig_anyref) (global.get $global_externref) (i32.const 3))
-    (call_indirect (type $sig_anyref) (ref.null extern) (i32.const 3))
-    (call_indirect (type $sig_anyref) (local.get $local_funcref) (i32.const 3))
-    (call_indirect (type $sig_anyref) (global.get $global_funcref) (i32.const 3))
-    (call_indirect (type $sig_anyref) (ref.null func) (i32.const 3))
-    (call_indirect (type $sig_anyref) (ref.func $foo) (i32.const 3))
+    (call_indirect (type $sig_anyref) (local.get $local_eqref) (i32.const 3))
+    (call_indirect (type $sig_anyref) (global.get $global_eqref) (i32.const 3))
+    (call_indirect (type $sig_anyref) (ref.null eq) (i32.const 3))
 
     ;; Test block return type
     (drop
-      (block (result externref)
-        (br_if 0 (local.get $local_externref) (i32.const 1))
+      (block (result eqref)
+        (br_if 0 (local.get $local_eqref) (i32.const 1))
       )
     )
     (drop
-      (block (result externref)
-        (br_if 0 (global.get $global_externref) (i32.const 1))
+      (block (result eqref)
+        (br_if 0 (global.get $global_eqref) (i32.const 1))
       )
     )
     (drop
-      (block (result externref)
-        (br_if 0 (ref.null extern) (i32.const 1))
+      (block (result eqref)
+        (br_if 0 (ref.null eq) (i32.const 1))
       )
     )
     (drop
@@ -181,44 +155,29 @@
     ;; Test subtype relationship for block return type
     (drop
       (block (result anyref)
-        (br_if 0 (local.get $local_externref) (i32.const 1))
-      )
-    )
-    (drop
-      (block (result anyref)
-        (br_if 0 (local.get $local_funcref) (i32.const 1))
-      )
-    )
-    (drop
-      (block (result anyref)
-        (br_if 0 (ref.null extern) (i32.const 1))
-      )
-    )
-    (drop
-      (block (result anyref)
-        (br_if 0 (ref.null func) (i32.const 1))
+        (br_if 0 (local.get $local_eqref) (i32.const 1))
       )
     )
     (drop
       (block (result anyref)
-        (br_if 0 (ref.func $foo) (i32.const 1))
+        (br_if 0 (ref.null eq) (i32.const 1))
       )
     )
 
     ;; Test loop return type
     (drop
-      (loop (result externref)
-        (local.get $local_externref)
+      (loop (result eqref)
+        (local.get $local_eqref)
       )
     )
     (drop
-      (loop (result externref)
-        (global.get $global_externref)
+      (loop (result eqref)
+        (global.get $global_eqref)
       )
     )
     (drop
-      (loop (result externref)
-        (ref.null extern)
+      (loop (result eqref)
+        (ref.null eq)
       )
     )
     (drop
@@ -260,46 +219,26 @@
     ;; Test subtype relationship for loop return type
     (drop
       (loop (result anyref)
-        (local.get $local_externref)
-      )
-    )
-    (drop
-      (loop (result anyref)
-        (global.get $global_externref)
-      )
-    )
-    (drop
-      (loop (result anyref)
-        (ref.null extern)
-      )
-    )
-    (drop
-      (loop (result anyref)
-        (local.get $local_funcref)
-      )
-    )
-    (drop
-      (loop (result anyref)
-        (global.get $global_funcref)
+        (local.get $local_eqref)
       )
     )
     (drop
       (loop (result anyref)
-        (ref.null func)
+        (global.get $global_eqref)
       )
     )
     (drop
       (loop (result anyref)
-        (ref.func $foo)
+        (ref.null eq)
       )
     )
 
     ;; Test if return type
     (drop
-      (if (result externref)
+      (if (result eqref)
         (i32.const 1)
-        (local.get $local_externref)
-        (ref.null extern)
+        (local.get $local_eqref)
+        (ref.null eq)
       )
     )
     (drop
@@ -321,34 +260,36 @@
     (drop
       (if (result anyref)
         (i32.const 1)
-        (local.get $local_externref)
-        (local.get $local_funcref)
+        (local.get $local_eqref)
+        (local.get $local_eqref)
       )
     )
     (drop
       (if (result anyref)
         (i32.const 1)
-        (ref.null extern)
-        (ref.null func)
+        (ref.null eq)
+        (ref.null i31)
       )
     )
     (drop
       (if (result anyref)
         (i32.const 1)
-        (ref.func $foo)
-        (ref.null extern)
+        (i31.new
+          (i32.const 0)
+        )
+        (ref.null eq)
       )
     )
 
     ;; Test try return type
     (drop
-      (try (result externref)
+      (try (result eqref)
         (do
-          (local.get $local_externref)
+          (local.get $local_eqref)
         )
         (catch $e-i32
           (drop (pop i32))
-          (ref.null extern)
+          (ref.null eq)
         )
       )
     )
@@ -368,31 +309,31 @@
     (drop
       (try (result anyref)
         (do
-          (local.get $local_externref)
+          (local.get $local_eqref)
         )
         (catch $e-i32
           (drop (pop i32))
-          (ref.func $foo)
+          (ref.null any)
         )
       )
     )
     (drop
       (try (result anyref)
         (do
-          (ref.func $foo)
+          (ref.null eq)
         )
         (catch $e-i32
           (drop (pop i32))
-          (local.get $local_externref)
+          (local.get $local_eqref)
         )
       )
     )
 
     ;; Test typed select
     (drop
-      (select (result externref)
-        (local.get $local_externref)
-        (ref.null extern)
+      (select (result eqref)
+        (local.get $local_eqref)
+        (ref.null eq)
         (i32.const 1)
       )
     )
@@ -414,23 +355,18 @@
     ;; Test subtype relationship for typed select
     (drop
       (select (result anyref)
-        (local.get $local_externref)
-        (local.get $local_funcref)
-        (i32.const 1)
-      )
-    )
-    (drop
-      (select (result anyref)
-        (local.get $local_funcref)
-        (local.get $local_externref)
+        (local.get $local_eqref)
+        (i31.new
+          (i32.const 0)
+        )
         (i32.const 1)
       )
     )
 
     ;; ref.is_null takes any reference types
-    (drop (ref.is_null (local.get $local_externref)))
-    (drop (ref.is_null (global.get $global_externref)))
-    (drop (ref.is_null (ref.null extern)))
+    (drop (ref.is_null (local.get $local_eqref)))
+    (drop (ref.is_null (global.get $global_eqref)))
+    (drop (ref.is_null (ref.null eq)))
     (drop (ref.is_null (local.get $local_funcref)))
     (drop (ref.is_null (global.get $global_funcref)))
     (drop (ref.is_null (ref.null func)))
@@ -441,15 +377,15 @@
   )
 
   ;; Test function return type
-  (func $return_externref_local (result externref)
-    (local $local_externref externref)
-    (local.get $local_externref)
+  (func $return_eqref_local (result eqref)
+    (local $local_eqref eqref)
+    (local.get $local_eqref)
   )
-  (func $return_externref_global (result externref)
-    (global.get $global_externref)
+  (func $return_eqref_global (result eqref)
+    (global.get $global_eqref)
   )
-  (func $return_externref_null (result externref)
-    (ref.null extern)
+  (func $return_eqref_null (result eqref)
+    (ref.null eq)
   )
   (func $return_funcref_local (result funcref)
     (local $local_funcref funcref)
@@ -477,35 +413,22 @@
 
   ;; Test subtype relationship in function return type
   (func $return_anyref2 (result anyref)
-    (local $local_externref externref)
-    (local.get $local_externref)
+    (local $local_eqref eqref)
+    (local.get $local_eqref)
   )
   (func $return_anyref3 (result anyref)
-    (global.get $global_externref)
+    (global.get $global_eqref)
   )
   (func $return_anyref4 (result anyref)
-    (ref.null extern)
-  )
-  (func $return_anyref5 (result anyref)
-    (local $local_funcref funcref)
-    (local.get $local_funcref)
-  )
-  (func $return_anyref6 (result anyref)
-    (global.get $global_funcref)
-  )
-  (func $return_anyref7 (result anyref)
-    (ref.null func)
-  )
-  (func $return_anyref8 (result anyref)
-    (ref.func $foo)
+    (ref.null eq)
   )
 
   ;; Test returns
-  (func $returns_externref (result externref)
-    (local $local_externref externref)
-    (return (local.get $local_externref))
-    (return (global.get $global_externref))
-    (return (ref.null extern))
+  (func $returns_eqref (result eqref)
+    (local $local_eqref eqref)
+    (return (local.get $local_eqref))
+    (return (global.get $global_eqref))
+    (return (ref.null eq))
   )
   (func $returns_funcref (result funcref)
     (local $local_funcref funcref)
@@ -523,15 +446,11 @@
 
   ;; Test subtype relationship in returns
   (func $returns_anyref2 (result anyref)
-    (local $local_externref externref)
+    (local $local_eqref eqref)
     (local $local_funcref funcref)
-    (return (local.get $local_externref))
-    (return (global.get $global_externref))
-    (return (ref.null extern))
-    (return (local.get $local_funcref))
-    (return (global.get $global_funcref))
-    (return (ref.func $foo))
-    (return (ref.null func))
+    (return (local.get $local_eqref))
+    (return (global.get $global_eqref))
+    (return (ref.null eq))
   )
 
   (func $ref-user
diff --git a/test/reference-types.wast.from-wast b/test/reference-types.wast.from-wast
index 5afb74e..e3a04c7 100644
--- a/test/reference-types.wast.from-wast
+++ b/test/reference-types.wast.from-wast
@@ -3,25 +3,25 @@
  (type $sig_anyref (func (param anyref)))
  (type $sig_funcref (func (param funcref)))
  (type $none_=>_funcref (func (result funcref)))
+ (type $sig_eqref (func (param eqref)))
  (type $none_=>_none (func))
+ (type $none_=>_eqref (func (result eqref)))
  (type $i32_=>_none (func (param i32)))
- (type $anyref_=>_funcref (func (param anyref) (result funcref)))
- (import "env" "import_global" (global $import_global anyref))
- (import "env" "import_func" (func $import_func (param anyref) (result funcref)))
- (global $global_externref (mut anyref) (ref.null any))
- (global $global_funcref (mut funcref) (ref.null func))
+ (type $eqref_=>_funcref (func (param eqref) (result funcref)))
+ (import "env" "import_global" (global $import_global eqref))
+ (import "env" "import_func" (func $import_func (param eqref) (result funcref)))
+ (global $global_eqref (mut eqref) (ref.null none))
+ (global $global_funcref (mut funcref) (ref.null nofunc))
  (global $global_funcref_func (mut funcref) (ref.func $foo))
- (global $global_anyref (mut anyref) (ref.null any))
- (global $global_anyref2 (mut anyref) (ref.null any))
- (global $global_anyref3 (mut anyref) (ref.null func))
- (global $global_anyref4 (mut anyref) (ref.func $foo))
+ (global $global_anyref (mut anyref) (ref.null none))
+ (global $global_anyref2 (mut anyref) (ref.null none))
  (table $0 3 3 funcref)
- (elem (i32.const 0) $take_externref $take_funcref $take_anyref)
+ (elem (i32.const 0) $take_eqref $take_funcref $take_anyref)
  (elem declare func $foo $ref-taken-but-not-in-table)
  (tag $e-i32 (param i32))
  (export "export_func" (func $import_func))
  (export "export_global" (global $import_global))
- (func $take_externref (param $0 anyref)
+ (func $take_eqref (param $0 eqref)
   (nop)
  )
  (func $take_funcref (param $0 funcref)
@@ -34,17 +34,17 @@
   (nop)
  )
  (func $test
-  (local $local_externref anyref)
+  (local $local_eqref eqref)
   (local $local_funcref funcref)
   (local $local_anyref anyref)
-  (local.set $local_externref
-   (local.get $local_externref)
+  (local.set $local_eqref
+   (local.get $local_eqref)
   )
-  (local.set $local_externref
-   (global.get $global_externref)
+  (local.set $local_eqref
+   (global.get $global_eqref)
   )
-  (local.set $local_externref
-   (ref.null any)
+  (local.set $local_eqref
+   (ref.null none)
   )
   (local.set $local_funcref
    (local.get $local_funcref)
@@ -53,7 +53,7 @@
    (global.get $global_funcref)
   )
   (local.set $local_funcref
-   (ref.null func)
+   (ref.null nofunc)
   )
   (local.set $local_funcref
    (ref.func $foo)
@@ -65,37 +65,25 @@
    (global.get $global_anyref)
   )
   (local.set $local_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (local.set $local_anyref
-   (local.get $local_externref)
+   (local.get $local_eqref)
   )
   (local.set $local_anyref
-   (global.get $global_externref)
+   (global.get $global_eqref)
   )
   (local.set $local_anyref
-   (ref.null any)
+   (ref.null none)
   )
-  (local.set $local_anyref
-   (local.get $local_funcref)
-  )
-  (local.set $local_anyref
-   (global.get $global_funcref)
-  )
-  (local.set $local_anyref
-   (ref.null func)
-  )
-  (local.set $local_anyref
-   (ref.func $foo)
+  (global.set $global_eqref
+   (global.get $global_eqref)
   )
-  (global.set $global_externref
-   (global.get $global_externref)
+  (global.set $global_eqref
+   (local.get $local_eqref)
   )
-  (global.set $global_externref
-   (local.get $local_externref)
-  )
-  (global.set $global_externref
-   (ref.null any)
+  (global.set $global_eqref
+   (ref.null none)
   )
   (global.set $global_funcref
    (global.get $global_funcref)
@@ -104,7 +92,7 @@
    (local.get $local_funcref)
   )
   (global.set $global_funcref
-   (ref.null func)
+   (ref.null nofunc)
   )
   (global.set $global_funcref
    (ref.func $foo)
@@ -116,37 +104,25 @@
    (local.get $local_anyref)
   )
   (global.set $global_anyref
-   (ref.null any)
-  )
-  (global.set $global_anyref
-   (global.get $global_externref)
-  )
-  (global.set $global_anyref
-   (local.get $local_externref)
-  )
-  (global.set $global_anyref
-   (ref.null any)
-  )
-  (global.set $global_anyref
-   (global.get $global_funcref)
+   (ref.null none)
   )
   (global.set $global_anyref
-   (local.get $local_funcref)
+   (global.get $global_eqref)
   )
   (global.set $global_anyref
-   (ref.null func)
+   (local.get $local_eqref)
   )
   (global.set $global_anyref
-   (ref.func $foo)
+   (ref.null none)
   )
-  (call $take_externref
-   (local.get $local_externref)
+  (call $take_eqref
+   (local.get $local_eqref)
   )
-  (call $take_externref
-   (global.get $global_externref)
+  (call $take_eqref
+   (global.get $global_eqref)
   )
-  (call $take_externref
-   (ref.null any)
+  (call $take_eqref
+   (ref.null none)
   )
   (call $take_funcref
    (local.get $local_funcref)
@@ -155,7 +131,7 @@
    (global.get $global_funcref)
   )
   (call $take_funcref
-   (ref.null func)
+   (ref.null nofunc)
   )
   (call $take_funcref
    (ref.func $foo)
@@ -167,39 +143,27 @@
    (global.get $global_anyref)
   )
   (call $take_anyref
-   (ref.null any)
-  )
-  (call $take_anyref
-   (local.get $local_externref)
+   (ref.null none)
   )
   (call $take_anyref
-   (global.get $global_externref)
+   (local.get $local_eqref)
   )
   (call $take_anyref
-   (ref.null any)
+   (global.get $global_eqref)
   )
   (call $take_anyref
-   (local.get $local_funcref)
-  )
-  (call $take_anyref
-   (global.get $global_funcref)
+   (ref.null none)
   )
-  (call $take_anyref
-   (ref.null func)
-  )
-  (call $take_anyref
-   (ref.func $foo)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (local.get $local_externref)
+  (call_indirect $0 (type $sig_eqref)
+   (local.get $local_eqref)
    (i32.const 0)
   )
-  (call_indirect $0 (type $sig_anyref)
-   (global.get $global_externref)
+  (call_indirect $0 (type $sig_eqref)
+   (global.get $global_eqref)
    (i32.const 0)
   )
-  (call_indirect $0 (type $sig_anyref)
-   (ref.null any)
+  (call_indirect $0 (type $sig_eqref)
+   (ref.null none)
    (i32.const 0)
   )
   (call_indirect $0 (type $sig_funcref)
@@ -211,7 +175,7 @@
    (i32.const 1)
   )
   (call_indirect $0 (type $sig_funcref)
-   (ref.null func)
+   (ref.null nofunc)
    (i32.const 1)
   )
   (call_indirect $0 (type $sig_funcref)
@@ -227,57 +191,41 @@
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (ref.null any)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (local.get $local_externref)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (global.get $global_externref)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (ref.null any)
+   (ref.null none)
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (local.get $local_funcref)
+   (local.get $local_eqref)
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (global.get $global_funcref)
+   (global.get $global_eqref)
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (ref.null func)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (ref.func $foo)
+   (ref.null none)
    (i32.const 3)
   )
   (drop
-   (block $block (result anyref)
+   (block $block (result eqref)
     (br_if $block
-     (local.get $local_externref)
+     (local.get $local_eqref)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $block0 (result anyref)
+   (block $block0 (result eqref)
     (br_if $block0
-     (global.get $global_externref)
+     (global.get $global_eqref)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $block1 (result anyref)
+   (block $block1 (result eqref)
     (br_if $block1
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
@@ -301,7 +249,7 @@
   (drop
    (block $block4 (result funcref)
     (br_if $block4
-     (ref.null func)
+     (ref.null nofunc)
      (i32.const 1)
     )
    )
@@ -333,7 +281,7 @@
   (drop
    (block $block8 (result anyref)
     (br_if $block8
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
@@ -341,7 +289,7 @@
   (drop
    (block $block9 (result anyref)
     (br_if $block9
-     (local.get $local_externref)
+     (local.get $local_eqref)
      (i32.const 1)
     )
    )
@@ -349,177 +297,135 @@
   (drop
    (block $block10 (result anyref)
     (br_if $block10
-     (local.get $local_funcref)
+     (ref.null none)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $block11 (result anyref)
-    (br_if $block11
-     (ref.null any)
-     (i32.const 1)
-    )
+   (loop $loop-in (result eqref)
+    (local.get $local_eqref)
    )
   )
   (drop
-   (block $block12 (result anyref)
-    (br_if $block12
-     (ref.null func)
-     (i32.const 1)
-    )
+   (loop $loop-in11 (result eqref)
+    (global.get $global_eqref)
    )
   )
   (drop
-   (block $block13 (result anyref)
-    (br_if $block13
-     (ref.func $foo)
-     (i32.const 1)
-    )
+   (loop $loop-in12 (result eqref)
+    (ref.null none)
    )
   )
   (drop
-   (loop $loop-in (result anyref)
-    (local.get $local_externref)
-   )
-  )
-  (drop
-   (loop $loop-in14 (result anyref)
-    (global.get $global_externref)
-   )
-  )
-  (drop
-   (loop $loop-in15 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $loop-in16 (result funcref)
+   (loop $loop-in13 (result funcref)
     (local.get $local_funcref)
    )
   )
   (drop
-   (loop $loop-in17 (result funcref)
+   (loop $loop-in14 (result funcref)
     (global.get $global_funcref)
    )
   )
   (drop
-   (loop $loop-in18 (result funcref)
-    (ref.null func)
+   (loop $loop-in15 (result funcref)
+    (ref.null nofunc)
    )
   )
   (drop
-   (loop $loop-in19 (result funcref)
+   (loop $loop-in16 (result funcref)
     (ref.func $foo)
    )
   )
   (drop
-   (loop $loop-in20 (result anyref)
+   (loop $loop-in17 (result anyref)
     (local.get $local_anyref)
    )
   )
   (drop
-   (loop $loop-in21 (result anyref)
+   (loop $loop-in18 (result anyref)
     (global.get $global_anyref)
    )
   )
   (drop
-   (loop $loop-in22 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $loop-in23 (result anyref)
-    (local.get $local_externref)
-   )
-  )
-  (drop
-   (loop $loop-in24 (result anyref)
-    (global.get $global_externref)
+   (loop $loop-in19 (result anyref)
+    (ref.null none)
    )
   )
   (drop
-   (loop $loop-in25 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $loop-in26 (result anyref)
-    (local.get $local_funcref)
-   )
-  )
-  (drop
-   (loop $loop-in27 (result anyref)
-    (global.get $global_funcref)
+   (loop $loop-in20 (result anyref)
+    (local.get $local_eqref)
    )
   )
   (drop
-   (loop $loop-in28 (result anyref)
-    (ref.null func)
+   (loop $loop-in21 (result anyref)
+    (global.get $global_eqref)
    )
   )
   (drop
-   (loop $loop-in29 (result anyref)
-    (ref.func $foo)
+   (loop $loop-in22 (result anyref)
+    (ref.null none)
    )
   )
   (drop
-   (if (result anyref)
+   (if (result eqref)
     (i32.const 1)
-    (local.get $local_externref)
-    (ref.null any)
+    (local.get $local_eqref)
+    (ref.null none)
    )
   )
   (drop
    (if (result funcref)
     (i32.const 1)
     (local.get $local_funcref)
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
     (local.get $local_anyref)
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (local.get $local_externref)
-    (local.get $local_funcref)
+    (local.get $local_eqref)
+    (local.get $local_eqref)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (ref.null any)
-    (ref.null func)
+    (ref.null none)
+    (ref.null none)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (ref.func $foo)
-    (ref.null any)
+    (i31.new
+     (i32.const 0)
+    )
+    (ref.null none)
    )
   )
   (drop
-   (try $try (result anyref)
+   (try $try (result eqref)
     (do
-     (local.get $local_externref)
+     (local.get $local_eqref)
     )
     (catch $e-i32
      (drop
       (pop i32)
      )
-     (ref.null any)
+     (ref.null none)
     )
    )
   )
   (drop
-   (try $try35 (result funcref)
+   (try $try28 (result funcref)
     (do
      (ref.func $foo)
     )
@@ -527,47 +433,47 @@
      (drop
       (pop i32)
      )
-     (ref.null func)
+     (ref.null nofunc)
     )
    )
   )
   (drop
-   (try $try36 (result anyref)
+   (try $try29 (result anyref)
     (do
-     (local.get $local_externref)
+     (local.get $local_eqref)
     )
     (catch $e-i32
      (drop
       (pop i32)
      )
-     (ref.func $foo)
+     (ref.null none)
     )
    )
   )
   (drop
-   (try $try37 (result anyref)
+   (try $try30 (result anyref)
     (do
-     (ref.func $foo)
+     (ref.null none)
     )
     (catch $e-i32
      (drop
       (pop i32)
      )
-     (local.get $local_externref)
+     (local.get $local_eqref)
     )
    )
   )
   (drop
-   (select (result anyref)
-    (local.get $local_externref)
-    (ref.null any)
+   (select (result eqref)
+    (local.get $local_eqref)
+    (ref.null none)
     (i32.const 1)
    )
   )
   (drop
    (select (result funcref)
     (local.get $local_funcref)
-    (ref.null func)
+    (ref.null nofunc)
     (i32.const 1)
    )
   )
@@ -580,31 +486,26 @@
   )
   (drop
    (select (result anyref)
-    (local.get $local_externref)
-    (local.get $local_funcref)
-    (i32.const 1)
-   )
-  )
-  (drop
-   (select (result anyref)
-    (local.get $local_funcref)
-    (local.get $local_externref)
+    (local.get $local_eqref)
+    (i31.new
+     (i32.const 0)
+    )
     (i32.const 1)
    )
   )
   (drop
    (ref.is_null
-    (local.get $local_externref)
+    (local.get $local_eqref)
    )
   )
   (drop
    (ref.is_null
-    (global.get $global_externref)
+    (global.get $global_eqref)
    )
   )
   (drop
    (ref.is_null
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -619,7 +520,7 @@
   )
   (drop
    (ref.is_null
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
@@ -639,19 +540,19 @@
   )
   (drop
    (ref.is_null
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
- (func $return_externref_local (result anyref)
-  (local $local_externref anyref)
-  (local.get $local_externref)
+ (func $return_eqref_local (result eqref)
+  (local $local_eqref eqref)
+  (local.get $local_eqref)
  )
- (func $return_externref_global (result anyref)
-  (global.get $global_externref)
+ (func $return_eqref_global (result eqref)
+  (global.get $global_eqref)
  )
- (func $return_externref_null (result anyref)
-  (ref.null any)
+ (func $return_eqref_null (result eqref)
+  (ref.null none)
  )
  (func $return_funcref_local (result funcref)
   (local $local_funcref funcref)
@@ -661,7 +562,7 @@
   (global.get $global_funcref)
  )
  (func $return_funcref_null (result funcref)
-  (ref.null func)
+  (ref.null nofunc)
  )
  (func $return_funcref_func (result funcref)
   (ref.func $foo)
@@ -674,41 +575,28 @@
   (global.get $global_anyref)
  )
  (func $return_anyref_null (result anyref)
-  (ref.null any)
+  (ref.null none)
  )
  (func $return_anyref2 (result anyref)
-  (local $local_externref anyref)
-  (local.get $local_externref)
+  (local $local_eqref eqref)
+  (local.get $local_eqref)
  )
  (func $return_anyref3 (result anyref)
-  (global.get $global_externref)
+  (global.get $global_eqref)
  )
  (func $return_anyref4 (result anyref)
-  (ref.null any)
+  (ref.null none)
  )
- (func $return_anyref5 (result anyref)
-  (local $local_funcref funcref)
-  (local.get $local_funcref)
- )
- (func $return_anyref6 (result anyref)
-  (global.get $global_funcref)
- )
- (func $return_anyref7 (result anyref)
-  (ref.null func)
- )
- (func $return_anyref8 (result anyref)
-  (ref.func $foo)
- )
- (func $returns_externref (result anyref)
-  (local $local_externref anyref)
+ (func $returns_eqref (result eqref)
+  (local $local_eqref eqref)
   (return
-   (local.get $local_externref)
+   (local.get $local_eqref)
   )
   (return
-   (global.get $global_externref)
+   (global.get $global_eqref)
   )
   (return
-   (ref.null any)
+   (ref.null none)
   )
  )
  (func $returns_funcref (result funcref)
@@ -723,7 +611,7 @@
    (ref.func $foo)
   )
   (return
-   (ref.null func)
+   (ref.null nofunc)
   )
  )
  (func $returns_anyref (result anyref)
@@ -735,32 +623,20 @@
    (global.get $global_anyref)
   )
   (return
-   (ref.null any)
+   (ref.null none)
   )
  )
  (func $returns_anyref2 (result anyref)
-  (local $local_externref anyref)
+  (local $local_eqref eqref)
   (local $local_funcref funcref)
   (return
-   (local.get $local_externref)
-  )
-  (return
-   (global.get $global_externref)
-  )
-  (return
-   (ref.null any)
+   (local.get $local_eqref)
   )
   (return
-   (local.get $local_funcref)
-  )
-  (return
-   (global.get $global_funcref)
-  )
-  (return
-   (ref.func $foo)
+   (global.get $global_eqref)
   )
   (return
-   (ref.null func)
+   (ref.null none)
   )
  )
  (func $ref-user
diff --git a/test/reference-types.wast.fromBinary b/test/reference-types.wast.fromBinary
index 37a57ac..670645c 100644
--- a/test/reference-types.wast.fromBinary
+++ b/test/reference-types.wast.fromBinary
@@ -3,25 +3,25 @@
  (type $sig_anyref (func (param anyref)))
  (type $sig_funcref (func (param funcref)))
  (type $none_=>_funcref (func (result funcref)))
+ (type $sig_eqref (func (param eqref)))
  (type $none_=>_none (func))
+ (type $none_=>_eqref (func (result eqref)))
  (type $i32_=>_none (func (param i32)))
- (type $anyref_=>_funcref (func (param anyref) (result funcref)))
- (import "env" "import_global" (global $import_global anyref))
- (import "env" "import_func" (func $import_func (param anyref) (result funcref)))
- (global $global_externref (mut anyref) (ref.null any))
- (global $global_funcref (mut funcref) (ref.null func))
+ (type $eqref_=>_funcref (func (param eqref) (result funcref)))
+ (import "env" "import_global" (global $import_global eqref))
+ (import "env" "import_func" (func $import_func (param eqref) (result funcref)))
+ (global $global_eqref (mut eqref) (ref.null none))
+ (global $global_funcref (mut funcref) (ref.null nofunc))
  (global $global_funcref_func (mut funcref) (ref.func $foo))
- (global $global_anyref (mut anyref) (ref.null any))
- (global $global_anyref2 (mut anyref) (ref.null any))
- (global $global_anyref3 (mut anyref) (ref.null func))
- (global $global_anyref4 (mut anyref) (ref.func $foo))
+ (global $global_anyref (mut anyref) (ref.null none))
+ (global $global_anyref2 (mut anyref) (ref.null none))
  (table $0 3 3 funcref)
- (elem (i32.const 0) $take_externref $take_funcref $take_anyref)
+ (elem (i32.const 0) $take_eqref $take_funcref $take_anyref)
  (elem declare func $foo $ref-taken-but-not-in-table)
- (tag $tag$0 (param i32))
+ (tag $e-i32 (param i32))
  (export "export_func" (func $import_func))
  (export "export_global" (global $import_global))
- (func $take_externref (param $0 anyref)
+ (func $take_eqref (param $0 eqref)
   (nop)
  )
  (func $take_funcref (param $0 funcref)
@@ -34,17 +34,17 @@
   (nop)
  )
  (func $test
-  (local $local_externref anyref)
-  (local $local_anyref anyref)
+  (local $local_eqref eqref)
   (local $local_funcref funcref)
-  (local.set $local_externref
-   (local.get $local_externref)
+  (local $local_anyref anyref)
+  (local.set $local_eqref
+   (local.get $local_eqref)
   )
-  (local.set $local_externref
-   (global.get $global_externref)
+  (local.set $local_eqref
+   (global.get $global_eqref)
   )
-  (local.set $local_externref
-   (ref.null any)
+  (local.set $local_eqref
+   (ref.null none)
   )
   (local.set $local_funcref
    (local.get $local_funcref)
@@ -53,7 +53,7 @@
    (global.get $global_funcref)
   )
   (local.set $local_funcref
-   (ref.null func)
+   (ref.null nofunc)
   )
   (local.set $local_funcref
    (ref.func $foo)
@@ -65,37 +65,25 @@
    (global.get $global_anyref)
   )
   (local.set $local_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (local.set $local_anyref
-   (local.get $local_externref)
+   (local.get $local_eqref)
   )
   (local.set $local_anyref
-   (global.get $global_externref)
+   (global.get $global_eqref)
   )
   (local.set $local_anyref
-   (ref.null any)
+   (ref.null none)
   )
-  (local.set $local_anyref
-   (local.get $local_funcref)
+  (global.set $global_eqref
+   (global.get $global_eqref)
   )
-  (local.set $local_anyref
-   (global.get $global_funcref)
+  (global.set $global_eqref
+   (local.get $local_eqref)
   )
-  (local.set $local_anyref
-   (ref.null func)
-  )
-  (local.set $local_anyref
-   (ref.func $foo)
-  )
-  (global.set $global_externref
-   (global.get $global_externref)
-  )
-  (global.set $global_externref
-   (local.get $local_externref)
-  )
-  (global.set $global_externref
-   (ref.null any)
+  (global.set $global_eqref
+   (ref.null none)
   )
   (global.set $global_funcref
    (global.get $global_funcref)
@@ -104,7 +92,7 @@
    (local.get $local_funcref)
   )
   (global.set $global_funcref
-   (ref.null func)
+   (ref.null nofunc)
   )
   (global.set $global_funcref
    (ref.func $foo)
@@ -116,37 +104,25 @@
    (local.get $local_anyref)
   )
   (global.set $global_anyref
-   (ref.null any)
+   (ref.null none)
   )
   (global.set $global_anyref
-   (global.get $global_externref)
+   (global.get $global_eqref)
   )
   (global.set $global_anyref
-   (local.get $local_externref)
+   (local.get $local_eqref)
   )
   (global.set $global_anyref
-   (ref.null any)
+   (ref.null none)
   )
-  (global.set $global_anyref
-   (global.get $global_funcref)
+  (call $take_eqref
+   (local.get $local_eqref)
   )
-  (global.set $global_anyref
-   (local.get $local_funcref)
-  )
-  (global.set $global_anyref
-   (ref.null func)
-  )
-  (global.set $global_anyref
-   (ref.func $foo)
+  (call $take_eqref
+   (global.get $global_eqref)
   )
-  (call $take_externref
-   (local.get $local_externref)
-  )
-  (call $take_externref
-   (global.get $global_externref)
-  )
-  (call $take_externref
-   (ref.null any)
+  (call $take_eqref
+   (ref.null none)
   )
   (call $take_funcref
    (local.get $local_funcref)
@@ -155,7 +131,7 @@
    (global.get $global_funcref)
   )
   (call $take_funcref
-   (ref.null func)
+   (ref.null nofunc)
   )
   (call $take_funcref
    (ref.func $foo)
@@ -167,39 +143,27 @@
    (global.get $global_anyref)
   )
   (call $take_anyref
-   (ref.null any)
-  )
-  (call $take_anyref
-   (local.get $local_externref)
-  )
-  (call $take_anyref
-   (global.get $global_externref)
-  )
-  (call $take_anyref
-   (ref.null any)
-  )
-  (call $take_anyref
-   (local.get $local_funcref)
+   (ref.null none)
   )
   (call $take_anyref
-   (global.get $global_funcref)
+   (local.get $local_eqref)
   )
   (call $take_anyref
-   (ref.null func)
+   (global.get $global_eqref)
   )
   (call $take_anyref
-   (ref.func $foo)
+   (ref.null none)
   )
-  (call_indirect $0 (type $sig_anyref)
-   (local.get $local_externref)
+  (call_indirect $0 (type $sig_eqref)
+   (local.get $local_eqref)
    (i32.const 0)
   )
-  (call_indirect $0 (type $sig_anyref)
-   (global.get $global_externref)
+  (call_indirect $0 (type $sig_eqref)
+   (global.get $global_eqref)
    (i32.const 0)
   )
-  (call_indirect $0 (type $sig_anyref)
-   (ref.null any)
+  (call_indirect $0 (type $sig_eqref)
+   (ref.null none)
    (i32.const 0)
   )
   (call_indirect $0 (type $sig_funcref)
@@ -211,7 +175,7 @@
    (i32.const 1)
   )
   (call_indirect $0 (type $sig_funcref)
-   (ref.null func)
+   (ref.null nofunc)
    (i32.const 1)
   )
   (call_indirect $0 (type $sig_funcref)
@@ -227,57 +191,41 @@
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (ref.null any)
+   (ref.null none)
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (local.get $local_externref)
+   (local.get $local_eqref)
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (global.get $global_externref)
+   (global.get $global_eqref)
    (i32.const 3)
   )
   (call_indirect $0 (type $sig_anyref)
-   (ref.null any)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (local.get $local_funcref)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (global.get $global_funcref)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (ref.null func)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $sig_anyref)
-   (ref.func $foo)
+   (ref.null none)
    (i32.const 3)
   )
   (drop
-   (block $label$1 (result anyref)
+   (block $label$1 (result eqref)
     (br_if $label$1
-     (local.get $local_externref)
+     (local.get $local_eqref)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $label$2 (result anyref)
+   (block $label$2 (result eqref)
     (br_if $label$2
-     (global.get $global_externref)
+     (global.get $global_eqref)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $label$3 (result anyref)
+   (block $label$3 (result eqref)
     (br_if $label$3
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
@@ -301,7 +249,7 @@
   (drop
    (block $label$6 (result funcref)
     (br_if $label$6
-     (ref.null func)
+     (ref.null nofunc)
      (i32.const 1)
     )
    )
@@ -333,7 +281,7 @@
   (drop
    (block $label$10 (result anyref)
     (br_if $label$10
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
@@ -341,7 +289,7 @@
   (drop
    (block $label$11 (result anyref)
     (br_if $label$11
-     (local.get $local_externref)
+     (local.get $local_eqref)
      (i32.const 1)
     )
    )
@@ -349,225 +297,183 @@
   (drop
    (block $label$12 (result anyref)
     (br_if $label$12
-     (local.get $local_funcref)
-     (i32.const 1)
-    )
-   )
-  )
-  (drop
-   (block $label$13 (result anyref)
-    (br_if $label$13
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $label$14 (result anyref)
-    (br_if $label$14
-     (ref.null func)
-     (i32.const 1)
-    )
+   (loop $label$13 (result eqref)
+    (local.get $local_eqref)
    )
   )
   (drop
-   (block $label$15 (result anyref)
-    (br_if $label$15
-     (ref.func $foo)
-     (i32.const 1)
-    )
+   (loop $label$14 (result eqref)
+    (global.get $global_eqref)
    )
   )
   (drop
-   (loop $label$16 (result anyref)
-    (local.get $local_externref)
+   (loop $label$15 (result eqref)
+    (ref.null none)
    )
   )
   (drop
-   (loop $label$17 (result anyref)
-    (global.get $global_externref)
-   )
-  )
-  (drop
-   (loop $label$18 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $label$19 (result funcref)
+   (loop $label$16 (result funcref)
     (local.get $local_funcref)
    )
   )
   (drop
-   (loop $label$20 (result funcref)
+   (loop $label$17 (result funcref)
     (global.get $global_funcref)
    )
   )
   (drop
-   (loop $label$21 (result funcref)
-    (ref.null func)
+   (loop $label$18 (result funcref)
+    (ref.null nofunc)
    )
   )
   (drop
-   (loop $label$22 (result funcref)
+   (loop $label$19 (result funcref)
     (ref.func $foo)
    )
   )
   (drop
-   (loop $label$23 (result anyref)
+   (loop $label$20 (result anyref)
     (local.get $local_anyref)
    )
   )
   (drop
-   (loop $label$24 (result anyref)
+   (loop $label$21 (result anyref)
     (global.get $global_anyref)
    )
   )
   (drop
-   (loop $label$25 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $label$26 (result anyref)
-    (local.get $local_externref)
-   )
-  )
-  (drop
-   (loop $label$27 (result anyref)
-    (global.get $global_externref)
-   )
-  )
-  (drop
-   (loop $label$28 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $label$29 (result anyref)
-    (local.get $local_funcref)
+   (loop $label$22 (result anyref)
+    (ref.null none)
    )
   )
   (drop
-   (loop $label$30 (result anyref)
-    (global.get $global_funcref)
+   (loop $label$23 (result anyref)
+    (local.get $local_eqref)
    )
   )
   (drop
-   (loop $label$31 (result anyref)
-    (ref.null func)
+   (loop $label$24 (result anyref)
+    (global.get $global_eqref)
    )
   )
   (drop
-   (loop $label$32 (result anyref)
-    (ref.func $foo)
+   (loop $label$25 (result anyref)
+    (ref.null none)
    )
   )
   (drop
-   (if (result anyref)
+   (if (result eqref)
     (i32.const 1)
-    (local.get $local_externref)
-    (ref.null any)
+    (local.get $local_eqref)
+    (ref.null none)
    )
   )
   (drop
    (if (result funcref)
     (i32.const 1)
     (local.get $local_funcref)
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
     (local.get $local_anyref)
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (local.get $local_externref)
-    (local.get $local_funcref)
+    (local.get $local_eqref)
+    (local.get $local_eqref)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (ref.null any)
-    (ref.null func)
+    (ref.null none)
+    (ref.null none)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (ref.func $foo)
-    (ref.null any)
+    (i31.new
+     (i32.const 0)
+    )
+    (ref.null none)
    )
   )
   (drop
-   (try $label$47 (result anyref)
+   (try $label$40 (result eqref)
     (do
-     (local.get $local_externref)
+     (local.get $local_eqref)
     )
-    (catch $tag$0
+    (catch $e-i32
      (drop
       (pop i32)
      )
-     (ref.null any)
+     (ref.null none)
     )
    )
   )
   (drop
-   (try $label$50 (result funcref)
+   (try $label$43 (result funcref)
     (do
      (ref.func $foo)
     )
-    (catch $tag$0
+    (catch $e-i32
      (drop
       (pop i32)
      )
-     (ref.null func)
+     (ref.null nofunc)
     )
    )
   )
   (drop
-   (try $label$53 (result anyref)
+   (try $label$46 (result anyref)
     (do
-     (local.get $local_externref)
+     (local.get $local_eqref)
     )
-    (catch $tag$0
+    (catch $e-i32
      (drop
       (pop i32)
      )
-     (ref.func $foo)
+     (ref.null none)
     )
    )
   )
   (drop
-   (try $label$56 (result anyref)
+   (try $label$49 (result anyref)
     (do
-     (ref.func $foo)
+     (ref.null none)
     )
-    (catch $tag$0
+    (catch $e-i32
      (drop
       (pop i32)
      )
-     (local.get $local_externref)
+     (local.get $local_eqref)
     )
    )
   )
   (drop
-   (select (result anyref)
-    (local.get $local_externref)
-    (ref.null any)
+   (select (result eqref)
+    (local.get $local_eqref)
+    (ref.null none)
     (i32.const 1)
    )
   )
   (drop
    (select (result funcref)
     (local.get $local_funcref)
-    (ref.null func)
+    (ref.null nofunc)
     (i32.const 1)
    )
   )
@@ -580,31 +486,26 @@
   )
   (drop
    (select (result anyref)
-    (local.get $local_externref)
-    (local.get $local_funcref)
-    (i32.const 1)
-   )
-  )
-  (drop
-   (select (result anyref)
-    (local.get $local_funcref)
-    (local.get $local_externref)
+    (local.get $local_eqref)
+    (i31.new
+     (i32.const 0)
+    )
     (i32.const 1)
    )
   )
   (drop
    (ref.is_null
-    (local.get $local_externref)
+    (local.get $local_eqref)
    )
   )
   (drop
    (ref.is_null
-    (global.get $global_externref)
+    (global.get $global_eqref)
    )
   )
   (drop
    (ref.is_null
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
@@ -619,7 +520,7 @@
   )
   (drop
    (ref.is_null
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
@@ -639,19 +540,19 @@
   )
   (drop
    (ref.is_null
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
- (func $return_externref_local (result anyref)
-  (local $local_externref anyref)
-  (local.get $local_externref)
+ (func $return_eqref_local (result eqref)
+  (local $local_eqref eqref)
+  (local.get $local_eqref)
  )
- (func $return_externref_global (result anyref)
-  (global.get $global_externref)
+ (func $return_eqref_global (result eqref)
+  (global.get $global_eqref)
  )
- (func $return_externref_null (result anyref)
-  (ref.null any)
+ (func $return_eqref_null (result eqref)
+  (ref.null none)
  )
  (func $return_funcref_local (result funcref)
   (local $local_funcref funcref)
@@ -661,7 +562,7 @@
   (global.get $global_funcref)
  )
  (func $return_funcref_null (result funcref)
-  (ref.null func)
+  (ref.null nofunc)
  )
  (func $return_funcref_func (result funcref)
   (ref.func $foo)
@@ -674,35 +575,22 @@
   (global.get $global_anyref)
  )
  (func $return_anyref_null (result anyref)
-  (ref.null any)
+  (ref.null none)
  )
  (func $return_anyref2 (result anyref)
-  (local $local_externref anyref)
-  (local.get $local_externref)
+  (local $local_eqref eqref)
+  (local.get $local_eqref)
  )
  (func $return_anyref3 (result anyref)
-  (global.get $global_externref)
+  (global.get $global_eqref)
  )
  (func $return_anyref4 (result anyref)
-  (ref.null any)
- )
- (func $return_anyref5 (result anyref)
-  (local $local_funcref funcref)
-  (local.get $local_funcref)
- )
- (func $return_anyref6 (result anyref)
-  (global.get $global_funcref)
- )
- (func $return_anyref7 (result anyref)
-  (ref.null func)
- )
- (func $return_anyref8 (result anyref)
-  (ref.func $foo)
+  (ref.null none)
  )
- (func $returns_externref (result anyref)
-  (local $local_externref anyref)
+ (func $returns_eqref (result eqref)
+  (local $local_eqref eqref)
   (return
-   (local.get $local_externref)
+   (local.get $local_eqref)
   )
  )
  (func $returns_funcref (result funcref)
@@ -718,10 +606,10 @@
   )
  )
  (func $returns_anyref2 (result anyref)
-  (local $local_externref anyref)
+  (local $local_eqref eqref)
   (local $local_funcref funcref)
   (return
-   (local.get $local_externref)
+   (local.get $local_eqref)
   )
  )
  (func $ref-user
diff --git a/test/reference-types.wast.fromBinary.noDebugInfo b/test/reference-types.wast.fromBinary.noDebugInfo
index 42ad402..85ebc9e 100644
--- a/test/reference-types.wast.fromBinary.noDebugInfo
+++ b/test/reference-types.wast.fromBinary.noDebugInfo
@@ -3,25 +3,25 @@
  (type $anyref_=>_none (func (param anyref)))
  (type $funcref_=>_none (func (param funcref)))
  (type $none_=>_funcref (func (result funcref)))
+ (type $eqref_=>_none (func (param eqref)))
  (type $none_=>_none (func))
+ (type $none_=>_eqref (func (result eqref)))
  (type $i32_=>_none (func (param i32)))
- (type $anyref_=>_funcref (func (param anyref) (result funcref)))
- (import "env" "import_global" (global $gimport$0 anyref))
- (import "env" "import_func" (func $fimport$0 (param anyref) (result funcref)))
- (global $global$0 (mut anyref) (ref.null any))
- (global $global$1 (mut funcref) (ref.null func))
+ (type $eqref_=>_funcref (func (param eqref) (result funcref)))
+ (import "env" "import_global" (global $gimport$0 eqref))
+ (import "env" "import_func" (func $fimport$0 (param eqref) (result funcref)))
+ (global $global$0 (mut eqref) (ref.null none))
+ (global $global$1 (mut funcref) (ref.null nofunc))
  (global $global$2 (mut funcref) (ref.func $3))
- (global $global$3 (mut anyref) (ref.null any))
- (global $global$4 (mut anyref) (ref.null any))
- (global $global$5 (mut anyref) (ref.null func))
- (global $global$6 (mut anyref) (ref.func $3))
+ (global $global$3 (mut anyref) (ref.null none))
+ (global $global$4 (mut anyref) (ref.null none))
  (table $0 3 3 funcref)
  (elem (i32.const 0) $0 $1 $2)
- (elem declare func $27 $3)
+ (elem declare func $23 $3)
  (tag $tag$0 (param i32))
  (export "export_func" (func $fimport$0))
  (export "export_global" (global $gimport$0))
- (func $0 (param $0 anyref)
+ (func $0 (param $0 eqref)
   (nop)
  )
  (func $1 (param $0 funcref)
@@ -34,9 +34,9 @@
   (nop)
  )
  (func $4
-  (local $0 anyref)
-  (local $1 anyref)
-  (local $2 funcref)
+  (local $0 eqref)
+  (local $1 funcref)
+  (local $2 anyref)
   (local.set $0
    (local.get $0)
   )
@@ -44,49 +44,37 @@
    (global.get $global$0)
   )
   (local.set $0
-   (ref.null any)
-  )
-  (local.set $2
-   (local.get $2)
-  )
-  (local.set $2
-   (global.get $global$1)
-  )
-  (local.set $2
-   (ref.null func)
-  )
-  (local.set $2
-   (ref.func $3)
+   (ref.null none)
   )
   (local.set $1
    (local.get $1)
   )
   (local.set $1
-   (global.get $global$3)
+   (global.get $global$1)
   )
   (local.set $1
-   (ref.null any)
+   (ref.null nofunc)
   )
   (local.set $1
-   (local.get $0)
+   (ref.func $3)
   )
-  (local.set $1
-   (global.get $global$0)
+  (local.set $2
+   (local.get $2)
   )
-  (local.set $1
-   (ref.null any)
+  (local.set $2
+   (global.get $global$3)
   )
-  (local.set $1
-   (local.get $2)
+  (local.set $2
+   (ref.null none)
   )
-  (local.set $1
-   (global.get $global$1)
+  (local.set $2
+   (local.get $0)
   )
-  (local.set $1
-   (ref.null func)
+  (local.set $2
+   (global.get $global$0)
   )
-  (local.set $1
-   (ref.func $3)
+  (local.set $2
+   (ref.null none)
   )
   (global.set $global$0
    (global.get $global$0)
@@ -95,16 +83,16 @@
    (local.get $0)
   )
   (global.set $global$0
-   (ref.null any)
+   (ref.null none)
   )
   (global.set $global$1
    (global.get $global$1)
   )
   (global.set $global$1
-   (local.get $2)
+   (local.get $1)
   )
   (global.set $global$1
-   (ref.null func)
+   (ref.null nofunc)
   )
   (global.set $global$1
    (ref.func $3)
@@ -113,10 +101,10 @@
    (global.get $global$3)
   )
   (global.set $global$3
-   (local.get $1)
+   (local.get $2)
   )
   (global.set $global$3
-   (ref.null any)
+   (ref.null none)
   )
   (global.set $global$3
    (global.get $global$0)
@@ -125,19 +113,7 @@
    (local.get $0)
   )
   (global.set $global$3
-   (ref.null any)
-  )
-  (global.set $global$3
-   (global.get $global$1)
-  )
-  (global.set $global$3
-   (local.get $2)
-  )
-  (global.set $global$3
-   (ref.null func)
-  )
-  (global.set $global$3
-   (ref.func $3)
+   (ref.null none)
   )
   (call $0
    (local.get $0)
@@ -146,28 +122,28 @@
    (global.get $global$0)
   )
   (call $0
-   (ref.null any)
+   (ref.null none)
   )
   (call $1
-   (local.get $2)
+   (local.get $1)
   )
   (call $1
    (global.get $global$1)
   )
   (call $1
-   (ref.null func)
+   (ref.null nofunc)
   )
   (call $1
    (ref.func $3)
   )
   (call $2
-   (local.get $1)
+   (local.get $2)
   )
   (call $2
    (global.get $global$3)
   )
   (call $2
-   (ref.null any)
+   (ref.null none)
   )
   (call $2
    (local.get $0)
@@ -176,34 +152,22 @@
    (global.get $global$0)
   )
   (call $2
-   (ref.null any)
+   (ref.null none)
   )
-  (call $2
-   (local.get $2)
-  )
-  (call $2
-   (global.get $global$1)
-  )
-  (call $2
-   (ref.null func)
-  )
-  (call $2
-   (ref.func $3)
-  )
-  (call_indirect $0 (type $anyref_=>_none)
+  (call_indirect $0 (type $eqref_=>_none)
    (local.get $0)
    (i32.const 0)
   )
-  (call_indirect $0 (type $anyref_=>_none)
+  (call_indirect $0 (type $eqref_=>_none)
    (global.get $global$0)
    (i32.const 0)
   )
-  (call_indirect $0 (type $anyref_=>_none)
-   (ref.null any)
+  (call_indirect $0 (type $eqref_=>_none)
+   (ref.null none)
    (i32.const 0)
   )
   (call_indirect $0 (type $funcref_=>_none)
-   (local.get $2)
+   (local.get $1)
    (i32.const 1)
   )
   (call_indirect $0 (type $funcref_=>_none)
@@ -211,7 +175,7 @@
    (i32.const 1)
   )
   (call_indirect $0 (type $funcref_=>_none)
-   (ref.null func)
+   (ref.null nofunc)
    (i32.const 1)
   )
   (call_indirect $0 (type $funcref_=>_none)
@@ -219,7 +183,7 @@
    (i32.const 1)
   )
   (call_indirect $0 (type $anyref_=>_none)
-   (local.get $1)
+   (local.get $2)
    (i32.const 3)
   )
   (call_indirect $0 (type $anyref_=>_none)
@@ -227,7 +191,7 @@
    (i32.const 3)
   )
   (call_indirect $0 (type $anyref_=>_none)
-   (ref.null any)
+   (ref.null none)
    (i32.const 3)
   )
   (call_indirect $0 (type $anyref_=>_none)
@@ -239,27 +203,11 @@
    (i32.const 3)
   )
   (call_indirect $0 (type $anyref_=>_none)
-   (ref.null any)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $anyref_=>_none)
-   (local.get $2)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $anyref_=>_none)
-   (global.get $global$1)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $anyref_=>_none)
-   (ref.null func)
-   (i32.const 3)
-  )
-  (call_indirect $0 (type $anyref_=>_none)
-   (ref.func $3)
+   (ref.null none)
    (i32.const 3)
   )
   (drop
-   (block $label$1 (result anyref)
+   (block $label$1 (result eqref)
     (br_if $label$1
      (local.get $0)
      (i32.const 1)
@@ -267,7 +215,7 @@
    )
   )
   (drop
-   (block $label$2 (result anyref)
+   (block $label$2 (result eqref)
     (br_if $label$2
      (global.get $global$0)
      (i32.const 1)
@@ -275,9 +223,9 @@
    )
   )
   (drop
-   (block $label$3 (result anyref)
+   (block $label$3 (result eqref)
     (br_if $label$3
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
@@ -285,7 +233,7 @@
   (drop
    (block $label$4 (result funcref)
     (br_if $label$4
-     (local.get $2)
+     (local.get $1)
      (i32.const 1)
     )
    )
@@ -301,7 +249,7 @@
   (drop
    (block $label$6 (result funcref)
     (br_if $label$6
-     (ref.null func)
+     (ref.null nofunc)
      (i32.const 1)
     )
    )
@@ -317,7 +265,7 @@
   (drop
    (block $label$8 (result anyref)
     (br_if $label$8
-     (local.get $1)
+     (local.get $2)
      (i32.const 1)
     )
    )
@@ -333,7 +281,7 @@
   (drop
    (block $label$10 (result anyref)
     (br_if $label$10
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
@@ -349,164 +297,122 @@
   (drop
    (block $label$12 (result anyref)
     (br_if $label$12
-     (local.get $2)
-     (i32.const 1)
-    )
-   )
-  )
-  (drop
-   (block $label$13 (result anyref)
-    (br_if $label$13
-     (ref.null any)
+     (ref.null none)
      (i32.const 1)
     )
    )
   )
   (drop
-   (block $label$14 (result anyref)
-    (br_if $label$14
-     (ref.null func)
-     (i32.const 1)
-    )
-   )
-  )
-  (drop
-   (block $label$15 (result anyref)
-    (br_if $label$15
-     (ref.func $3)
-     (i32.const 1)
-    )
-   )
-  )
-  (drop
-   (loop $label$16 (result anyref)
+   (loop $label$13 (result eqref)
     (local.get $0)
    )
   )
   (drop
-   (loop $label$17 (result anyref)
+   (loop $label$14 (result eqref)
     (global.get $global$0)
    )
   )
   (drop
-   (loop $label$18 (result anyref)
-    (ref.null any)
+   (loop $label$15 (result eqref)
+    (ref.null none)
    )
   )
   (drop
-   (loop $label$19 (result funcref)
-    (local.get $2)
+   (loop $label$16 (result funcref)
+    (local.get $1)
    )
   )
   (drop
-   (loop $label$20 (result funcref)
+   (loop $label$17 (result funcref)
     (global.get $global$1)
    )
   )
   (drop
-   (loop $label$21 (result funcref)
-    (ref.null func)
+   (loop $label$18 (result funcref)
+    (ref.null nofunc)
    )
   )
   (drop
-   (loop $label$22 (result funcref)
+   (loop $label$19 (result funcref)
     (ref.func $3)
    )
   )
   (drop
-   (loop $label$23 (result anyref)
-    (local.get $1)
+   (loop $label$20 (result anyref)
+    (local.get $2)
    )
   )
   (drop
-   (loop $label$24 (result anyref)
+   (loop $label$21 (result anyref)
     (global.get $global$3)
    )
   )
   (drop
-   (loop $label$25 (result anyref)
-    (ref.null any)
+   (loop $label$22 (result anyref)
+    (ref.null none)
    )
   )
   (drop
-   (loop $label$26 (result anyref)
+   (loop $label$23 (result anyref)
     (local.get $0)
    )
   )
   (drop
-   (loop $label$27 (result anyref)
+   (loop $label$24 (result anyref)
     (global.get $global$0)
    )
   )
   (drop
-   (loop $label$28 (result anyref)
-    (ref.null any)
-   )
-  )
-  (drop
-   (loop $label$29 (result anyref)
-    (local.get $2)
-   )
-  )
-  (drop
-   (loop $label$30 (result anyref)
-    (global.get $global$1)
-   )
-  )
-  (drop
-   (loop $label$31 (result anyref)
-    (ref.null func)
-   )
-  )
-  (drop
-   (loop $label$32 (result anyref)
-    (ref.func $3)
+   (loop $label$25 (result anyref)
+    (ref.null none)
    )
   )
   (drop
-   (if (result anyref)
+   (if (result eqref)
     (i32.const 1)
     (local.get $0)
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
    (if (result funcref)
     (i32.const 1)
-    (local.get $2)
-    (ref.null func)
+    (local.get $1)
+    (ref.null nofunc)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (local.get $1)
-    (ref.null any)
+    (local.get $2)
+    (ref.null none)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
     (local.get $0)
-    (local.get $2)
+    (local.get $0)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (ref.null any)
-    (ref.null func)
+    (ref.null none)
+    (ref.null none)
    )
   )
   (drop
    (if (result anyref)
     (i32.const 1)
-    (ref.func $3)
-    (ref.null any)
+    (i31.new
+     (i32.const 0)
+    )
+    (ref.null none)
    )
   )
   (drop
-   (try $label$47 (result anyref)
+   (try $label$40 (result eqref)
     (do
      (local.get $0)
     )
@@ -514,12 +420,12 @@
      (drop
       (pop i32)
      )
-     (ref.null any)
+     (ref.null none)
     )
    )
   )
   (drop
-   (try $label$50 (result funcref)
+   (try $label$43 (result funcref)
     (do
      (ref.func $3)
     )
@@ -527,12 +433,12 @@
      (drop
       (pop i32)
      )
-     (ref.null func)
+     (ref.null nofunc)
     )
    )
   )
   (drop
-   (try $label$53 (result anyref)
+   (try $label$46 (result anyref)
     (do
      (local.get $0)
     )
@@ -540,14 +446,14 @@
      (drop
       (pop i32)
      )
-     (ref.func $3)
+     (ref.null none)
     )
    )
   )
   (drop
-   (try $label$56 (result anyref)
+   (try $label$49 (result anyref)
     (do
-     (ref.func $3)
+     (ref.null none)
     )
     (catch $tag$0
      (drop
@@ -558,16 +464,16 @@
    )
   )
   (drop
-   (select (result anyref)
+   (select (result eqref)
     (local.get $0)
-    (ref.null any)
+    (ref.null none)
     (i32.const 1)
    )
   )
   (drop
    (select (result funcref)
-    (local.get $2)
-    (ref.null func)
+    (local.get $1)
+    (ref.null nofunc)
     (i32.const 1)
    )
   )
@@ -581,14 +487,9 @@
   (drop
    (select (result anyref)
     (local.get $0)
-    (local.get $2)
-    (i32.const 1)
-   )
-  )
-  (drop
-   (select (result anyref)
-    (local.get $2)
-    (local.get $0)
+    (i31.new
+     (i32.const 0)
+    )
     (i32.const 1)
    )
   )
@@ -604,12 +505,12 @@
   )
   (drop
    (ref.is_null
-    (ref.null any)
+    (ref.null none)
    )
   )
   (drop
    (ref.is_null
-    (local.get $2)
+    (local.get $1)
    )
   )
   (drop
@@ -619,7 +520,7 @@
   )
   (drop
    (ref.is_null
-    (ref.null func)
+    (ref.null nofunc)
    )
   )
   (drop
@@ -629,7 +530,7 @@
   )
   (drop
    (ref.is_null
-    (local.get $1)
+    (local.get $2)
    )
   )
   (drop
@@ -639,19 +540,19 @@
   )
   (drop
    (ref.is_null
-    (ref.null any)
+    (ref.null none)
    )
   )
  )
- (func $5 (result anyref)
-  (local $0 anyref)
+ (func $5 (result eqref)
+  (local $0 eqref)
   (local.get $0)
  )
- (func $6 (result anyref)
+ (func $6 (result eqref)
   (global.get $global$0)
  )
- (func $7 (result anyref)
-  (ref.null any)
+ (func $7 (result eqref)
+  (ref.null none)
  )
  (func $8 (result funcref)
   (local $0 funcref)
@@ -661,7 +562,7 @@
   (global.get $global$1)
  )
  (func $10 (result funcref)
-  (ref.null func)
+  (ref.null nofunc)
  )
  (func $11 (result funcref)
   (ref.func $3)
@@ -674,62 +575,49 @@
   (global.get $global$3)
  )
  (func $14 (result anyref)
-  (ref.null any)
+  (ref.null none)
  )
  (func $15 (result anyref)
-  (local $0 anyref)
+  (local $0 eqref)
   (local.get $0)
  )
  (func $16 (result anyref)
   (global.get $global$0)
  )
  (func $17 (result anyref)
-  (ref.null any)
- )
- (func $18 (result anyref)
-  (local $0 funcref)
-  (local.get $0)
+  (ref.null none)
  )
- (func $19 (result anyref)
-  (global.get $global$1)
- )
- (func $20 (result anyref)
-  (ref.null func)
- )
- (func $21 (result anyref)
-  (ref.func $3)
- )
- (func $22 (result anyref)
-  (local $0 anyref)
+ (func $18 (result eqref)
+  (local $0 eqref)
   (return
    (local.get $0)
   )
  )
- (func $23 (result funcref)
+ (func $19 (result funcref)
   (local $0 funcref)
   (return
    (local.get $0)
   )
  )
- (func $24 (result anyref)
+ (func $20 (result anyref)
   (local $0 anyref)
   (return
    (local.get $0)
   )
  )
- (func $25 (result anyref)
-  (local $0 anyref)
+ (func $21 (result anyref)
+  (local $0 eqref)
   (local $1 funcref)
   (return
    (local.get $0)
   )
  )
- (func $26
+ (func $22
   (drop
-   (ref.func $27)
+   (ref.func $23)
   )
  )
- (func $27
+ (func $23
   (nop)
  )
 )
diff --git a/test/spec/README.md b/test/spec/README.md
index 3191514..f9f5ecb 100644
--- a/test/spec/README.md
+++ b/test/spec/README.md
@@ -2,10 +2,12 @@ This directory contains tests for the core WebAssembly semantics, as described i
 
 Tests are written in the [S-Expression script format](https://github.com/WebAssembly/spec/blob/master/interpreter/README.md#s-expression-syntax) defined by the interpreter.
 
-The test suite can be run with the spec interpreter as follows:
+To execute all spec tests, run the following command from the binaryen top-level directory:
 ```
-./run.py --wasm <path-to-wasm-interpreter>
+./check.py spec
 ```
-where the path points to the spec interpreter executable (or a tool that understands similar options). If the binary is in the working directory, this option can be omitted.
 
-In addition, the option `--js <path-to-js-interpreter>` can be given to point to a stand-alone JavaScript interpreter supporting the WebAssembly API. If provided, all tests are also executed in JavaScript.
+Individual spec tests may be executed by running the following command from the binaryen top-level directory:
+```
+bin/wasm-shell [path to spec test]
+```
diff --git a/test/spec/array-new-data.wast b/test/spec/array-new-data.wast
new file mode 100644
index 0000000..f735f7c
--- /dev/null
+++ b/test/spec/array-new-data.wast
@@ -0,0 +1,91 @@
+(module
+  (type $vec (array i8))
+  (type $mvec (array (mut i8)))
+
+  (data "\00\01\02\03\04")
+
+  (func $new (export "new") (result (ref $vec))
+    (array.new_data $vec 0 (i32.const 1) (i32.const 3))
+  )
+
+  (func $get (param $i i32) (param $v (ref $vec)) (result i32)
+    (array.get_u $vec (local.get $v) (local.get $i))
+  )
+  (func (export "get") (param $i i32) (result i32)
+    (call $get (local.get $i) (call $new))
+  )
+
+  (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32)
+    (array.set $mvec (local.get $v) (local.get $i) (local.get $y))
+    (array.get_u $mvec (local.get $v) (local.get $i))
+  )
+  (func (export "set_get") (param $i i32) (param $y i32) (result i32)
+    (call $set_get (local.get $i)
+      (array.new_data $mvec 0 (i32.const 1) (i32.const 3))
+      (local.get $y)
+    )
+  )
+
+  (func $len (param $v (ref array)) (result i32)
+    (array.len (local.get $v))
+  )
+  (func (export "len") (result i32)
+    (call $len (call $new))
+  )
+)
+
+(assert_return (invoke "get" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "set_get" (i32.const 1) (i32.const 7)) (i32.const 7))
+(assert_return (invoke "len") (i32.const 3))
+
+(module
+  (type $vec (array i32))
+  (type $mvec (array (mut i8)))
+
+  (data "\00\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00")
+
+  (func $new (export "new") (result (ref $vec))
+    (array.new_data $vec 0 (i32.const 1) (i32.const 3))
+  )
+
+  (func $get (param $i i32) (param $v (ref $vec)) (result i32)
+    (array.get $vec (local.get $v) (local.get $i))
+  )
+  (func (export "get") (param $i i32) (result i32)
+    (call $get (local.get $i) (call $new))
+  )
+
+  (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32)
+    (array.set $mvec (local.get $v) (local.get $i) (local.get $y))
+    (array.get $mvec (local.get $v) (local.get $i))
+  )
+  (func (export "set_get") (param $i i32) (param $y i32) (result i32)
+    (call $set_get (local.get $i)
+      (array.new_data $mvec 0 (i32.const 1) (i32.const 3))
+      (local.get $y)
+    )
+  )
+
+  (func $len (param $v (ref array)) (result i32)
+    (array.len (local.get $v))
+  )
+  (func (export "len") (result i32)
+    (call $len (call $new))
+  )
+)
+
+(assert_return (invoke "get" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "set_get" (i32.const 1) (i32.const 7)) (i32.const 7))
+(assert_return (invoke "len") (i32.const 3))
+
+(module
+  (type $vec (array i32))
+
+  (data "")
+
+  (func $new-huge (export "new-huge") (result (ref $vec))
+    (array.new_data $vec 0 (i32.const 4) (i32.const -1))
+  )
+)
+
+(assert_trap (invoke "new-huge") "out of bounds segment access in array.new_data")
diff --git a/test/spec/array-new-elem.wast b/test/spec/array-new-elem.wast
new file mode 100644
index 0000000..d20d810
--- /dev/null
+++ b/test/spec/array-new-elem.wast
@@ -0,0 +1,60 @@
+(module
+  (type $vec (array funcref))
+  (type $mvec (array (mut funcref)))
+  (type $f (func (result i32)))
+
+  (elem func $ret0 $ret1 $ret2 $ret3 $ret4)
+
+  (func $ret0 (type $f) (i32.const 0))
+  (func $ret1 (type $f) (i32.const 1))
+  (func $ret2 (type $f) (i32.const 2))
+  (func $ret3 (type $f) (i32.const 3))
+  (func $ret4 (type $f) (i32.const 4))
+
+  (func $new (export "new") (result (ref $vec))
+    (array.new_elem $vec 0 (i32.const 1) (i32.const 3))
+  )
+
+  (func $get (param $i i32) (param $v (ref $vec)) (result i32)
+    (call_ref $f (ref.cast_static $f (array.get $vec (local.get $v) (local.get $i))))
+  )
+  (func (export "get") (param $i i32) (result i32)
+    (call $get (local.get $i) (call $new))
+  )
+
+  (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32)
+    (array.set $mvec (local.get $v) (local.get $i) (array.get $mvec (local.get $v) (local.get $y)))
+    (call_ref $f (ref.cast_static $f (array.get $mvec (local.get $v) (local.get $i))))
+  )
+  (func (export "set_get") (param $i i32) (param $y i32) (result i32)
+    (call $set_get
+      (local.get $i)
+      (array.new_elem $mvec 0 (i32.const 1) (i32.const 3))
+      (local.get $y)
+    )
+  )
+
+  (func $len (param $v (ref array)) (result i32)
+    (array.len (local.get $v))
+  )
+  (func (export "len") (result i32)
+    (call $len (call $new))
+  )
+)
+
+(assert_return (invoke "get" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "get" (i32.const 1)) (i32.const 2))
+(assert_return (invoke "set_get" (i32.const 0) (i32.const 2)) (i32.const 3))
+(assert_return (invoke "len") (i32.const 3))
+
+(module
+  (type $vec (array funcref))
+
+  (elem func)
+
+  (func $new-huge (export "new-huge") (result (ref $vec))
+    (array.new_elem $vec 0 (i32.const 1) (i32.const -1))
+  )
+)
+
+(assert_trap (invoke "new-huge") "out of bounds segment access in array.new_data")
diff --git a/test/spec/array.wast b/test/spec/array.wast
index 2434313..fd15166 100644
--- a/test/spec/array.wast
+++ b/test/spec/array.wast
@@ -1,5 +1,3 @@
-;; XXX BINARYEN: rename array.new_default => array.new_default_with_rtt
-
 ;; Type syntax
 
 (module
@@ -13,8 +11,6 @@
   (type (array (ref data)))
   (type (array (ref 0)))
   (type (array (ref null 1)))
-  (type (array (rtt 1)))
-  (type (array (rtt 10 1)))
   (type (array (mut i8)))
   (type (array (mut i16)))
   (type (array (mut i32)))
@@ -25,8 +21,6 @@
   (type (array (mut (ref data))))
   (type (array (mut (ref 0))))
   (type (array (mut (ref null i31))))
-  (type (array (mut (rtt 0))))
-  (type (array (mut (rtt 10 0))))
 )
 
 
@@ -70,7 +64,7 @@
   )
   (func (export "get") (param $i i32) (result f32)
     (call $get (local.get $i)
-      (array.new_default_with_rtt $vec (i32.const 3) (rtt.canon $vec))
+      (array.new_default $vec (i32.const 3))
     )
   )
 
@@ -80,7 +74,7 @@
   )
   (func (export "set_get") (param $i i32) (param $y f32) (result f32)
     (call $set_get (local.get $i)
-      (array.new_default_with_rtt $mvec (i32.const 3) (rtt.canon $mvec))
+      (array.new_default $mvec (i32.const 3))
       (local.get $y)
     )
   )
@@ -89,7 +83,7 @@
     (array.len $vec (local.get $v))
   )
   (func (export "len") (result i32)
-    (call $len (array.new_default_with_rtt $vec (i32.const 3) (rtt.canon $vec)))
+    (call $len (array.new_default $vec (i32.const 3)))
   )
 )
 
@@ -130,16 +124,8 @@
   (module
     (type $t (array i32))
     (func (export "array.new-null")
-      (local (ref null (rtt $t))) (drop (array.new_default_with_rtt $t (i32.const 1) (i32.const 3) (local.get 0)))
-    )
-  )
-  "type mismatch"
-)
-(assert_invalid
-  (module
-    (type $t (array (mut i32)))
-    (func (export "array.new_default_with_rtt-null")
-      (local (ref null (rtt $t))) (drop (array.new_default_with_rtt $t (i32.const 3) (local.get 0)))
+      (local i64)
+      (drop (array.new_default $t (local.get 0)))
     )
   )
   "type mismatch"
diff --git a/test/spec/br_on_null.wast b/test/spec/br_on_null.wast
index 8e0a859..e3c45ec 100644
--- a/test/spec/br_on_null.wast
+++ b/test/spec/br_on_null.wast
@@ -3,13 +3,13 @@
 
   (func $nn (param $r (ref $t)) (result i32)
     (block $l
-      (return (call_ref (br_on_null $l (local.get $r))))
+      (return (call_ref $t (br_on_null $l (local.get $r))))
     )
     (i32.const -1)
   )
   (func $n (param $r (ref null $t)) (result i32)
     (block $l
-      (return (call_ref (br_on_null $l (local.get $r))))
+      (return (call_ref $t (br_on_null $l (local.get $r))))
     )
     (i32.const -1)
   )
@@ -22,7 +22,7 @@
 
   (func (export "unreachable") (result i32)
     (block $l
-      (return (call_ref (br_on_null $l (unreachable))))
+      (return (call_ref $t (br_on_null $l (unreachable))))
     )
     (i32.const -1)
   )
@@ -49,7 +49,7 @@
 
     (func $nn (param $r (ref $t)) (result i32)
       (block $l (ref null $t) ;; br_on_null sends no value; a br to here is bad
-        (return (call_ref (br_on_null $l (local.get $r))))
+        (return (call_ref $t (br_on_null $l (local.get $r))))
       )
       (i32.const -1)
     )
diff --git a/test/spec/call_ref.wast b/test/spec/call_ref.wast
index 8a34706..071aa8b 100644
--- a/test/spec/call_ref.wast
+++ b/test/spec/call_ref.wast
@@ -2,7 +2,7 @@
   (type $ii (func (param i32) (result i32)))
 
   (func $apply (param $f (ref $ii)) (param $x i32) (result i32)
-    (call_ref (local.get $x) (local.get $f))
+    (call_ref $ii (local.get $x) (local.get $f))
   )
 
   (func $f (type $ii) (i32.mul (local.get 0) (local.get 0)))
@@ -15,11 +15,11 @@
     (local $rg (ref null $ii))
     (local.set $rf (ref.func $f))
     (local.set $rg (ref.func $g))
-    (call_ref (call_ref (local.get $x) (local.get $rf)) (local.get $rg))
+    (call_ref $ii (call_ref $ii (local.get $x) (local.get $rf)) (local.get $rg))
   )
 
   (func (export "null") (result i32)
-    (call_ref (i32.const 1) (ref.null $ii))
+    (call_ref $ii (i32.const 1) (ref.null $ii))
   )
 
   ;; Recursion
@@ -36,7 +36,7 @@
       (else
         (i64.mul
           (local.get 0)
-          (call_ref (i64.sub (local.get 0) (i64.const 1)) (global.get $fac))
+          (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $fac))
         )
       )
     )
@@ -49,7 +49,7 @@
     (if (result i64) (i64.eqz (local.get 0))
       (then (local.get 1))
       (else
-        (call_ref
+        (call_ref $lll
           (i64.sub (local.get 0) (i64.const 1))
           (i64.mul (local.get 0) (local.get 1))
           (global.get $fac-acc)
@@ -66,8 +66,8 @@
       (then (i64.const 1))
       (else
         (i64.add
-          (call_ref (i64.sub (local.get 0) (i64.const 2)) (global.get $fib))
-          (call_ref (i64.sub (local.get 0) (i64.const 1)) (global.get $fib))
+          (call_ref $ll (i64.sub (local.get 0) (i64.const 2)) (global.get $fib))
+          (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $fib))
         )
       )
     )
@@ -80,13 +80,13 @@
   (func $even (export "even") (type $ll)
     (if (result i64) (i64.eqz (local.get 0))
       (then (i64.const 44))
-      (else (call_ref (i64.sub (local.get 0) (i64.const 1)) (global.get $odd)))
+      (else (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $odd)))
     )
   )
   (func $odd (export "odd") (type $ll)
     (if (result i64) (i64.eqz (local.get 0))
       (then (i64.const 99))
-      (else (call_ref (i64.sub (local.get 0) (i64.const 1)) (global.get $even)))
+      (else (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $even)))
     )
   )
 )
@@ -127,8 +127,9 @@
 
 (assert_invalid
   (module
+    (type $t (func))
     (func $f (param $r externref)
-      (call_ref (local.get $r))
+      (call_ref $t (local.get $r))
     )
   )
   "type mismatch"
diff --git a/test/spec/imports.wast b/test/spec/imports.wast
index 43ffeda..2ef8574 100644
--- a/test/spec/imports.wast
+++ b/test/spec/imports.wast
@@ -489,19 +489,6 @@
 (assert_return (invoke "load" (i32.const 8)) (i32.const 0x100000))
 (assert_trap (invoke "load" (i32.const 1000000)) "out of bounds memory access")
 
-(assert_invalid
-  (module (import "" "" (memory 1)) (import "" "" (memory 1)))
-  "multiple memories"
-)
-(assert_invalid
-  (module (import "" "" (memory 1)) (memory 0))
-  "multiple memories"
-)
-(assert_invalid
-  (module (memory 0) (memory 0))
-  "multiple memories"
-)
-
 (module (import "test" "memory-2-inf" (memory 2)))
 (module (import "test" "memory-2-inf" (memory 1)))
 (module (import "test" "memory-2-inf" (memory 0)))
diff --git a/test/spec/memory.wast b/test/spec/memory.wast
index 3c42645..c4e932b 100644
--- a/test/spec/memory.wast
+++ b/test/spec/memory.wast
@@ -5,9 +5,6 @@
 (module (memory 1 256))
 (module (memory 0 65536))
 
-(assert_invalid (module (memory 0) (memory 0)) "multiple memories")
-(assert_invalid (module (memory (import "spectest" "memory") 0) (memory 0)) "multiple memories")
-
 (module (memory (data)) (func (export "memsize") (result i32) (memory.size)))
 (assert_return (invoke "memsize") (i32.const 0))
 (module (memory (data "")) (func (export "memsize") (result i32) (memory.size)))
diff --git a/test/spec/memory64.wast b/test/spec/memory64.wast
index da4ba59..683bfe2 100644
--- a/test/spec/memory64.wast
+++ b/test/spec/memory64.wast
@@ -5,9 +5,6 @@
 (module (memory i64 1 256))
 (module (memory i64 0 65536))
 
-(assert_invalid (module (memory i64 0) (memory i64 0)) "multiple memories")
-(assert_invalid (module (memory (import "spectest" "memory") i64 0) (memory i64 0)) "multiple memories")
-
 (module (memory i64 (data)) (func (export "memsize") (result i64) (memory.size)))
 (assert_return (invoke "memsize") (i64.const 0))
 (module (memory i64 (data "")) (func (export "memsize") (result i64) (memory.size)))
diff --git a/test/spec/multi-memories_size.wast b/test/spec/multi-memories_size.wast
new file mode 100644
index 0000000..8f0e7bd
--- /dev/null
+++ b/test/spec/multi-memories_size.wast
@@ -0,0 +1,35 @@
+(module
+ (memory $appMemory 0)
+ (memory $dataMemory 2)
+ (memory $instrumentMemory 4)
+  (func (export "size") (result i32) (memory.size))
+  (func (export "grow") (param $sz i32) (drop (memory.grow (local.get $sz))))
+  (func (export "size1") (result i32) (memory.size 1))
+  (func (export "grow1") (param $sz i32) (drop (memory.grow 1 (local.get $sz))))
+  (func (export "size2") (result i32) (memory.size 2))
+  (func (export "grow2") (param $sz i32) (drop (memory.grow 2 (local.get $sz))))
+)
+
+(assert_return (invoke "size") (i32.const 0))
+(assert_return (invoke "grow" (i32.const 1)))
+(assert_return (invoke "size") (i32.const 1))
+(assert_return (invoke "grow" (i32.const 4)))
+(assert_return (invoke "size") (i32.const 5))
+(assert_return (invoke "grow" (i32.const 0)))
+(assert_return (invoke "size") (i32.const 5))
+
+(assert_return (invoke "size1") (i32.const 2))
+(assert_return (invoke "grow1" (i32.const 2)))
+(assert_return (invoke "size1") (i32.const 4))
+(assert_return (invoke "grow1" (i32.const 4)))
+(assert_return (invoke "size1") (i32.const 8))
+(assert_return (invoke "grow1" (i32.const 0)))
+(assert_return (invoke "size1") (i32.const 8))
+
+(assert_return (invoke "size2") (i32.const 4))
+(assert_return (invoke "grow2" (i32.const 4)))
+(assert_return (invoke "size2") (i32.const 8))
+(assert_return (invoke "grow2" (i32.const 8)))
+(assert_return (invoke "size2") (i32.const 16))
+(assert_return (invoke "grow2" (i32.const 0)))
+(assert_return (invoke "size2") (i32.const 16))
diff --git a/test/spec/old_import.wast b/test/spec/old_import.wast
index a26c16e..eba6333 100644
--- a/test/spec/old_import.wast
+++ b/test/spec/old_import.wast
@@ -179,19 +179,6 @@
 (assert_return (invoke "load" (i32.const 8)) (i32.const 0x100000))
 (assert_trap (invoke "load" (i32.const 1000000)) "out of bounds memory access")
 
-(assert_invalid
-  (module (import "" "" (memory 1)) (import "" "" (memory 1)))
-  "multiple memories"
-)
-(assert_invalid
-  (module (import "" "" (memory 1)) (memory 0))
-  "multiple memories"
-)
-(assert_invalid
-  (module (memory 0) (memory 0))
-  "multiple memories"
-)
-
 (assert_unlinkable
   (module (import "spectest" "unknown" (memory 1)))
   "unknown import"
diff --git a/test/spec/old_select.wast b/test/spec/old_select.wast
index 5228017..e6a7ed6 100644
--- a/test/spec/old_select.wast
+++ b/test/spec/old_select.wast
@@ -93,8 +93,8 @@
 (assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan) (i32.const 0)) (f64.const nan))
 (assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan:0x20304) (i32.const 0)) (f64.const nan:0x20304))
 
-(assert_return (invoke "select-funcref" (ref.func "dummy") (ref.null func) (i32.const 1)) (ref.func "dummy"))
-(assert_return (invoke "select-funcref" (ref.func "dummy") (ref.null func) (i32.const 0)) (ref.null func))
+(assert_return (invoke "select-funcref" (ref.func $dummy) (ref.null func) (i32.const 1)) (ref.func $dummy))
+(assert_return (invoke "select-funcref" (ref.func $dummy) (ref.null func) (i32.const 0)) (ref.null func))
 (assert_return (invoke "select-externref" (ref.null extern) (ref.null extern) (i32.const 1)) (ref.null extern))
 (assert_return (invoke "select-externref" (ref.null extern) (ref.null extern) (i32.const 0)) (ref.null extern))
 
diff --git a/test/spec/ref_as_non_null.wast b/test/spec/ref_as_non_null.wast
index 7e94171..e597e7b 100644
--- a/test/spec/ref_as_non_null.wast
+++ b/test/spec/ref_as_non_null.wast
@@ -2,10 +2,10 @@
   (type $t (func (result i32)))
 
   (func $nn (param $r (ref $t)) (result i32)
-    (call_ref (ref.as_non_null (local.get $r)))
+    (call_ref $t (ref.as_non_null (local.get $r)))
   )
   (func $n (param $r (ref null $t)) (result i32)
-    (call_ref (ref.as_non_null (local.get $r)))
+    (call_ref $t (ref.as_non_null (local.get $r)))
   )
 
   (elem func $f)
diff --git a/test/spec/ref_cast.wast b/test/spec/ref_cast.wast
index 8712e59..1af96d9 100644
--- a/test/spec/ref_cast.wast
+++ b/test/spec/ref_cast.wast
@@ -6,15 +6,6 @@
   (type $t2' (struct (field i32) (field i32)))
   (type $t3  (struct (field i32) (field i32)))
 
-  (global $t0  (rtt $t0)  (rtt.canon $t0))
-  (global $t0' (rtt $t0)  (rtt.canon $t0))
-  (global $t1  (rtt $t1)  (rtt.sub $t1  (global.get $t0)))
-  (global $t1' (rtt $t1') (rtt.sub $t1' (global.get $t0)))
-  (global $t2  (rtt $t2)  (rtt.sub $t2  (global.get $t1)))
-  (global $t2' (rtt $t2') (rtt.sub $t2' (global.get $t1')))
-  (global $t3  (rtt $t3)  (rtt.sub $t3  (global.get $t0)))
-  (global $t4  (rtt $t3)  (rtt.sub $t3  (rtt.sub $t0 (global.get $t0))))
-
   (global $tab.0  (mut (ref null data)) (ref.null data))
   (global $tab.1  (mut (ref null data)) (ref.null data))
   (global $tab.2  (mut (ref null data)) (ref.null data))
@@ -25,62 +16,55 @@
   (global $tab.12 (mut (ref null data)) (ref.null data))
 
   (func $init
-    (global.set $tab.0  (struct.new_default_with_rtt $t0  (global.get $t0)))
-    (global.set $tab.10 (struct.new_default_with_rtt $t0  (global.get $t0')))
-    (global.set $tab.1  (struct.new_default_with_rtt $t1  (global.get $t1)))
-    (global.set $tab.11 (struct.new_default_with_rtt $t1' (global.get $t1')))
-    (global.set $tab.2  (struct.new_default_with_rtt $t2  (global.get $t2)))
-    (global.set $tab.12 (struct.new_default_with_rtt $t2' (global.get $t2')))
-    (global.set $tab.3  (struct.new_default_with_rtt $t3  (global.get $t3)))
-    (global.set $tab.4  (struct.new_default_with_rtt $t3  (global.get $t4)))
+    (global.set $tab.0  (struct.new_default $t0))
+    (global.set $tab.10 (struct.new_default $t0))
+    (global.set $tab.1  (struct.new_default $t1))
+    (global.set $tab.11 (struct.new_default $t1'))
+    (global.set $tab.2  (struct.new_default $t2))
+    (global.set $tab.12 (struct.new_default $t2'))
+    (global.set $tab.3  (struct.new_default $t3))
+    (global.set $tab.4  (struct.new_default $t3))
   )
 
   (func (export "test-sub")
     (call $init)
 
-    (drop (ref.cast (ref.null data) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.0) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.1) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.2) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.3) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.4) (global.get $t0)))
+    (drop (ref.cast_static $t0 (ref.null data)))
+    (drop (ref.cast_static $t0 (global.get $tab.0)))
+    (drop (ref.cast_static $t0 (global.get $tab.1)))
+    (drop (ref.cast_static $t0 (global.get $tab.2)))
+    (drop (ref.cast_static $t0 (global.get $tab.3)))
+    (drop (ref.cast_static $t0 (global.get $tab.4)))
 
-    (drop (ref.cast (ref.null data) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.1) (global.get $t1)))
-    (drop (ref.cast (global.get $tab.2) (global.get $t1)))
+    (drop (ref.cast_static $t0 (ref.null data)))
+    (drop (ref.cast_static $t1 (global.get $tab.1)))
+    (drop (ref.cast_static $t1 (global.get $tab.2)))
 
-    (drop (ref.cast (ref.null data) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.2) (global.get $t2)))
+    (drop (ref.cast_static $t0 (ref.null data)))
+    (drop (ref.cast_static $t2 (global.get $tab.2)))
 
-    (drop (ref.cast (ref.null data) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.3) (global.get $t3)))
+    (drop (ref.cast_static $t0 (ref.null data)))
+    (drop (ref.cast_static $t3 (global.get $tab.3)))
 
-    (drop (ref.cast (ref.null data) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.4) (global.get $t4)))
+    (drop (ref.cast_static $t0 (ref.null data)))
   )
 
   (func (export "test-canon")
     (call $init)
 
-    (drop (ref.cast (global.get $tab.0) (global.get $t0')))
-    (drop (ref.cast (global.get $tab.1) (global.get $t0')))
-    (drop (ref.cast (global.get $tab.2) (global.get $t0')))
-    (drop (ref.cast (global.get $tab.3) (global.get $t0')))
-    (drop (ref.cast (global.get $tab.4) (global.get $t0')))
-
-    (drop (ref.cast (global.get $tab.10) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.11) (global.get $t0)))
-    (drop (ref.cast (global.get $tab.12) (global.get $t0)))
+    (drop (ref.cast_static $t0 (global.get $tab.10)))
+    (drop (ref.cast_static $t0 (global.get $tab.11)))
+    (drop (ref.cast_static $t0 (global.get $tab.12)))
 
-    (drop (ref.cast (global.get $tab.1) (global.get $t1')))
-    (drop (ref.cast (global.get $tab.2) (global.get $t1')))
+    (drop (ref.cast_static $t1' (global.get $tab.1)))
+    (drop (ref.cast_static $t1' (global.get $tab.2)))
 
-    (drop (ref.cast (global.get $tab.11) (global.get $t1)))
-    (drop (ref.cast (global.get $tab.12) (global.get $t1)))
+    (drop (ref.cast_static $t1 (global.get $tab.11)))
+    (drop (ref.cast_static $t1 (global.get $tab.12)))
 
-    (drop (ref.cast (global.get $tab.2) (global.get $t2')))
+    (drop (ref.cast_static $t2' (global.get $tab.2)))
 
-    (drop (ref.cast (global.get $tab.12) (global.get $t2)))
+    (drop (ref.cast_static $t2 (global.get $tab.12)))
   )
 )
 
diff --git a/test/spec/run.py b/test/spec/run.py
deleted file mode 100755
index ec07929..0000000
--- a/test/spec/run.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python3
-
-from __future__ import print_function
-import argparse
-import os
-import os.path
-import unittest
-import subprocess
-import glob
-import sys
-
-
-ownDir = os.path.dirname(os.path.abspath(sys.argv[0]))
-inputDir = ownDir
-outputDir = os.path.join(inputDir, "_output")
-
-parser = argparse.ArgumentParser()
-parser.add_argument("--wasm", metavar="<wasm-command>", default=os.path.join(os.getcwd(), "wasm"))
-parser.add_argument("--js", metavar="<js-command>")
-parser.add_argument("--out", metavar="<out-dir>", default=outputDir)
-parser.add_argument("file", nargs='*')
-arguments = parser.parse_args()
-sys.argv = sys.argv[:1]
-
-wasmCommand = arguments.wasm
-jsCommand = arguments.js
-outputDir = arguments.out
-inputFiles = arguments.file if arguments.file else glob.glob(os.path.join(inputDir, "*.wast"))
-
-if not os.path.exists(wasmCommand):
-  sys.stderr.write("""\
-Error: The executable '%s' does not exist.
-Provide the correct path with the '--wasm' flag.
-
-""" % (wasmCommand))
-  parser.print_help()
-  sys.exit(1)
-
-
-class RunTests(unittest.TestCase):
-  def _runCommand(self, command, logPath, expectedExitCode = 0):
-    with open(logPath, 'w+') as out:
-      exitCode = subprocess.call(command, shell=True, stdout=out, stderr=subprocess.STDOUT)
-      self.assertEqual(expectedExitCode, exitCode, "failed with exit code %i (expected %i) for %s" % (exitCode, expectedExitCode, command))
-
-  def _auxFile(self, path):
-    if os.path.exists(path):
-      os.remove(path)
-    return path
-
-  def _compareFile(self, expectFile, actualFile):
-    if os.path.exists(expectFile):
-      with open(expectFile) as expect:
-        with open(actualFile) as actual:
-          expectText = expect.read()
-          actualText = actual.read()
-          self.assertEqual(expectText, actualText)
-
-  def _runTestFile(self, inputPath):
-    dir, inputFile = os.path.split(inputPath)
-    outputPath = os.path.join(outputDir, inputFile)
-
-    # Run original file
-    expectedExitCode = 1 if ".fail." in inputFile else 0
-    logPath = self._auxFile(outputPath + ".log")
-    self._runCommand(('%s "%s"') % (wasmCommand, inputPath), logPath, expectedExitCode)
-
-    if expectedExitCode != 0:
-      return
-
-    # Convert to binary and run again
-    wasmPath = self._auxFile(outputPath + ".bin.wast")
-    logPath = self._auxFile(wasmPath + ".log")
-    self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, inputPath, wasmPath), logPath)
-    self._runCommand(('%s "%s"') % (wasmCommand, wasmPath), logPath)
-
-    # Convert back to text and run again
-    wastPath = self._auxFile(wasmPath + ".wast")
-    logPath = self._auxFile(wastPath + ".log")
-    self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, wasmPath, wastPath), logPath)
-    self._runCommand(('%s "%s"') % (wasmCommand, wastPath), logPath)
-
-    # Convert back to binary once more and compare
-    wasm2Path = self._auxFile(wastPath + ".bin.wast")
-    logPath = self._auxFile(wasm2Path + ".log")
-    self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, wastPath, wasm2Path), logPath)
-    self._compareFile(wasmPath, wasm2Path)
-
-    # Convert back to text once more and compare
-    wast2Path = self._auxFile(wasm2Path + ".wast")
-    logPath = self._auxFile(wast2Path + ".log")
-    self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, wasm2Path, wast2Path), logPath)
-    self._compareFile(wastPath, wast2Path)
-
-    # Convert to JavaScript
-    jsPath = self._auxFile(outputPath.replace(".wast", ".js"))
-    logPath = self._auxFile(jsPath + ".log")
-    self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, inputPath, jsPath), logPath)
-    if jsCommand != None:
-      self._runCommand(('%s "%s"') % (jsCommand, jsPath), logPath)
-
-
-if __name__ == "__main__":
-  if not os.path.exists(outputDir):
-    os.makedirs(outputDir)
-  for fileName in inputFiles:
-    testName = 'test ' + os.path.basename(fileName)
-    setattr(RunTests, testName, lambda self, file=fileName: self._runTestFile(file))
-  unittest.main()
diff --git a/test/spec/struct.wast b/test/spec/struct.wast
index bac4d45..5bc8e2f 100644
--- a/test/spec/struct.wast
+++ b/test/spec/struct.wast
@@ -28,7 +28,7 @@
     (struct.get $vec 0 (local.get $v))
   )
   (func (export "get_0") (result f32)
-    (call $get_0 (struct.new_default_with_rtt $vec (rtt.canon $vec)))
+    (call $get_0 (struct.new_default $vec))
   )
 
   (func $set_get_y (param $v (ref $vec)) (param $y f32) (result f32)
@@ -36,7 +36,7 @@
     (struct.get $vec $y (local.get $v))
   )
   (func (export "set_get_y") (param $y f32) (result f32)
-    (call $set_get_y (struct.new_default_with_rtt $vec (rtt.canon $vec)) (local.get $y))
+    (call $set_get_y (struct.new_default $vec) (local.get $y))
   )
 
   (func $set_get_1 (param $v (ref $vec)) (param $y f32) (result f32)
@@ -44,7 +44,7 @@
     (struct.get $vec $y (local.get $v))
   )
   (func (export "set_get_1") (param $y f32) (result f32)
-    (call $set_get_1 (struct.new_default_with_rtt $vec (rtt.canon $vec)) (local.get $y))
+    (call $set_get_1 (struct.new_default $vec) (local.get $y))
   )
 )
 
@@ -82,29 +82,12 @@
   (module
     (type $t (struct (field i32) (field (mut i32))))
     (func (export "struct.new-null")
-      (local (ref null (rtt $t))) (drop (struct.new $t (i32.const 1) (i32.const 2) (local.get 0)))
+      (local i64)
+      (drop (struct.new $t (i32.const 1) (i32.const 2) (local.get 0)))
     )
   )
   "type mismatch"
 )
-(assert_invalid
-  (module
-    (type $t (struct (field i32) (field (mut i32))))
-    (func (export "struct.new_default-null")
-      (local (ref null (rtt $t))) (drop (struct.new_default_with_rtt $t (local.get 0)))
-    )
-  )
-  "type mismatch"
-)
-
-(assert_invalid
-  (module
-    (type $A (struct (field i32)))
-    (type $B (struct (field i64)))
-    (global $glob (rtt $A) (rtt.sub $A (rtt.canon $B)))
-  )
-  "invalid rtt"
-)
 
 (assert_invalid
   (module
@@ -112,7 +95,7 @@
     (func $test
       (drop
         ;; too many arguments
-        (struct.new_with_rtt $vec (i32.const 1) (i32.const 2) (rtt.canon $vec))
+        (struct.new $vec (i32.const 1) (i32.const 2))
       )
     )
   )
@@ -125,7 +108,7 @@
     (func $test
       (drop
         ;; too few arguments
-        (struct.new_with_rtt $vec (i32.const 1) (rtt.canon $vec))
+        (struct.new $vec (i32.const 1))
       )
     )
   )
diff --git a/test/spec/tuples.wast b/test/spec/tuples.wast
deleted file mode 100644
index 83a30aa..0000000
--- a/test/spec/tuples.wast
+++ /dev/null
@@ -1,9 +0,0 @@
-(assert_invalid
-  (module
-    (func $foo
-      (local $temp ((ref func) i32))
-    )
-  )
-  "var must be defaultable"
-)
-
diff --git a/test/subtypes.wast.from-wast b/test/subtypes.wast.from-wast
index d7114ff..88bd9b9 100644
--- a/test/subtypes.wast.from-wast
+++ b/test/subtypes.wast.from-wast
@@ -1,17 +1,17 @@
 (module
  (type $struct-rec-two (struct (field (ref $struct-rec-two)) (field (ref $struct-rec-two))))
  (type $vector-i32 (array i32))
- (type $struct-i31 (struct (field i31ref)))
+ (type $struct-i31 (struct (field (ref i31))))
  (type $struct-rec-one (struct (field (ref $struct-rec-one))))
  (type $ref|$vector-i32|_ref?|$vector-i32|_=>_none (func (param (ref $vector-i32) (ref null $vector-i32))))
  (type $ref|$vector-i31|_ref|$vector-any|_=>_none (func (param (ref $vector-i31) (ref $vector-any))))
  (type $ref|$struct-i31|_ref|$struct-any|_=>_none (func (param (ref $struct-i31) (ref $struct-any))))
  (type $ref|$struct-i31|_ref|$struct-i31_any|_=>_none (func (param (ref $struct-i31) (ref $struct-i31_any))))
  (type $ref|$struct-rec-one|_ref|$struct-rec-two|_=>_none (func (param (ref $struct-rec-one) (ref $struct-rec-two))))
- (type $vector-i31 (array i31ref))
+ (type $vector-i31 (array (ref i31)))
  (type $vector-any (array (ref any)))
  (type $struct-any (struct (field (ref any))))
- (type $struct-i31_any (struct (field i31ref) (field (ref any))))
+ (type $struct-i31_any (struct (field (ref i31)) (field (ref any))))
  (func $foo (param $no-null (ref $vector-i32)) (param $yes-null (ref null $vector-i32))
   (local.set $yes-null
    (local.get $no-null)
diff --git a/test/subtypes.wast.fromBinary b/test/subtypes.wast.fromBinary
index 8936285..6286fb5 100644
--- a/test/subtypes.wast.fromBinary
+++ b/test/subtypes.wast.fromBinary
@@ -1,17 +1,17 @@
 (module
  (type $struct-rec-two (struct (field (ref $struct-rec-two)) (field (ref $struct-rec-two))))
  (type $vector-i32 (array i32))
- (type $struct-i31 (struct (field i31ref)))
+ (type $struct-i31 (struct (field (ref i31))))
  (type $struct-rec-one (struct (field (ref $struct-rec-one))))
  (type $ref|$vector-i32|_ref?|$vector-i32|_=>_none (func (param (ref $vector-i32) (ref null $vector-i32))))
  (type $ref|$vector-i31|_ref|$vector-any|_=>_none (func (param (ref $vector-i31) (ref $vector-any))))
  (type $ref|$struct-i31|_ref|$struct-any|_=>_none (func (param (ref $struct-i31) (ref $struct-any))))
  (type $ref|$struct-i31|_ref|$struct-i31_any|_=>_none (func (param (ref $struct-i31) (ref $struct-i31_any))))
  (type $ref|$struct-rec-one|_ref|$struct-rec-two|_=>_none (func (param (ref $struct-rec-one) (ref $struct-rec-two))))
- (type $vector-i31 (array i31ref))
+ (type $vector-i31 (array (ref i31)))
  (type $vector-any (array (ref any)))
  (type $struct-any (struct (field (ref any))))
- (type $struct-i31_any (struct (field i31ref) (field (ref any))))
+ (type $struct-i31_any (struct (field (ref i31)) (field (ref any))))
  (func $foo (param $no-null (ref $vector-i32)) (param $yes-null (ref null $vector-i32))
   (local.set $yes-null
    (local.get $no-null)
diff --git a/test/subtypes.wast.fromBinary.noDebugInfo b/test/subtypes.wast.fromBinary.noDebugInfo
index b275da6..210e9ab 100644
--- a/test/subtypes.wast.fromBinary.noDebugInfo
+++ b/test/subtypes.wast.fromBinary.noDebugInfo
@@ -1,33 +1,33 @@
 (module
  (type ${ref|...0|_ref|...0|} (struct (field (ref ${ref|...0|_ref|...0|})) (field (ref ${ref|...0|_ref|...0|}))))
  (type $[i32] (array i32))
- (type ${i31ref} (struct (field i31ref)))
+ (type ${ref|i31|} (struct (field (ref i31))))
  (type ${ref|...0|} (struct (field (ref ${ref|...0|}))))
  (type $ref|[i32]|_ref?|[i32]|_=>_none (func (param (ref $[i32]) (ref null $[i32]))))
- (type $ref|[i31ref]|_ref|[ref|any|]|_=>_none (func (param (ref $[i31ref]) (ref $[ref|any|]))))
- (type $ref|{i31ref}|_ref|{ref|any|}|_=>_none (func (param (ref ${i31ref}) (ref ${ref|any|}))))
- (type $ref|{i31ref}|_ref|{i31ref_ref|any|}|_=>_none (func (param (ref ${i31ref}) (ref ${i31ref_ref|any|}))))
+ (type $ref|[ref|i31|]|_ref|[ref|any|]|_=>_none (func (param (ref $[ref|i31|]) (ref $[ref|any|]))))
+ (type $ref|{ref|i31|}|_ref|{ref|any|}|_=>_none (func (param (ref ${ref|i31|}) (ref ${ref|any|}))))
+ (type $ref|{ref|i31|}|_ref|{ref|i31|_ref|any|}|_=>_none (func (param (ref ${ref|i31|}) (ref ${ref|i31|_ref|any|}))))
  (type $ref|{ref|...0|}|_ref|{ref|...0|_ref|...0|}|_=>_none (func (param (ref ${ref|...0|}) (ref ${ref|...0|_ref|...0|}))))
- (type $[i31ref] (array i31ref))
+ (type $[ref|i31|] (array (ref i31)))
  (type $[ref|any|] (array (ref any)))
  (type ${ref|any|} (struct (field (ref any))))
- (type ${i31ref_ref|any|} (struct (field i31ref) (field (ref any))))
+ (type ${ref|i31|_ref|any|} (struct (field (ref i31)) (field (ref any))))
  (func $0 (param $0 (ref $[i32])) (param $1 (ref null $[i32]))
   (local.set $1
    (local.get $0)
   )
  )
- (func $1 (param $0 (ref $[i31ref])) (param $1 (ref $[ref|any|]))
+ (func $1 (param $0 (ref $[ref|i31|])) (param $1 (ref $[ref|any|]))
   (local.set $1
    (local.get $0)
   )
  )
- (func $2 (param $0 (ref ${i31ref})) (param $1 (ref ${ref|any|}))
+ (func $2 (param $0 (ref ${ref|i31|})) (param $1 (ref ${ref|any|}))
   (local.set $1
    (local.get $0)
   )
  )
- (func $3 (param $0 (ref ${i31ref})) (param $1 (ref ${i31ref_ref|any|}))
+ (func $3 (param $0 (ref ${ref|i31|})) (param $1 (ref ${ref|i31|_ref|any|}))
   (local.set $0
    (local.get $1)
   )
diff --git a/test/tags.wast.from-wast b/test/tags.wast.from-wast
index 669afcc..cc79bf6 100644
--- a/test/tags.wast.from-wast
+++ b/test/tags.wast.from-wast
@@ -10,5 +10,6 @@
  (tag $e-params0 (param i32 f32))
  (tag $e-params1 (param i32 f32))
  (tag $e-export (param i32))
+ (export "ex0" (tag $e-export))
  (export "ex1" (tag $e))
 )
diff --git a/test/tags.wast.fromBinary b/test/tags.wast.fromBinary
index 9886261..5528068 100644
--- a/test/tags.wast.fromBinary
+++ b/test/tags.wast.fromBinary
@@ -2,14 +2,15 @@
  (type $i32_f32_=>_none (func (param i32 f32)))
  (type $i32_=>_none (func (param i32)))
  (type $none_=>_none (func))
- (import "env" "im0" (tag $eimport$0 (param i32)))
+ (import "env" "im0" (tag $e-import (param i32)))
  (import "env" "im1" (tag $eimport$1 (param i32 f32)))
  (tag $tag$0 (param i32))
- (tag $tag$1 (param i32 f32))
- (tag $tag$2 (param))
- (tag $tag$3 (param i32 f32))
- (tag $tag$4 (param i32 f32))
- (tag $tag$5 (param i32))
- (export "ex1" (tag $tag$1))
+ (tag $e (param i32 f32))
+ (tag $empty (param))
+ (tag $e-params0 (param i32 f32))
+ (tag $e-params1 (param i32 f32))
+ (tag $e-export (param i32))
+ (export "ex0" (tag $e-export))
+ (export "ex1" (tag $e))
 )
 
diff --git a/test/tags.wast.fromBinary.noDebugInfo b/test/tags.wast.fromBinary.noDebugInfo
index 9886261..81043d6 100644
--- a/test/tags.wast.fromBinary.noDebugInfo
+++ b/test/tags.wast.fromBinary.noDebugInfo
@@ -10,6 +10,7 @@
  (tag $tag$3 (param i32 f32))
  (tag $tag$4 (param i32 f32))
  (tag $tag$5 (param i32))
+ (export "ex0" (tag $tag$5))
  (export "ex1" (tag $tag$1))
 )
 
diff --git a/test/typed-function-references.wast b/test/typed-function-references.wast
deleted file mode 100644
index 1057d44..0000000
--- a/test/typed-function-references.wast
+++ /dev/null
@@ -1,44 +0,0 @@
-(module
-  ;; inline ref type in result
-  (type $_=>_eqref (func (result eqref)))
-  (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $_=>_eqref))))
-  (type $=>eqref (func (result eqref)))
-  (type $=>anyref (func (result anyref)))
-  (type $mixed_results (func (result anyref f32 anyref f32)))
-
-  (type $i32-i32 (func (param i32) (result i32)))
-
-  (func $call-ref
-    (call_ref (ref.func $call-ref))
-  )
-  (func $return-call-ref
-    (return_call_ref (ref.func $call-ref))
-  )
-  (func $call-ref-more (param i32) (result i32)
-    (call_ref (i32.const 42) (ref.func $call-ref-more))
-  )
-  (func $call_from-param (param $f (ref $i32-i32)) (result i32)
-    (call_ref (i32.const 42) (local.get $f))
-  )
-  (func $call_from-param-null (param $f (ref null $i32-i32)) (result i32)
-    (call_ref (i32.const 42) (local.get $f))
-  )
-  (func $call_from-local-null (result i32)
-    (local $f (ref null $i32-i32))
-    (local.set $f (ref.func $call-ref-more))
-    (call_ref (i32.const 42) (local.get $f))
-  )
-  (func $ref-in-sig (param $0 f64) (result (ref null $=>eqref))
-    (ref.null $=>eqref)
-  )
-  (func $type-only-in-tuple-local
-    (local $x (i32 (ref null $=>anyref) f64))
-  )
-  (func $type-only-in-tuple-block
-    (drop
-      (block (result i32 (ref null $mixed_results) f64)
-        (unreachable)
-      )
-    )
-  )
-)
diff --git a/test/typed-function-references.wast.from-wast b/test/typed-function-references.wast.from-wast
deleted file mode 100644
index cb9dd56..0000000
--- a/test/typed-function-references.wast.from-wast
+++ /dev/null
@@ -1,65 +0,0 @@
-(module
- (type $none_=>_none (func))
- (type $i32-i32 (func (param i32) (result i32)))
- (type $=>eqref (func (result eqref)))
- (type $ref|$i32-i32|_=>_i32 (func (param (ref $i32-i32)) (result i32)))
- (type $ref?|$i32-i32|_=>_i32 (func (param (ref null $i32-i32)) (result i32)))
- (type $none_=>_i32 (func (result i32)))
- (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $=>eqref))))
- (type $=>anyref (func (result anyref)))
- (type $none_=>_i32_ref?|$mixed_results|_f64 (func (result i32 (ref null $mixed_results) f64)))
- (type $mixed_results (func (result anyref f32 anyref f32)))
- (elem declare func $call-ref $call-ref-more)
- (func $call-ref
-  (call_ref
-   (ref.func $call-ref)
-  )
- )
- (func $return-call-ref
-  (return_call_ref
-   (ref.func $call-ref)
-  )
- )
- (func $call-ref-more (param $0 i32) (result i32)
-  (call_ref
-   (i32.const 42)
-   (ref.func $call-ref-more)
-  )
- )
- (func $call_from-param (param $f (ref $i32-i32)) (result i32)
-  (call_ref
-   (i32.const 42)
-   (local.get $f)
-  )
- )
- (func $call_from-param-null (param $f (ref null $i32-i32)) (result i32)
-  (call_ref
-   (i32.const 42)
-   (local.get $f)
-  )
- )
- (func $call_from-local-null (result i32)
-  (local $f (ref null $i32-i32))
-  (local.set $f
-   (ref.func $call-ref-more)
-  )
-  (call_ref
-   (i32.const 42)
-   (local.get $f)
-  )
- )
- (func $ref-in-sig (param $0 f64) (result (ref null $=>eqref))
-  (ref.null $=>eqref)
- )
- (func $type-only-in-tuple-local
-  (local $x (i32 (ref null $=>anyref) f64))
-  (nop)
- )
- (func $type-only-in-tuple-block
-  (drop
-   (block $block (result i32 (ref null $mixed_results) f64)
-    (unreachable)
-   )
-  )
- )
-)
diff --git a/test/typed-function-references.wast.fromBinary b/test/typed-function-references.wast.fromBinary
deleted file mode 100644
index e3839f3..0000000
--- a/test/typed-function-references.wast.fromBinary
+++ /dev/null
@@ -1,96 +0,0 @@
-(module
- (type $none_=>_none (func))
- (type $i32-i32 (func (param i32) (result i32)))
- (type $mixed_results (func (result anyref f32 anyref f32)))
- (type $=>eqref (func (result eqref)))
- (type $ref|$i32-i32|_=>_i32 (func (param (ref $i32-i32)) (result i32)))
- (type $ref?|$i32-i32|_=>_i32 (func (param (ref null $i32-i32)) (result i32)))
- (type $none_=>_i32 (func (result i32)))
- (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $=>eqref))))
- (type $=>anyref (func (result anyref)))
- (type $none_=>_i32_ref?|$mixed_results|_f64 (func (result i32 (ref null $mixed_results) f64)))
- (elem declare func $call-ref $call-ref-more)
- (func $call-ref
-  (call_ref
-   (ref.func $call-ref)
-  )
- )
- (func $return-call-ref
-  (return_call_ref
-   (ref.func $call-ref)
-  )
- )
- (func $call-ref-more (param $0 i32) (result i32)
-  (call_ref
-   (i32.const 42)
-   (ref.func $call-ref-more)
-  )
- )
- (func $call_from-param (param $f (ref $i32-i32)) (result i32)
-  (call_ref
-   (i32.const 42)
-   (local.get $f)
-  )
- )
- (func $call_from-param-null (param $f (ref null $i32-i32)) (result i32)
-  (call_ref
-   (i32.const 42)
-   (local.get $f)
-  )
- )
- (func $call_from-local-null (result i32)
-  (local $f (ref null $i32-i32))
-  (local.set $f
-   (ref.func $call-ref-more)
-  )
-  (call_ref
-   (i32.const 42)
-   (local.get $f)
-  )
- )
- (func $ref-in-sig (param $0 f64) (result (ref null $=>eqref))
-  (ref.null $=>eqref)
- )
- (func $type-only-in-tuple-local
-  (local $x i32)
-  (local $1 (ref null $=>anyref))
-  (local $2 f64)
-  (nop)
- )
- (func $type-only-in-tuple-block
-  (local $0 (i32 (ref null $mixed_results) f64))
-  (local $1 (ref null $mixed_results))
-  (local $2 i32)
-  (local.set $0
-   (block $label$1 (result i32 (ref null $mixed_results) f64)
-    (unreachable)
-   )
-  )
-  (drop
-   (block (result i32)
-    (local.set $2
-     (tuple.extract 0
-      (local.get $0)
-     )
-    )
-    (drop
-     (block (result (ref null $mixed_results))
-      (local.set $1
-       (tuple.extract 1
-        (local.get $0)
-       )
-      )
-      (drop
-       (tuple.extract 2
-        (local.get $0)
-       )
-      )
-      (local.get $1)
-     )
-    )
-    (local.get $2)
-   )
-  )
- )
-)
-
diff --git a/test/typed-function-references.wast.fromBinary.noDebugInfo b/test/typed-function-references.wast.fromBinary.noDebugInfo
deleted file mode 100644
index cf462ff..0000000
--- a/test/typed-function-references.wast.fromBinary.noDebugInfo
+++ /dev/null
@@ -1,96 +0,0 @@
-(module
- (type $none_=>_none (func))
- (type $i32_=>_i32 (func (param i32) (result i32)))
- (type $none_=>_anyref_f32_anyref_f32 (func (result anyref f32 anyref f32)))
- (type $none_=>_eqref (func (result eqref)))
- (type $ref|i32_->_i32|_=>_i32 (func (param (ref $i32_=>_i32)) (result i32)))
- (type $ref?|i32_->_i32|_=>_i32 (func (param (ref null $i32_=>_i32)) (result i32)))
- (type $none_=>_i32 (func (result i32)))
- (type $f64_=>_ref?|none_->_eqref| (func (param f64) (result (ref null $none_=>_eqref))))
- (type $none_=>_anyref (func (result anyref)))
- (type $none_=>_i32_ref?|none_->_anyref_f32_anyref_f32|_f64 (func (result i32 (ref null $none_=>_anyref_f32_anyref_f32) f64)))
- (elem declare func $0 $2)
- (func $0
-  (call_ref
-   (ref.func $0)
-  )
- )
- (func $1
-  (return_call_ref
-   (ref.func $0)
-  )
- )
- (func $2 (param $0 i32) (result i32)
-  (call_ref
-   (i32.const 42)
-   (ref.func $2)
-  )
- )
- (func $3 (param $0 (ref $i32_=>_i32)) (result i32)
-  (call_ref
-   (i32.const 42)
-   (local.get $0)
-  )
- )
- (func $4 (param $0 (ref null $i32_=>_i32)) (result i32)
-  (call_ref
-   (i32.const 42)
-   (local.get $0)
-  )
- )
- (func $5 (result i32)
-  (local $0 (ref null $i32_=>_i32))
-  (local.set $0
-   (ref.func $2)
-  )
-  (call_ref
-   (i32.const 42)
-   (local.get $0)
-  )
- )
- (func $6 (param $0 f64) (result (ref null $none_=>_eqref))
-  (ref.null $none_=>_eqref)
- )
- (func $7
-  (local $0 i32)
-  (local $1 (ref null $none_=>_anyref))
-  (local $2 f64)
-  (nop)
- )
- (func $8
-  (local $0 (i32 (ref null $none_=>_anyref_f32_anyref_f32) f64))
-  (local $1 (ref null $none_=>_anyref_f32_anyref_f32))
-  (local $2 i32)
-  (local.set $0
-   (block $label$1 (result i32 (ref null $none_=>_anyref_f32_anyref_f32) f64)
-    (unreachable)
-   )
-  )
-  (drop
-   (block (result i32)
-    (local.set $2
-     (tuple.extract 0
-      (local.get $0)
-     )
-    )
-    (drop
-     (block (result (ref null $none_=>_anyref_f32_anyref_f32))
-      (local.set $1
-       (tuple.extract 1
-        (local.get $0)
-       )
-      )
-      (drop
-       (tuple.extract 2
-        (local.get $0)
-       )
-      )
-      (local.get $1)
-     )
-    )
-    (local.get $2)
-   )
-  )
- )
-)
-
diff --git a/test/unit/input/gc_target_feature.wasm b/test/unit/input/gc_target_feature.wasm
index df5ab9c..30b1b21 100644
Binary files a/test/unit/input/gc_target_feature.wasm and b/test/unit/input/gc_target_feature.wasm differ
diff --git a/test/unit/input/only-imported-memory.wasm b/test/unit/input/only-imported-memory.wasm
new file mode 100644
index 0000000..175113e
Binary files /dev/null and b/test/unit/input/only-imported-memory.wasm differ
diff --git a/test/unit/test_features.py b/test/unit/test_features.py
index 53c2263..d40eaf5 100644
--- a/test/unit/test_features.py
+++ b/test/unit/test_features.py
@@ -98,7 +98,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          )
         )
         '''
-        self.check_simd(module, 'SIMD operation (SIMD is disabled)')
+        self.check_simd(module, 'SIMD operations require SIMD [--enable-simd]')
 
     def test_simd_splat(self):
         module = '''
@@ -130,7 +130,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
         )
         '''
         self.check_bulk_mem(module,
-                            'Bulk memory operation (bulk memory is disabled')
+                            'Bulk memory operations require bulk memory [--enable-bulk-memory]')
 
     def test_bulk_mem_segment(self):
         module = '''
@@ -139,7 +139,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          (data "42")
         )
         '''
-        self.check_bulk_mem(module, 'nonzero segment flags (bulk memory is disabled)')
+        self.check_bulk_mem(module, 'nonzero segment flags require bulk memory [--enable-bulk-memory]')
 
     def test_tail_call(self):
         module = '''
@@ -150,7 +150,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          )
         )
         '''
-        self.check_tail_call(module, 'return_call* requires tail calls to be enabled')
+        self.check_tail_call(module, 'return_call* requires tail calls [--enable-tail-call]')
 
     def test_tail_call_indirect(self):
         module = '''
@@ -164,7 +164,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          )
         )
         '''
-        self.check_tail_call(module, 'return_call* requires tail calls to be enabled')
+        self.check_tail_call(module, 'return_call* requires tail calls [--enable-tail-call]')
 
     def test_reference_types_externref(self):
         module = '''
@@ -193,7 +193,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          )
         )
         '''
-        self.check_exception_handling(module, 'Module has tags')
+        self.check_exception_handling(module, 'Tags require exception-handling [--enable-exception-handling]')
 
     def test_multivalue_import(self):
         module = '''
@@ -201,8 +201,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          (import "env" "foo" (func $foo (result i32 i64)))
         )
         '''
-        self.check_multivalue(module, 'Imported multivalue function ' +
-                              '(multivalue is not enabled)')
+        self.check_multivalue(module, 'Imported multivalue function requires multivalue [--enable-multivalue]')
 
     def test_multivalue_function(self):
         module = '''
@@ -224,8 +223,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          (tag $foo (param i32 i64))
         )
         '''
-        self.check_multivalue_exception_handling(module, 'Multivalue tag type ' +
-                                                 '(multivalue is not enabled)')
+        self.check_multivalue_exception_handling(module, 'Multivalue tag type requires multivalue [--enable-multivalue]')
 
     def test_multivalue_block(self):
         module = '''
@@ -242,8 +240,7 @@ class FeatureValidationTest(utils.BinaryenTestCase):
          )
         )
         '''
-        self.check_multivalue(module, 'Multivalue block type ' +
-                              '(multivalue is not enabled)')
+        self.check_multivalue(module, 'Multivalue block type require multivalue [--enable-multivalue]')
 
     def test_i31_global(self):
         module = '''
@@ -343,7 +340,7 @@ class TargetFeaturesSectionTest(utils.BinaryenTestCase):
         self.roundtrip(filename)
         self.check_features(filename, ['reference-types', 'gc'])
         disassembly = self.disassemble(filename)
-        self.assertIn('anyref', disassembly)
+        self.assertIn('externref', disassembly)
         self.assertIn('eqref', disassembly)
 
     def test_superset(self):
@@ -395,7 +392,8 @@ class TargetFeaturesSectionTest(utils.BinaryenTestCase):
             '--enable-multivalue',
             '--enable-gc',
             '--enable-memory64',
-            '--enable-typed-function-references',
             '--enable-relaxed-simd',
             '--enable-extended-const',
+            '--enable-strings',
+            '--enable-multi-memories',
         ], p2.stdout.splitlines())
diff --git a/test/unit/test_finalize.py b/test/unit/test_finalize.py
index 6733155..4f5029b 100644
--- a/test/unit/test_finalize.py
+++ b/test/unit/test_finalize.py
@@ -4,14 +4,10 @@ from . import utils
 
 class EmscriptenFinalizeTest(utils.BinaryenTestCase):
     def do_output_test(self, args):
-        # without any output file specified, don't error, don't write the wasm,
-        # but do emit metadata
+        # without any output file specified, don't error, don't write the wasm
         p = shared.run_process(shared.WASM_EMSCRIPTEN_FINALIZE + [
             self.input_path('empty_lld.wat'), '--global-base=1024'
         ] + args, capture_output=True)
-        # metadata is always present
-        self.assertIn('{', p.stdout)
-        self.assertIn('}', p.stdout)
         return p.stdout
 
     def test_no_output(self):
diff --git a/test/unit/test_only_imported_memory.py b/test/unit/test_only_imported_memory.py
new file mode 100644
index 0000000..427f464
--- /dev/null
+++ b/test/unit/test_only_imported_memory.py
@@ -0,0 +1,8 @@
+from . import utils
+
+
+class OnlyImportedMemoryTest(utils.BinaryenTestCase):
+    def test_only_imported_memory(self):
+        # We should not create a memories section for a file with only an
+        # imported memory: such a module has no declared memories.
+        self.roundtrip('only-imported-memory.wasm', debug=False)
diff --git a/test/unit/test_web_limitations.py b/test/unit/test_web_limitations.py
new file mode 100644
index 0000000..6359390
--- /dev/null
+++ b/test/unit/test_web_limitations.py
@@ -0,0 +1,22 @@
+import os
+
+from scripts.test import shared
+from . import utils
+
+
+class WebLimitations(utils.BinaryenTestCase):
+    def test_many_params(self):
+        """Test that we warn on large numbers of parameters, which Web VMs
+        disallow."""
+
+        params = '(param i32) ' * 1001
+        module = '''
+        (module
+         (func $foo %s
+         )
+        )
+        ''' % params
+        p = shared.run_process(shared.WASM_OPT + ['-o', os.devnull],
+                               input=module, capture_output=True)
+        self.assertIn('Some VMs may not accept this binary because it has a large number of parameters in function foo.',
+                      p.stderr)
diff --git a/test/unit/utils.py b/test/unit/utils.py
index b75ed31..6fe2d4f 100644
--- a/test/unit/utils.py
+++ b/test/unit/utils.py
@@ -9,11 +9,12 @@ class BinaryenTestCase(unittest.TestCase):
         return os.path.join(shared.options.binaryen_test, 'unit', 'input',
                             filename)
 
-    def roundtrip(self, filename, opts=[]):
+    def roundtrip(self, filename, opts=[], debug=True):
+        opts = ['--mvp-features'] + opts
+        if debug:
+            opts.append('-g')
         path = self.input_path(filename)
-        p = shared.run_process(shared.WASM_OPT + ['-g', '-o', 'a.wasm', path] +
-                               opts)
-        self.assertEqual(p.returncode, 0)
+        shared.run_process(shared.WASM_OPT + ['-o', 'a.wasm', path] + opts)
         with open(path, 'rb') as f:
             with open('a.wasm', 'rb') as g:
                 self.assertEqual(g.read(), f.read())
@@ -22,15 +23,14 @@ class BinaryenTestCase(unittest.TestCase):
         path = self.input_path(filename)
         p = shared.run_process(shared.WASM_OPT +
                                ['--print', '-o', os.devnull, path],
-                               check=False, capture_output=True)
-        self.assertEqual(p.returncode, 0)
+                               capture_output=True)
         self.assertEqual(p.stderr, '')
         return p.stdout
 
     def check_features(self, filename, features, opts=[]):
         path = self.input_path(filename)
         cmd = shared.WASM_OPT + \
-            ['--print-features', '-o', os.devnull, path] + opts
+            ['--mvp-features', '--print-features', '-o', os.devnull, path] + opts
         p = shared.run_process(cmd, check=False, capture_output=True)
         self.assertEqual(p.returncode, 0)
         self.assertEqual(p.stderr, '')
diff --git a/test/unreachable-code.wast.from-wast b/test/unreachable-code.wast.from-wast
index 867a362..afd67f7 100644
--- a/test/unreachable-code.wast.from-wast
+++ b/test/unreachable-code.wast.from-wast
@@ -14,20 +14,16 @@
   )
  )
  (func $a-block
-  (block $block
-   (if
-    (i32.const 1)
-    (unreachable)
-   )
+  (if
+   (i32.const 1)
+   (unreachable)
   )
  )
  (func $b-block
-  (block $block
-   (if
-    (i32.const 1)
-    (unreachable)
-    (unreachable)
-   )
+  (if
+   (i32.const 1)
+   (unreachable)
+   (unreachable)
   )
  )
  (func $a-prepost
@@ -49,7 +45,7 @@
  )
  (func $a-block-prepost
   (nop)
-  (block $block
+  (block
    (if
     (i32.const 1)
     (unreachable)
@@ -59,7 +55,7 @@
  )
  (func $b-block-prepost
   (nop)
-  (block $block
+  (block
    (if
     (i32.const 1)
     (unreachable)
diff --git a/test/unreachable-code.wast.fromBinary b/test/unreachable-code.wast.fromBinary
index a93ffe7..67b8c30 100644
--- a/test/unreachable-code.wast.fromBinary
+++ b/test/unreachable-code.wast.fromBinary
@@ -44,22 +44,18 @@
  )
  (func $a-block-prepost
   (nop)
-  (block $label$1
-   (if
-    (i32.const 1)
-    (unreachable)
-   )
+  (if
+   (i32.const 1)
+   (unreachable)
   )
   (nop)
  )
  (func $b-block-prepost
   (nop)
-  (block $label$1
-   (if
-    (i32.const 1)
-    (unreachable)
-    (unreachable)
-   )
+  (if
+   (i32.const 1)
+   (unreachable)
+   (unreachable)
   )
  )
  (func $recurse
diff --git a/test/unreachable-code.wast.fromBinary.noDebugInfo b/test/unreachable-code.wast.fromBinary.noDebugInfo
index 8f9fd6b..3ff74e9 100644
--- a/test/unreachable-code.wast.fromBinary.noDebugInfo
+++ b/test/unreachable-code.wast.fromBinary.noDebugInfo
@@ -44,22 +44,18 @@
  )
  (func $6
   (nop)
-  (block $label$1
-   (if
-    (i32.const 1)
-    (unreachable)
-   )
+  (if
+   (i32.const 1)
+   (unreachable)
   )
   (nop)
  )
  (func $7
   (nop)
-  (block $label$1
-   (if
-    (i32.const 1)
-    (unreachable)
-    (unreachable)
-   )
+  (if
+   (i32.const 1)
+   (unreachable)
+   (unreachable)
   )
  )
  (func $8
diff --git a/test/wasm2js.asserts.js b/test/wasm2js.asserts.js
index 2770c07..2fff251 100644
--- a/test/wasm2js.asserts.js
+++ b/test/wasm2js.asserts.js
@@ -30,7 +30,7 @@
        return (actual_lo | 0) == (expected_lo | 0) && (actual_hi | 0) == (expected_hi | 0);
     }
   
-function asmFunc0(env) {
+function asmFunc0(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -41,7 +41,6 @@ function asmFunc0(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -67,8 +66,8 @@ function asmFunc0(env) {
  };
 }
 
-var retasmFunc0 = asmFunc0(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc0 = asmFunc0({
+});
 function check1() {
  retasmFunc0.empty();
  return 1 | 0;
diff --git a/test/wasm2js.traps.js b/test/wasm2js.traps.js
index d3b672d..0b1d24b 100644
--- a/test/wasm2js.traps.js
+++ b/test/wasm2js.traps.js
@@ -30,7 +30,7 @@
        return (actual_lo | 0) == (expected_lo | 0) && (actual_hi | 0) == (expected_hi | 0);
     }
   
-function asmFunc0(env) {
+function asmFunc0(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -41,7 +41,6 @@ function asmFunc0(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -67,8 +66,8 @@ function asmFunc0(env) {
  };
 }
 
-var retasmFunc0 = asmFunc0(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc0 = asmFunc0({
+});
 function check1() {
  retasmFunc0.empty();
  return 1 | 0;
diff --git a/test/wasm2js/add_div.2asm.js b/test/wasm2js/add_div.2asm.js
index f160dcb..07e1a9c 100644
--- a/test/wasm2js/add_div.2asm.js
+++ b/test/wasm2js/add_div.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo($0) {
@@ -23,6 +22,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/add_div.2asm.js.opt b/test/wasm2js/add_div.2asm.js.opt
index 1e575d9..6a6b29c 100644
--- a/test/wasm2js/add_div.2asm.js.opt
+++ b/test/wasm2js/add_div.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo($0) {
@@ -23,6 +22,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/atomic_fence.2asm.js b/test/wasm2js/atomic_fence.2asm.js
index 2caca85..8259183 100644
--- a/test/wasm2js/atomic_fence.2asm.js
+++ b/test/wasm2js/atomic_fence.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(1507328);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -19,7 +19,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -56,6 +55,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var atomic_fence = retasmFunc.atomic_fence;
diff --git a/test/wasm2js/atomic_fence.2asm.js.opt b/test/wasm2js/atomic_fence.2asm.js.opt
index 666988a..f201b8e 100644
--- a/test/wasm2js/atomic_fence.2asm.js.opt
+++ b/test/wasm2js/atomic_fence.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -22,6 +21,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var atomic_fence = retasmFunc.atomic_fence;
diff --git a/test/wasm2js/atomics_32.2asm.js b/test/wasm2js/atomics_32.2asm.js
index 1eb0334..bd9f10f 100644
--- a/test/wasm2js/atomics_32.2asm.js
+++ b/test/wasm2js/atomics_32.2asm.js
@@ -24,11 +24,6 @@
 memorySegments[0] = base64DecodeToExistingUint8Array(new Uint8Array(6), 0, "aGVsbG8s");
 memorySegments[1] = base64DecodeToExistingUint8Array(new Uint8Array(6), 0, "d29ybGQh");
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_atomic_wait_i32(ptr, expected, timeoutLow, timeoutHigh) {
     var timeout = Infinity;
     if (timeoutHigh >= 0) {
@@ -94,7 +89,7 @@ memorySegments[1] = base64DecodeToExistingUint8Array(new Uint8Array(6), 0, "d29y
     bufferView.set(memorySegments[segment].subarray(offset, offset + size), dest);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(16777216);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -114,7 +109,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -158,6 +152,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var test = retasmFunc.test;
diff --git a/test/wasm2js/atomics_32.2asm.js.opt b/test/wasm2js/atomics_32.2asm.js.opt
index 8d2a205..eecd0eb 100644
--- a/test/wasm2js/atomics_32.2asm.js.opt
+++ b/test/wasm2js/atomics_32.2asm.js.opt
@@ -24,11 +24,6 @@
 memorySegments[0] = base64DecodeToExistingUint8Array(new Uint8Array(6), 0, "aGVsbG8s");
 memorySegments[1] = base64DecodeToExistingUint8Array(new Uint8Array(6), 0, "d29ybGQh");
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_atomic_wait_i32(ptr, expected, timeoutLow, timeoutHigh) {
     var timeout = Infinity;
     if (timeoutHigh >= 0) {
@@ -94,7 +89,7 @@ memorySegments[1] = base64DecodeToExistingUint8Array(new Uint8Array(6), 0, "d29y
     bufferView.set(memorySegments[segment].subarray(offset, offset + size), dest);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(16777216);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -114,7 +109,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -154,6 +148,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var test = retasmFunc.test;
diff --git a/test/wasm2js/base64.2asm.js b/test/wasm2js/base64.2asm.js
index 3278e98..91dfa17 100644
--- a/test/wasm2js/base64.2asm.js
+++ b/test/wasm2js/base64.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/base64.2asm.js.opt b/test/wasm2js/base64.2asm.js.opt
index 3278e98..91dfa17 100644
--- a/test/wasm2js/base64.2asm.js.opt
+++ b/test/wasm2js/base64.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/br.2asm.js b/test/wasm2js/br.2asm.js
index ccd6bff..91d0222 100644
--- a/test/wasm2js/br.2asm.js
+++ b/test/wasm2js/br.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function dummy() {
@@ -716,9 +716,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var type_i32 = retasmFunc.type_i32;
 export var type_i64 = retasmFunc.type_i64;
 export var type_f32 = retasmFunc.type_f32;
diff --git a/test/wasm2js/br_table.2asm.js b/test/wasm2js/br_table.2asm.js
index be7b89b..84f6e4d 100644
--- a/test/wasm2js/br_table.2asm.js
+++ b/test/wasm2js/br_table.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function dummy() {
@@ -13421,9 +13421,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var type_i32 = retasmFunc.type_i32;
 export var type_i64 = retasmFunc.type_i64;
 export var type_f32 = retasmFunc.type_f32;
diff --git a/test/wasm2js/br_table_hoisting.2asm.js b/test/wasm2js/br_table_hoisting.2asm.js
index 9bf6abd..70d3e64 100644
--- a/test/wasm2js/br_table_hoisting.2asm.js
+++ b/test/wasm2js/br_table_hoisting.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function zed($0) {
@@ -177,8 +176,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo1 = retasmFunc.foo1;
 export var foo2 = retasmFunc.foo2;
 export var foo3 = retasmFunc.foo3;
diff --git a/test/wasm2js/br_table_hoisting.2asm.js.opt b/test/wasm2js/br_table_hoisting.2asm.js.opt
index 422c33a..cd77a53 100644
--- a/test/wasm2js/br_table_hoisting.2asm.js.opt
+++ b/test/wasm2js/br_table_hoisting.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function zed($0) {
@@ -167,8 +166,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo1 = retasmFunc.foo1;
 export var foo2 = retasmFunc.foo2;
 export var foo3 = retasmFunc.foo3;
diff --git a/test/wasm2js/br_table_temp.2asm.js b/test/wasm2js/br_table_temp.2asm.js
index 32850d0..fc039ab 100644
--- a/test/wasm2js/br_table_temp.2asm.js
+++ b/test/wasm2js/br_table_temp.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function dummy() {
@@ -13264,8 +13263,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var type_i32 = retasmFunc.type_i32;
 export var type_i64 = retasmFunc.type_i64;
 export var type_f32 = retasmFunc.type_f32;
diff --git a/test/wasm2js/br_table_temp.2asm.js.opt b/test/wasm2js/br_table_temp.2asm.js.opt
index 5c7d88a..eee9e56 100644
--- a/test/wasm2js/br_table_temp.2asm.js.opt
+++ b/test/wasm2js/br_table_temp.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function dummy() {
@@ -12685,8 +12684,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var type_i32 = retasmFunc.type_i32;
 export var type_i64 = retasmFunc.type_i64;
 export var type_f32 = retasmFunc.type_f32;
diff --git a/test/wasm2js/br_table_to_loop.2asm.js b/test/wasm2js/br_table_to_loop.2asm.js
index 0d33a87..67ac434 100644
--- a/test/wasm2js/br_table_to_loop.2asm.js
+++ b/test/wasm2js/br_table_to_loop.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -41,7 +40,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var exp1 = retasmFunc.exp1;
 export var exp2 = retasmFunc.exp2;
diff --git a/test/wasm2js/br_table_to_loop.2asm.js.opt b/test/wasm2js/br_table_to_loop.2asm.js.opt
index b69dcdd..94280b2 100644
--- a/test/wasm2js/br_table_to_loop.2asm.js.opt
+++ b/test/wasm2js/br_table_to_loop.2asm.js.opt
@@ -1,5 +1,7 @@
 
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,11 +12,10 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
-  while (1) continue;
+  wasm2js_trap();
  }
  
  function $1() {
@@ -27,7 +28,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var exp1 = retasmFunc.exp1;
 export var exp2 = retasmFunc.exp2;
diff --git a/test/wasm2js/br_table_to_loop.wast b/test/wasm2js/br_table_to_loop.wast
index 83d3702..a74d5ff 100644
--- a/test/wasm2js/br_table_to_loop.wast
+++ b/test/wasm2js/br_table_to_loop.wast
@@ -1,6 +1,8 @@
 (module
  (func "exp1"
   (block $block
+   ;; An infinite loop. When optimizing, wasm2js enables ignore-implicit-traps
+   ;; and so it can simplify this.
    (loop $loop
     (br_table $block $loop $block (i32.const 1))
    )
@@ -8,6 +10,7 @@
  )
  (func "exp2"
   (block $block
+   ;; A loop that never executes. This can be optimized into a nop.
    (loop $loop
     (br_table $loop $block $loop (i32.const 1))
    )
diff --git a/test/wasm2js/break-drop.2asm.js b/test/wasm2js/break-drop.2asm.js
index a3fc7a9..8cde788 100644
--- a/test/wasm2js/break-drop.2asm.js
+++ b/test/wasm2js/break-drop.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -32,8 +31,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var br = retasmFunc.br;
 export var br_if = retasmFunc.br_if;
 export var br_table = retasmFunc.br_table;
diff --git a/test/wasm2js/bulk-memory.2asm.js b/test/wasm2js/bulk-memory.2asm.js
index a517931..4901736 100644
--- a/test/wasm2js/bulk-memory.2asm.js
+++ b/test/wasm2js/bulk-memory.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,16 +17,11 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 
   var bufferView;
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_memory_fill(dest, value, size) {
     dest = dest >>> 0;
     size = size >>> 0;
@@ -35,7 +29,7 @@ var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
     bufferView.fill(value, dest, dest + size);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -55,7 +49,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1, $1_1, $2) {
@@ -103,8 +96,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var fill = retasmFunc.fill;
 export var load8_u = retasmFunc.load8_u;
 
@@ -133,17 +126,12 @@ function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 0, "qrvM3Q==");
 }
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_memory_copy(dest, source, size) {
     // TODO: traps on invalid things
     bufferView.copyWithin(dest, source, source + size);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -163,7 +151,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1, $1_1, $2) {
@@ -179,7 +166,7 @@ function asmFunc(env) {
  }
  
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -190,8 +177,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var copy = retasmFunc.copy;
 export var load8_u = retasmFunc.load8_u;
 
@@ -219,17 +206,12 @@ export var load8_u = retasmFunc.load8_u;
   }
 memorySegments[0] = base64DecodeToExistingUint8Array(new Uint8Array(4), 0, "qrvM3Q==");
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_memory_init(segment, dest, offset, size) {
     // TODO: traps on invalid things
     bufferView.set(memorySegments[segment].subarray(offset, offset + size), dest);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -249,7 +231,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1, $1_1, $2) {
@@ -297,8 +278,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var init = retasmFunc.init;
 export var load8_u = retasmFunc.load8_u;
 
@@ -329,11 +310,6 @@ function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 0, "");
 }
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_data_drop(segment) {
     // TODO: traps on invalid things
     memorySegments[segment] = new Uint8Array(0);
@@ -344,7 +320,7 @@ function initActiveSegments(imports) {
     bufferView.set(memorySegments[segment].subarray(offset, offset + size), dest);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -364,7 +340,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -384,7 +359,7 @@ function asmFunc(env) {
  }
  
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -419,8 +394,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var drop_passive = retasmFunc.drop_passive;
 export var init_passive = retasmFunc.init_passive;
 export var drop_active = retasmFunc.drop_active;
diff --git a/test/wasm2js/bulk-memory.2asm.js.opt b/test/wasm2js/bulk-memory.2asm.js.opt
index 4febf1a..50c1eb1 100644
--- a/test/wasm2js/bulk-memory.2asm.js.opt
+++ b/test/wasm2js/bulk-memory.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,16 +17,11 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 
   var bufferView;
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_memory_fill(dest, value, size) {
     dest = dest >>> 0;
     size = size >>> 0;
@@ -35,7 +29,7 @@ var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
     bufferView.fill(value, dest, dest + size);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -55,7 +49,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1, $1_1, $2) {
@@ -103,8 +96,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var fill = retasmFunc.fill;
 export var load8_u = retasmFunc.load8_u;
 
@@ -133,17 +126,12 @@ function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 0, "qrvM3Q==");
 }
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_memory_copy(dest, source, size) {
     // TODO: traps on invalid things
     bufferView.copyWithin(dest, source, source + size);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -163,7 +151,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1, $1_1, $2) {
@@ -179,7 +166,7 @@ function asmFunc(env) {
  }
  
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -190,8 +177,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var copy = retasmFunc.copy;
 export var load8_u = retasmFunc.load8_u;
 
@@ -219,17 +206,12 @@ export var load8_u = retasmFunc.load8_u;
   }
 memorySegments[0] = base64DecodeToExistingUint8Array(new Uint8Array(4), 0, "qrvM3Q==");
 
-  var scratchBuffer = new ArrayBuffer(16);
-  var i32ScratchView = new Int32Array(scratchBuffer);
-  var f32ScratchView = new Float32Array(scratchBuffer);
-  var f64ScratchView = new Float64Array(scratchBuffer);
-  
   function wasm2js_memory_init(segment, dest, offset, size) {
     // TODO: traps on invalid things
     bufferView.set(memorySegments[segment].subarray(offset, offset + size), dest);
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -249,7 +231,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1, $1_1, $2) {
@@ -297,12 +278,15 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var init = retasmFunc.init;
 export var load8_u = retasmFunc.load8_u;
 
-function asmFunc(env) {
+  var bufferView;
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -322,7 +306,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -331,10 +314,11 @@ function asmFunc(env) {
  
  function $1() {
   if (__wasm_memory_size() << 16 >>> 0 < 0) {
-   abort()
+   wasm2js_trap()
   }
  }
  
+ bufferView = HEAPU8;
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -356,6 +340,7 @@ function asmFunc(env) {
    HEAPF32 = new Float32Array(newBuffer);
    HEAPF64 = new Float64Array(newBuffer);
    buffer = newBuffer;
+   bufferView = HEAPU8;
   }
   return oldPages;
  }
@@ -368,8 +353,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var drop_passive = retasmFunc.drop_passive;
 export var init_passive = retasmFunc.init_passive;
 export var drop_active = retasmFunc.drop_active;
diff --git a/test/wasm2js/comments.2asm.js b/test/wasm2js/comments.2asm.js
index 5689ecd..9ee9d1c 100644
--- a/test/wasm2js/comments.2asm.js
+++ b/test/wasm2js/comments.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,10 +17,10 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -32,7 +31,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -40,5 +38,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/conversions-modified.2asm.js b/test/wasm2js/conversions-modified.2asm.js
index 6f4ec81..892bdf0 100644
--- a/test/wasm2js/conversions-modified.2asm.js
+++ b/test/wasm2js/conversions-modified.2asm.js
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
 
   var scratchBuffer = new ArrayBuffer(16);
@@ -30,7 +30,7 @@ import { setTempRet0 } from 'env';
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -41,9 +41,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(x) {
@@ -619,9 +619,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i64_extend_s_i32 = retasmFunc.i64_extend_s_i32;
 export var i64_extend_u_i32 = retasmFunc.i64_extend_u_i32;
 export var i32_wrap_i64 = retasmFunc.i32_wrap_i64;
diff --git a/test/wasm2js/conversions-modified.2asm.js.opt b/test/wasm2js/conversions-modified.2asm.js.opt
index 1b96f42..4c2b6d1 100644
--- a/test/wasm2js/conversions-modified.2asm.js.opt
+++ b/test/wasm2js/conversions-modified.2asm.js.opt
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
 
   var scratchBuffer = new ArrayBuffer(16);
@@ -30,7 +30,7 @@ import { setTempRet0 } from 'env';
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -41,9 +41,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $3($0) {
@@ -162,9 +162,8 @@ function asmFunc(env) {
   $1 = wasm2js_scratch_load_i32(1) | 0;
   $2 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $1;
-  $1 = $2;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $1;
+  return $2;
  }
  
  return {
@@ -196,9 +195,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i64_extend_s_i32 = retasmFunc.i64_extend_s_i32;
 export var i64_extend_u_i32 = retasmFunc.i64_extend_u_i32;
 export var i32_wrap_i64 = retasmFunc.i32_wrap_i64;
diff --git a/test/wasm2js/deterministic.2asm.js b/test/wasm2js/deterministic.2asm.js
index 8049c3f..09731cf 100644
--- a/test/wasm2js/deterministic.2asm.js
+++ b/test/wasm2js/deterministic.2asm.js
@@ -1,5 +1,9 @@
 
-function asmFunc(env) {
+  var bufferView;
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  var HEAP8 = new Int8Array(buffer);
@@ -20,17 +24,17 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var global$0 = -44;
  function $0() {
   if ((global$0 >>> 0) / ((HEAP32[0 >> 2] | 0) >>> 0) | 0) {
-   abort()
+   wasm2js_trap()
   }
   return 1 | 0;
  }
  
+ bufferView = HEAPU8;
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -41,7 +45,9 @@ function asmFunc(env) {
 }
 
 var memasmFunc = new ArrayBuffer(65536);
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
+var retasmFunc = asmFunc({
+  "env": {
     memory: { buffer : memasmFunc }
-  });
+  },
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/deterministic.2asm.js.opt b/test/wasm2js/deterministic.2asm.js.opt
index b6b137d..a5bc1b3 100644
--- a/test/wasm2js/deterministic.2asm.js.opt
+++ b/test/wasm2js/deterministic.2asm.js.opt
@@ -1,5 +1,9 @@
 
-function asmFunc(env) {
+  var bufferView;
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  var HEAP8 = new Int8Array(buffer);
@@ -20,16 +24,16 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
   if (4294967252 / HEAPU32[0] | 0) {
-   abort()
+   wasm2js_trap()
   }
   return 1;
  }
  
+ bufferView = HEAPU8;
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -40,7 +44,9 @@ function asmFunc(env) {
 }
 
 var memasmFunc = new ArrayBuffer(65536);
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
+var retasmFunc = asmFunc({
+  "env": {
     memory: { buffer : memasmFunc }
-  });
+  },
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/dot_import.2asm.js b/test/wasm2js/dot_import.2asm.js
index bfeb025..f2b5fa1 100644
--- a/test/wasm2js/dot_import.2asm.js
+++ b/test/wasm2js/dot_import.2asm.js
@@ -1,6 +1,6 @@
-import { ba_se } from 'mod.ule';
+import * as mod_ule from 'mod.ule';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,10 +11,10 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
- var base = env.ba_se;
+ var mod_ule = imports["mod.ule"];
+ var base = mod_ule["ba.se"];
  function $0() {
   base();
  }
@@ -24,7 +24,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    ba_se
-  });
+var retasmFunc = asmFunc({
+  "mod.ule": mod_ule,
+});
 export var exported = retasmFunc.exported;
diff --git a/test/wasm2js/dot_import.2asm.js.opt b/test/wasm2js/dot_import.2asm.js.opt
index bfeb025..f2b5fa1 100644
--- a/test/wasm2js/dot_import.2asm.js.opt
+++ b/test/wasm2js/dot_import.2asm.js.opt
@@ -1,6 +1,6 @@
-import { ba_se } from 'mod.ule';
+import * as mod_ule from 'mod.ule';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,10 +11,10 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
- var base = env.ba_se;
+ var mod_ule = imports["mod.ule"];
+ var base = mod_ule["ba.se"];
  function $0() {
   base();
  }
@@ -24,7 +24,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    ba_se
-  });
+var retasmFunc = asmFunc({
+  "mod.ule": mod_ule,
+});
 export var exported = retasmFunc.exported;
diff --git a/test/wasm2js/dynamicLibrary.2asm.js b/test/wasm2js/dynamicLibrary.2asm.js
index 03f8b79..bae9aeb 100644
--- a/test/wasm2js/dynamicLibrary.2asm.js
+++ b/test/wasm2js/dynamicLibrary.2asm.js
@@ -1,5 +1,4 @@
-import { memoryBase } from 'env';
-import { tableBase } from 'env';
+import * as env from 'env';
 
 function Table(ret) {
   // grow method not included; table is not growable
@@ -34,9 +33,10 @@ function Table(ret) {
     return uint8Array;
   }
 function initActiveSegments(imports) {
-  base64DecodeToExistingUint8Array(bufferView, imports[memoryBase], "ZHluYW1pYyBkYXRh");
+  base64DecodeToExistingUint8Array(bufferView, imports['env']['memoryBase'], "ZHluYW1pYyBkYXRh");
 }
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  var HEAP8 = new Int8Array(buffer);
@@ -57,7 +57,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var import$memoryBase = env.memoryBase | 0;
@@ -75,7 +74,7 @@ function asmFunc(env) {
  }
  
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  var FUNCTION_TABLE = Table(new Array(10));
  FUNCTION_TABLE[import$tableBase + 0] = foo;
  FUNCTION_TABLE[import$tableBase + 1] = bar;
@@ -90,7 +89,9 @@ function asmFunc(env) {
 }
 
 var memasmFunc = new ArrayBuffer(16777216);
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
+var retasmFunc = asmFunc({
+  "env": {
     memory: { buffer : memasmFunc }
-  });
+  },
+});
 export var baz = retasmFunc.baz;
diff --git a/test/wasm2js/dynamicLibrary.2asm.js.opt b/test/wasm2js/dynamicLibrary.2asm.js.opt
index c862938..61bdab1 100644
--- a/test/wasm2js/dynamicLibrary.2asm.js.opt
+++ b/test/wasm2js/dynamicLibrary.2asm.js.opt
@@ -1,5 +1,4 @@
-import { memoryBase } from 'env';
-import { tableBase } from 'env';
+import * as env from 'env';
 
 function Table(ret) {
   // grow method not included; table is not growable
@@ -34,9 +33,10 @@ function Table(ret) {
     return uint8Array;
   }
 function initActiveSegments(imports) {
-  base64DecodeToExistingUint8Array(bufferView, imports[memoryBase], "ZHluYW1pYyBkYXRh");
+  base64DecodeToExistingUint8Array(bufferView, imports['env']['memoryBase'], "ZHluYW1pYyBkYXRh");
 }
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  var HEAP8 = new Int8Array(buffer);
@@ -57,7 +57,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var import$memoryBase = env.memoryBase | 0;
@@ -67,7 +66,7 @@ function asmFunc(env) {
  }
  
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  var FUNCTION_TABLE = Table(new Array(10));
  FUNCTION_TABLE[import$tableBase + 0] = foo;
  FUNCTION_TABLE[import$tableBase + 1] = foo;
@@ -82,7 +81,9 @@ function asmFunc(env) {
 }
 
 var memasmFunc = new ArrayBuffer(16777216);
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
+var retasmFunc = asmFunc({
+  "env": {
     memory: { buffer : memasmFunc }
-  });
+  },
+});
 export var baz = retasmFunc.baz;
diff --git a/test/wasm2js/empty_export.2asm.js b/test/wasm2js/empty_export.2asm.js
index a4e12d5..7a6a012 100644
--- a/test/wasm2js/empty_export.2asm.js
+++ b/test/wasm2js/empty_export.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo() {
@@ -22,6 +21,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var $ = retasmFunc.$;
diff --git a/test/wasm2js/empty_export.2asm.js.opt b/test/wasm2js/empty_export.2asm.js.opt
index a4e12d5..7a6a012 100644
--- a/test/wasm2js/empty_export.2asm.js.opt
+++ b/test/wasm2js/empty_export.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo() {
@@ -22,6 +21,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var $ = retasmFunc.$;
diff --git a/test/wasm2js/empty_table.2asm.js b/test/wasm2js/empty_table.2asm.js
index 3278e98..91dfa17 100644
--- a/test/wasm2js/empty_table.2asm.js
+++ b/test/wasm2js/empty_table.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/emscripten-grow-no.2asm.js b/test/wasm2js/emscripten-grow-no.2asm.js
index d349b62..66e3305 100644
--- a/test/wasm2js/emscripten-grow-no.2asm.js
+++ b/test/wasm2js/emscripten-grow-no.2asm.js
@@ -1,4 +1,4 @@
-function instantiate(asmLibraryArg) {
+function instantiate(info) {
   var bufferView;
   var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
   for (var i = 25; i >= 0; --i) {
@@ -23,7 +23,8 @@ function instantiate(asmLibraryArg) {
 function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 1600, "YWJj");
 }
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  var HEAP8 = new Int8Array(buffer);
@@ -44,7 +45,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  // EMSCRIPTEN_START_FUNCS
@@ -52,7 +52,7 @@ function asmFunc(env) {
  // EMSCRIPTEN_END_FUNCS
 ;
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -72,5 +72,5 @@ function asmFunc(env) {
  };
 }
 
-  return asmFunc(asmLibraryArg);
+  return asmFunc(info);
 }
diff --git a/test/wasm2js/emscripten-grow-no.2asm.js.opt b/test/wasm2js/emscripten-grow-no.2asm.js.opt
index d349b62..66e3305 100644
--- a/test/wasm2js/emscripten-grow-no.2asm.js.opt
+++ b/test/wasm2js/emscripten-grow-no.2asm.js.opt
@@ -1,4 +1,4 @@
-function instantiate(asmLibraryArg) {
+function instantiate(info) {
   var bufferView;
   var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
   for (var i = 25; i >= 0; --i) {
@@ -23,7 +23,8 @@ function instantiate(asmLibraryArg) {
 function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 1600, "YWJj");
 }
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  var HEAP8 = new Int8Array(buffer);
@@ -44,7 +45,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  // EMSCRIPTEN_START_FUNCS
@@ -52,7 +52,7 @@ function asmFunc(env) {
  // EMSCRIPTEN_END_FUNCS
 ;
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -72,5 +72,5 @@ function asmFunc(env) {
  };
 }
 
-  return asmFunc(asmLibraryArg);
+  return asmFunc(info);
 }
diff --git a/test/wasm2js/emscripten-grow-yes.2asm.js b/test/wasm2js/emscripten-grow-yes.2asm.js
index 6f02ce3..8fe2832 100644
--- a/test/wasm2js/emscripten-grow-yes.2asm.js
+++ b/test/wasm2js/emscripten-grow-yes.2asm.js
@@ -1,4 +1,4 @@
-function instantiate(asmLibraryArg) {
+function instantiate(info) {
   var bufferView;
   var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
   for (var i = 25; i >= 0; --i) {
@@ -23,7 +23,8 @@ function instantiate(asmLibraryArg) {
 function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 1600, "YWJj");
 }
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  memory.grow = __wasm_memory_grow;
@@ -45,7 +46,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  // EMSCRIPTEN_START_FUNCS
@@ -57,7 +57,7 @@ function asmFunc(env) {
  // EMSCRIPTEN_END_FUNCS
 ;
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -101,5 +101,5 @@ function asmFunc(env) {
  };
 }
 
-  return asmFunc(asmLibraryArg);
+  return asmFunc(info);
 }
diff --git a/test/wasm2js/emscripten-grow-yes.2asm.js.opt b/test/wasm2js/emscripten-grow-yes.2asm.js.opt
index 6f02ce3..8fe2832 100644
--- a/test/wasm2js/emscripten-grow-yes.2asm.js.opt
+++ b/test/wasm2js/emscripten-grow-yes.2asm.js.opt
@@ -1,4 +1,4 @@
-function instantiate(asmLibraryArg) {
+function instantiate(info) {
   var bufferView;
   var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
   for (var i = 25; i >= 0; --i) {
@@ -23,7 +23,8 @@ function instantiate(asmLibraryArg) {
 function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 1600, "YWJj");
 }
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.memory;
  var buffer = memory.buffer;
  memory.grow = __wasm_memory_grow;
@@ -45,7 +46,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  // EMSCRIPTEN_START_FUNCS
@@ -57,7 +57,7 @@ function asmFunc(env) {
  // EMSCRIPTEN_END_FUNCS
 ;
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  function __wasm_memory_size() {
   return buffer.byteLength / 65536 | 0;
  }
@@ -101,5 +101,5 @@ function asmFunc(env) {
  };
 }
 
-  return asmFunc(asmLibraryArg);
+  return asmFunc(info);
 }
diff --git a/test/wasm2js/emscripten.2asm.js b/test/wasm2js/emscripten.2asm.js
index d411b62..81d272c 100644
--- a/test/wasm2js/emscripten.2asm.js
+++ b/test/wasm2js/emscripten.2asm.js
@@ -1,4 +1,4 @@
-function instantiate(asmLibraryArg) {
+function instantiate(info) {
   var bufferView;
   var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
   for (var i = 25; i >= 0; --i) {
@@ -24,8 +24,11 @@ function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 1024, "aGVsbG8sIHdvcmxkIQoAAJwMAAAtKyAgIDBYMHgAKG51bGwpAAAAAAAAAAAAAAAAEQAKABEREQAAAAAFAAAAAAAACQAAAAALAAAAAAAAAAARAA8KERERAwoHAAETCQsLAAAJBgsAAAsABhEAAAAREREAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAEQAKChEREQAKAAACAAkLAAAACQALAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAwAAAAADAAAAAAJDAAAAAAADAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAANAAAABA0AAAAACQ4AAAAAAA4AAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAADwAAAAAPAAAAAAkQAAAAAAAQAAAQAAASAAAAEhISAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAASEhIAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAKAAAAAAoAAAAACQsAAAAAAAsAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAAMAAAAAAkMAAAAAAAMAAAMAAAwMTIzNDU2Nzg5QUJDREVGLTBYKzBYIDBYLTB4KzB4IDB4AGluZgBJTkYAbmFuAE5BTgAuAA==");
   base64DecodeToExistingUint8Array(bufferView, 1600, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
 }
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(16777216);
+ var env = imports.env;
  var FUNCTION_TABLE = env.table;
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -45,7 +48,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var syscall$6 = env.__syscall6;
@@ -66,7 +68,7 @@ function asmFunc(env) {
  }
  
  function foo() {
-  abort();
+  wasm2js_trap();
  }
  
  function bar() {
@@ -161,7 +163,7 @@ function asmFunc(env) {
  
  function __growWasmMemory($0) {
   $0 = $0 | 0;
-  return abort() | 0;
+  return wasm2js_trap() | 0;
  }
  
  function internal(x) {
@@ -206,13 +208,13 @@ function asmFunc(env) {
    bools(2 | 0) | 0
   }
   bools(!(x ^ 1 | 0) | 0) | 0;
-  abort();
+  wasm2js_trap();
  }
  
  // EMSCRIPTEN_END_FUNCS
 ;
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  FUNCTION_TABLE[1] = foo;
  FUNCTION_TABLE[2] = bar;
  FUNCTION_TABLE[3] = tabled;
@@ -231,5 +233,5 @@ function asmFunc(env) {
  };
 }
 
-  return asmFunc(asmLibraryArg);
+  return asmFunc(info);
 }
diff --git a/test/wasm2js/emscripten.2asm.js.opt b/test/wasm2js/emscripten.2asm.js.opt
index 57df79d..dd28fd8 100644
--- a/test/wasm2js/emscripten.2asm.js.opt
+++ b/test/wasm2js/emscripten.2asm.js.opt
@@ -1,4 +1,4 @@
-function instantiate(asmLibraryArg) {
+function instantiate(info) {
   var bufferView;
   var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
   for (var i = 25; i >= 0; --i) {
@@ -36,8 +36,11 @@ function initActiveSegments(imports) {
   base64DecodeToExistingUint8Array(bufferView, 1501, "DA==");
   base64DecodeToExistingUint8Array(bufferView, 1513, "DAAAAAAMAAAAAAkMAAAAAAAMAAAMAAAwMTIzNDU2Nzg5QUJDREVGLTBYKzBYIDBYLTB4KzB4IDB4AGluZgBJTkYAbmFuAE5BTgAu");
 }
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(16777216);
+ var env = imports.env;
  var FUNCTION_TABLE = env.table;
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -57,7 +60,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var syscall$6 = env.__syscall6;
@@ -75,7 +77,7 @@ function asmFunc(env) {
  }
  
  function foo() {
-  abort();
+  wasm2js_trap();
  }
  
  function bar() {
@@ -166,7 +168,7 @@ function asmFunc(env) {
  
  function __growWasmMemory($0) {
   $0 = $0 | 0;
-  return abort() | 0;
+  return wasm2js_trap() | 0;
  }
  
  function internal($0) {
@@ -201,13 +203,13 @@ function asmFunc(env) {
    bools(2)
   }
   bools(!($0 ^ 1));
-  abort();
+  wasm2js_trap();
  }
  
  // EMSCRIPTEN_END_FUNCS
 ;
  bufferView = HEAPU8;
- initActiveSegments(env);
+ initActiveSegments(imports);
  FUNCTION_TABLE[1] = foo;
  FUNCTION_TABLE[2] = bar;
  FUNCTION_TABLE[3] = internal;
@@ -226,5 +228,5 @@ function asmFunc(env) {
  };
 }
 
-  return asmFunc(asmLibraryArg);
+  return asmFunc(info);
 }
diff --git a/test/wasm2js/endianness.2asm.js b/test/wasm2js/endianness.2asm.js
index 5d32183..12b4c8d 100644
--- a/test/wasm2js/endianness.2asm.js
+++ b/test/wasm2js/endianness.2asm.js
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
   var bufferView;
 
@@ -31,7 +31,7 @@ import { setTempRet0 } from 'env';
     return f32ScratchView[2];
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -51,9 +51,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function i16_store_little(address, value) {
@@ -700,9 +700,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_load16_s = retasmFunc.i32_load16_s;
 export var i32_load16_u = retasmFunc.i32_load16_u;
 export var i32_load = retasmFunc.i32_load;
diff --git a/test/wasm2js/excess_fallthrough.2asm.js b/test/wasm2js/excess_fallthrough.2asm.js
index 17db957..9bad3f3 100644
--- a/test/wasm2js/excess_fallthrough.2asm.js
+++ b/test/wasm2js/excess_fallthrough.2asm.js
@@ -1,5 +1,7 @@
 
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +12,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function bar() {
@@ -22,18 +23,18 @@ function asmFunc(env) {
   label$4 : while (1) {
    label$5 : {
     bar();
-    block : {
+    label$7 : {
      switch (123 | 0) {
      case 0:
-      bar();
-      break;
+      break label$7;
      default:
       break label$5;
      };
     }
+    bar();
     return;
    }
-   abort();
+   wasm2js_trap();
   };
  }
  
@@ -42,6 +43,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/excess_fallthrough.2asm.js.opt b/test/wasm2js/excess_fallthrough.2asm.js.opt
index d54b292..0a71bff 100644
--- a/test/wasm2js/excess_fallthrough.2asm.js.opt
+++ b/test/wasm2js/excess_fallthrough.2asm.js.opt
@@ -1,5 +1,7 @@
 
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,12 +12,11 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo($0) {
   $0 = $0 | 0;
-  abort();
+  wasm2js_trap();
  }
  
  return {
@@ -23,6 +24,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/export_global.2asm.js b/test/wasm2js/export_global.2asm.js
index 7db0d3a..b2efa0b 100644
--- a/test/wasm2js/export_global.2asm.js
+++ b/test/wasm2js/export_global.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var global0 = 655360;
@@ -31,7 +30,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var HELLO = retasmFunc.HELLO;
 export var helloWorld = retasmFunc.helloWorld;
diff --git a/test/wasm2js/export_global.2asm.js.opt b/test/wasm2js/export_global.2asm.js.opt
index eb4e880..2b60ada 100644
--- a/test/wasm2js/export_global.2asm.js.opt
+++ b/test/wasm2js/export_global.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var global0 = 655360;
@@ -31,7 +30,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var HELLO = retasmFunc.HELLO;
 export var helloWorld = retasmFunc.helloWorld;
diff --git a/test/wasm2js/f32.2asm.js b/test/wasm2js/f32.2asm.js
index 66c0b93..728d9eb 100644
--- a/test/wasm2js/f32.2asm.js
+++ b/test/wasm2js/f32.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x, y) {
@@ -80,14 +79,12 @@ function asmFunc(env) {
   var$1 = Math_fround(Math_floor(var$0));
   var$2 = Math_fround(var$0 - var$1);
   if (!(var$2 < Math_fround(.5))) {
-   block : {
-    var$0 = Math_fround(Math_ceil(var$0));
-    if (var$2 > Math_fround(.5)) {
-     return Math_fround(var$0)
-    }
-    var$2 = Math_fround(var$1 * Math_fround(.5));
-    var$1 = Math_fround(var$2 - Math_fround(Math_floor(var$2))) == Math_fround(0.0) ? var$1 : var$0;
+   var$0 = Math_fround(Math_ceil(var$0));
+   if (var$2 > Math_fround(.5)) {
+    return Math_fround(var$0)
    }
+   var$2 = Math_fround(var$1 * Math_fround(.5));
+   var$1 = Math_fround(var$2 - Math_fround(Math_floor(var$2))) == Math_fround(0.0) ? var$1 : var$0;
   }
   return Math_fround(var$1);
  }
@@ -107,8 +104,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var add = retasmFunc.add;
 export var sub = retasmFunc.sub;
 export var mul = retasmFunc.mul;
diff --git a/test/wasm2js/f32_cmp.2asm.js b/test/wasm2js/f32_cmp.2asm.js
index 54ba603..85a0289 100644
--- a/test/wasm2js/f32_cmp.2asm.js
+++ b/test/wasm2js/f32_cmp.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x, y) {
@@ -59,8 +58,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var eq = retasmFunc.eq;
 export var ne = retasmFunc.ne;
 export var lt = retasmFunc.lt;
diff --git a/test/wasm2js/f64_cmp.2asm.js b/test/wasm2js/f64_cmp.2asm.js
index 127beaa..d391752 100644
--- a/test/wasm2js/f64_cmp.2asm.js
+++ b/test/wasm2js/f64_cmp.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x, y) {
@@ -59,8 +58,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var eq = retasmFunc.eq;
 export var ne = retasmFunc.ne;
 export var lt = retasmFunc.lt;
diff --git a/test/wasm2js/fac.2asm.js b/test/wasm2js/fac.2asm.js
index e8b31b1..ac0640d 100644
--- a/test/wasm2js/fac.2asm.js
+++ b/test/wasm2js/fac.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0($0_1, $0$hi) {
@@ -574,9 +574,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var fac_rec = retasmFunc.fac_rec;
 export var fac_rec_named = retasmFunc.fac_rec_named;
 export var fac_iter = retasmFunc.fac_iter;
diff --git a/test/wasm2js/float-ops.2asm.js b/test/wasm2js/float-ops.2asm.js
index 4ae8dbc..c9fe7cd 100644
--- a/test/wasm2js/float-ops.2asm.js
+++ b/test/wasm2js/float-ops.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0, $1_1) {
@@ -505,8 +504,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var f32_add = retasmFunc.f32_add;
 export var f32_sub = retasmFunc.f32_sub;
 export var f32_mul = retasmFunc.f32_mul;
diff --git a/test/wasm2js/float-ops.2asm.js.opt b/test/wasm2js/float-ops.2asm.js.opt
index a78785a..6209485 100644
--- a/test/wasm2js/float-ops.2asm.js.opt
+++ b/test/wasm2js/float-ops.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0, $1_1) {
@@ -315,8 +314,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var f32_add = retasmFunc.f32_add;
 export var f32_sub = retasmFunc.f32_sub;
 export var f32_mul = retasmFunc.f32_mul;
diff --git a/test/wasm2js/float_literals-modified.2asm.js b/test/wasm2js/float_literals-modified.2asm.js
index aaa086d..2792dee 100644
--- a/test/wasm2js/float_literals-modified.2asm.js
+++ b/test/wasm2js/float_literals-modified.2asm.js
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
 
   var scratchBuffer = new ArrayBuffer(16);
@@ -18,7 +18,7 @@ import { setTempRet0 } from 'env';
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -29,9 +29,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -1148,9 +1148,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var f32_nan = retasmFunc.f32_nan;
 export var f32_positive_nan = retasmFunc.f32_positive_nan;
 export var f32_negative_nan = retasmFunc.f32_negative_nan;
diff --git a/test/wasm2js/float_literals-modified.2asm.js.opt b/test/wasm2js/float_literals-modified.2asm.js.opt
index 9ae915a..613e52a 100644
--- a/test/wasm2js/float_literals-modified.2asm.js.opt
+++ b/test/wasm2js/float_literals-modified.2asm.js.opt
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
 
   var scratchBuffer = new ArrayBuffer(16);
@@ -14,7 +14,7 @@ import { setTempRet0 } from 'env';
     f64ScratchView[0] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -25,9 +25,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -108,9 +108,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$32() {
@@ -119,9 +118,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$34() {
@@ -130,9 +128,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$35() {
@@ -141,9 +138,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$36() {
@@ -152,9 +148,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$37() {
@@ -163,9 +158,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$38() {
@@ -174,9 +168,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$39() {
@@ -185,9 +178,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$41() {
@@ -196,9 +188,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$42() {
@@ -207,9 +198,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$44() {
@@ -218,9 +208,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$45() {
@@ -229,9 +218,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$46() {
@@ -240,9 +228,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$47() {
@@ -251,9 +238,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$48() {
@@ -262,9 +248,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$49() {
@@ -273,9 +258,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$50() {
@@ -284,9 +268,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  function legalstub$59() {
@@ -295,9 +278,8 @@ function asmFunc(env) {
   $0_1 = wasm2js_scratch_load_i32(1) | 0;
   $1 = wasm2js_scratch_load_i32(0) | 0;
   i64toi32_i32$HIGH_BITS = $0_1;
-  $0_1 = $1;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0_1;
+  return $1;
  }
  
  return {
@@ -364,9 +346,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var f32_nan = retasmFunc.f32_nan;
 export var f32_positive_nan = retasmFunc.f32_positive_nan;
 export var f32_negative_nan = retasmFunc.f32_negative_nan;
diff --git a/test/wasm2js/float_misc.2asm.js b/test/wasm2js/float_misc.2asm.js
index 4bdff02..e175aa6 100644
--- a/test/wasm2js/float_misc.2asm.js
+++ b/test/wasm2js/float_misc.2asm.js
@@ -29,7 +29,7 @@
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -40,7 +40,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x, y) {
@@ -227,14 +226,12 @@ function asmFunc(env) {
   var$1 = Math_fround(Math_floor(var$0));
   var$2 = Math_fround(var$0 - var$1);
   if (!(var$2 < Math_fround(.5))) {
-   block : {
-    var$0 = Math_fround(Math_ceil(var$0));
-    if (var$2 > Math_fround(.5)) {
-     return Math_fround(var$0)
-    }
-    var$2 = Math_fround(var$1 * Math_fround(.5));
-    var$1 = Math_fround(var$2 - Math_fround(Math_floor(var$2))) == Math_fround(0.0) ? var$1 : var$0;
+   var$0 = Math_fround(Math_ceil(var$0));
+   if (var$2 > Math_fround(.5)) {
+    return Math_fround(var$0)
    }
+   var$2 = Math_fround(var$1 * Math_fround(.5));
+   var$1 = Math_fround(var$2 - Math_fround(Math_floor(var$2))) == Math_fround(0.0) ? var$1 : var$0;
   }
   return Math_fround(var$1);
  }
@@ -245,14 +242,12 @@ function asmFunc(env) {
   var$1 = Math_floor(var$0);
   var$2 = var$0 - var$1;
   if (!(var$2 < .5)) {
-   block : {
-    var$0 = Math_ceil(var$0);
-    if (var$2 > .5) {
-     return +var$0
-    }
-    var$2 = var$1 * .5;
-    var$1 = var$2 - Math_floor(var$2) == 0.0 ? var$1 : var$0;
+   var$0 = Math_ceil(var$0);
+   if (var$2 > .5) {
+    return +var$0
    }
+   var$2 = var$1 * .5;
+   var$1 = var$2 - Math_floor(var$2) == 0.0 ? var$1 : var$0;
   }
   return +var$1;
  }
@@ -289,8 +284,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var f32_add = retasmFunc.f32_add;
 export var f32_sub = retasmFunc.f32_sub;
 export var f32_mul = retasmFunc.f32_mul;
diff --git a/test/wasm2js/forward.2asm.js b/test/wasm2js/forward.2asm.js
index 1dff26c..d20479b 100644
--- a/test/wasm2js/forward.2asm.js
+++ b/test/wasm2js/forward.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function even(n) {
@@ -41,7 +40,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var even = retasmFunc.even;
 export var odd = retasmFunc.odd;
diff --git a/test/wasm2js/func-ptr-offset.2asm.js b/test/wasm2js/func-ptr-offset.2asm.js
index 48156f9..a6e8289 100644
--- a/test/wasm2js/func-ptr-offset.2asm.js
+++ b/test/wasm2js/func-ptr-offset.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function t1() {
@@ -36,6 +35,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var call = retasmFunc.call;
diff --git a/test/wasm2js/func-ptr-offset.2asm.js.opt b/test/wasm2js/func-ptr-offset.2asm.js.opt
index 0666c1e..04b4c64 100644
--- a/test/wasm2js/func-ptr-offset.2asm.js.opt
+++ b/test/wasm2js/func-ptr-offset.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function t1() {
@@ -36,6 +35,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var call = retasmFunc.call;
diff --git a/test/wasm2js/func_ptrs.2asm.js b/test/wasm2js/func_ptrs.2asm.js
index eef4acd..3da7a50 100644
--- a/test/wasm2js/func_ptrs.2asm.js
+++ b/test/wasm2js/func_ptrs.2asm.js
@@ -1,6 +1,6 @@
-import { print_i32 } from 'spectest';
+import * as spectest from 'spectest';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,10 +11,10 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
- var print = env.print_i32;
+ var spectest = imports.spectest;
+ var print = spectest.print_i32;
  function $3() {
   return 13 | 0;
  }
@@ -42,15 +42,15 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    print_i32
-  });
+var retasmFunc = asmFunc({
+  "spectest": spectest,
+});
 export var one = retasmFunc.one;
 export var two = retasmFunc.two;
 export var three = retasmFunc.three;
 export var four = retasmFunc.four;
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -61,7 +61,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function t1() {
@@ -101,12 +100,12 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var callt = retasmFunc.callt;
 export var callu = retasmFunc.callu;
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -117,7 +116,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function t1() {
@@ -139,6 +137,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var callt = retasmFunc.callt;
diff --git a/test/wasm2js/get-set-local.2asm.js b/test/wasm2js/get-set-local.2asm.js
index cfd7e65..30557e5 100644
--- a/test/wasm2js/get-set-local.2asm.js
+++ b/test/wasm2js/get-set-local.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0, r, r$hi) {
@@ -61,6 +60,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var check_extend_ui32 = retasmFunc.check_extend_ui32;
diff --git a/test/wasm2js/get-set-local.2asm.js.opt b/test/wasm2js/get-set-local.2asm.js.opt
index 9a6cb07..e9dd660 100644
--- a/test/wasm2js/get-set-local.2asm.js.opt
+++ b/test/wasm2js/get-set-local.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function legalstub$1($0, $1, $2) {
@@ -22,6 +21,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var check_extend_ui32 = retasmFunc.check_extend_ui32;
diff --git a/test/wasm2js/get_local.2asm.js b/test/wasm2js/get_local.2asm.js
index bac4f75..8eed0e9 100644
--- a/test/wasm2js/get_local.2asm.js
+++ b/test/wasm2js/get_local.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -70,9 +70,6 @@ function asmFunc(env) {
   $3_1 = $3_1 | 0;
   $4_1 = $4_1 | 0;
   var i64toi32_i32$0 = 0, $5_1 = Math_fround(0), $6$hi = 0, $6_1 = 0, $7$hi = 0, $7_1 = 0, $8_1 = 0.0;
-  i64toi32_i32$0 = $0$hi;
-  i64toi32_i32$0 = $6$hi;
-  i64toi32_i32$0 = $7$hi;
  }
  
  function $9($0_1, $0$hi, $1_1, $2_1, $3_1, $4_1) {
@@ -241,9 +238,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var type_local_i32 = retasmFunc.type_local_i32;
 export var type_local_i64 = retasmFunc.type_local_i64;
 export var type_local_f32 = retasmFunc.type_local_f32;
diff --git a/test/wasm2js/global_i64.2asm.js b/test/wasm2js/global_i64.2asm.js
index 85c7a8c..71073de 100644
--- a/test/wasm2js/global_i64.2asm.js
+++ b/test/wasm2js/global_i64.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var f = -1412567121;
@@ -34,6 +33,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var exp = retasmFunc.exp;
diff --git a/test/wasm2js/global_i64.2asm.js.opt b/test/wasm2js/global_i64.2asm.js.opt
index fd42701..9399e09 100644
--- a/test/wasm2js/global_i64.2asm.js.opt
+++ b/test/wasm2js/global_i64.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1() {
@@ -22,6 +21,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var exp = retasmFunc.exp;
diff --git a/test/wasm2js/grow-memory-tricky.2asm.js b/test/wasm2js/grow-memory-tricky.2asm.js
index 44e7d81..7430500 100644
--- a/test/wasm2js/grow-memory-tricky.2asm.js
+++ b/test/wasm2js/grow-memory-tricky.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -19,7 +19,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -80,8 +79,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var memory = retasmFunc.memory;
 export var f1 = retasmFunc.f1;
 export var f2 = retasmFunc.f2;
diff --git a/test/wasm2js/grow-memory-tricky.2asm.js.opt b/test/wasm2js/grow-memory-tricky.2asm.js.opt
index 793feef..e1b090e 100644
--- a/test/wasm2js/grow-memory-tricky.2asm.js.opt
+++ b/test/wasm2js/grow-memory-tricky.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -19,7 +19,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -70,8 +69,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var memory = retasmFunc.memory;
 export var f1 = retasmFunc.f1;
 export var f2 = retasmFunc.f2;
diff --git a/test/wasm2js/grow_memory.2asm.js b/test/wasm2js/grow_memory.2asm.js
index 62de4bb..598b027 100644
--- a/test/wasm2js/grow_memory.2asm.js
+++ b/test/wasm2js/grow_memory.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -19,7 +19,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(var$0) {
@@ -73,8 +72,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var memory = retasmFunc.memory;
 export var grow = retasmFunc.grow;
 export var current = retasmFunc.current;
diff --git a/test/wasm2js/i32.2asm.js b/test/wasm2js/i32.2asm.js
index 2bc41be..6aa4ef0 100644
--- a/test/wasm2js/i32.2asm.js
+++ b/test/wasm2js/i32.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x, y) {
@@ -259,8 +258,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var add = retasmFunc.add;
 export var sub = retasmFunc.sub;
 export var mul = retasmFunc.mul;
diff --git a/test/wasm2js/i64-add-sub.2asm.js b/test/wasm2js/i64-add-sub.2asm.js
index 683c4f2..0351d89 100644
--- a/test/wasm2js/i64-add-sub.2asm.js
+++ b/test/wasm2js/i64-add-sub.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0, $0$hi, $1_1, $1$hi, r, r$hi) {
@@ -230,7 +229,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var check_add_i64 = retasmFunc.check_add_i64;
 export var check_sub_i64 = retasmFunc.check_sub_i64;
diff --git a/test/wasm2js/i64-add-sub.2asm.js.opt b/test/wasm2js/i64-add-sub.2asm.js.opt
index 62f4648..4d72485 100644
--- a/test/wasm2js/i64-add-sub.2asm.js.opt
+++ b/test/wasm2js/i64-add-sub.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function legalstub$1($0, $1, $2, $3, $4, $5) {
@@ -30,7 +29,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var check_add_i64 = retasmFunc.check_add_i64;
 export var check_sub_i64 = retasmFunc.check_sub_i64;
diff --git a/test/wasm2js/i64-ctz.2asm.js b/test/wasm2js/i64-ctz.2asm.js
index d799b51..9fb18e5 100644
--- a/test/wasm2js/i64-ctz.2asm.js
+++ b/test/wasm2js/i64-ctz.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function popcnt64($0, $0$hi) {
@@ -233,8 +233,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var a = retasmFunc.a;
 export var b = retasmFunc.b;
diff --git a/test/wasm2js/i64-ctz.2asm.js.opt b/test/wasm2js/i64-ctz.2asm.js.opt
index 5fc52a8..0d6ffd6 100644
--- a/test/wasm2js/i64-ctz.2asm.js.opt
+++ b/test/wasm2js/i64-ctz.2asm.js.opt
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function legalstub$popcnt64($0, $1) {
@@ -22,7 +22,7 @@ function asmFunc(env) {
   while (1) {
    if ($0 | $2) {
     $1 = $0;
-    $0 = $0 - 1 & $0;
+    $0 = $1 & $1 - 1;
     $2 = $2 - !$1 & $2;
     $3 = $3 + 1 | 0;
     $4 = $3 ? $4 : $4 + 1 | 0;
@@ -31,9 +31,8 @@ function asmFunc(env) {
    break;
   };
   i64toi32_i32$HIGH_BITS = $4;
-  $0 = $3;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0;
+  return $3;
  }
  
  function legalstub$ctz64($0, $1) {
@@ -65,8 +64,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var a = retasmFunc.a;
 export var b = retasmFunc.b;
diff --git a/test/wasm2js/i64-lowering.2asm.js b/test/wasm2js/i64-lowering.2asm.js
index deaecb1..75d4db3 100644
--- a/test/wasm2js/i64-lowering.2asm.js
+++ b/test/wasm2js/i64-lowering.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0, $0$hi, $1_1, $1$hi) {
@@ -789,8 +788,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var eq_i64 = retasmFunc.eq_i64;
 export var ne_i64 = retasmFunc.ne_i64;
 export var ge_s_i64 = retasmFunc.ge_s_i64;
diff --git a/test/wasm2js/i64-lowering.2asm.js.opt b/test/wasm2js/i64-lowering.2asm.js.opt
index 6d90214..52d1385 100644
--- a/test/wasm2js/i64-lowering.2asm.js.opt
+++ b/test/wasm2js/i64-lowering.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function legalstub$1($0, $1, $2, $3) {
@@ -67,8 +66,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var eq_i64 = retasmFunc.eq_i64;
 export var ne_i64 = retasmFunc.ne_i64;
 export var ge_s_i64 = retasmFunc.ge_s_i64;
diff --git a/test/wasm2js/i64-rotate.2asm.js b/test/wasm2js/i64-rotate.2asm.js
index 22d8a23..cd1a40d 100644
--- a/test/wasm2js/i64-rotate.2asm.js
+++ b/test/wasm2js/i64-rotate.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var i64toi32_i32$HIGH_BITS = 0;
@@ -442,7 +441,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var rotl = retasmFunc.rotl;
 export var rotr = retasmFunc.rotr;
diff --git a/test/wasm2js/i64-rotate.2asm.js.opt b/test/wasm2js/i64-rotate.2asm.js.opt
index 9aeec11..5cf2e36 100644
--- a/test/wasm2js/i64-rotate.2asm.js.opt
+++ b/test/wasm2js/i64-rotate.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var i64toi32_i32$HIGH_BITS = 0;
@@ -116,7 +115,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var rotl = retasmFunc.rotl;
 export var rotr = retasmFunc.rotr;
diff --git a/test/wasm2js/i64-select.2asm.js b/test/wasm2js/i64-select.2asm.js
index 3278e98..91dfa17 100644
--- a/test/wasm2js/i64-select.2asm.js
+++ b/test/wasm2js/i64-select.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/i64-select.2asm.js.opt b/test/wasm2js/i64-select.2asm.js.opt
index 3278e98..91dfa17 100644
--- a/test/wasm2js/i64-select.2asm.js.opt
+++ b/test/wasm2js/i64-select.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/i64-shifts.2asm.js b/test/wasm2js/i64-shifts.2asm.js
index 63de706..5feee59 100644
--- a/test/wasm2js/i64-shifts.2asm.js
+++ b/test/wasm2js/i64-shifts.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0, $0$hi, $1_1, $1$hi, $2_1, $2$hi) {
@@ -247,7 +246,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var shl_i64 = retasmFunc.shl_i64;
 export var shr_i64 = retasmFunc.shr_i64;
diff --git a/test/wasm2js/i64-shifts.2asm.js.opt b/test/wasm2js/i64-shifts.2asm.js.opt
index 4981ca8..797cbbc 100644
--- a/test/wasm2js/i64-shifts.2asm.js.opt
+++ b/test/wasm2js/i64-shifts.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function legalstub$1($0, $1, $2, $3, $4, $5) {
@@ -45,7 +44,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var shl_i64 = retasmFunc.shl_i64;
 export var shr_i64 = retasmFunc.shr_i64;
diff --git a/test/wasm2js/if_unreachable.2asm.js b/test/wasm2js/if_unreachable.2asm.js
index 3278e98..91dfa17 100644
--- a/test/wasm2js/if_unreachable.2asm.js
+++ b/test/wasm2js/if_unreachable.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/if_unreachable.2asm.js.opt b/test/wasm2js/if_unreachable.2asm.js.opt
index 3278e98..91dfa17 100644
--- a/test/wasm2js/if_unreachable.2asm.js.opt
+++ b/test/wasm2js/if_unreachable.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/indirect-select.2asm.js b/test/wasm2js/indirect-select.2asm.js
index 5ae5067..a5036ec 100644
--- a/test/wasm2js/indirect-select.2asm.js
+++ b/test/wasm2js/indirect-select.2asm.js
@@ -1,6 +1,7 @@
-import { table } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var FUNCTION_TABLE = env.table;
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
@@ -12,7 +13,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x) {
@@ -31,8 +31,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    table
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var foo_true = retasmFunc.foo_true;
 export var foo_false = retasmFunc.foo_false;
diff --git a/test/wasm2js/indirect-select.2asm.js.opt b/test/wasm2js/indirect-select.2asm.js.opt
index b4a4752..6bb594e 100644
--- a/test/wasm2js/indirect-select.2asm.js.opt
+++ b/test/wasm2js/indirect-select.2asm.js.opt
@@ -1,6 +1,7 @@
-import { table } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var FUNCTION_TABLE = env.table;
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
@@ -12,7 +13,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0($0_1) {
@@ -31,8 +31,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    table
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var foo_true = retasmFunc.foo_true;
 export var foo_false = retasmFunc.foo_false;
diff --git a/test/wasm2js/int_exprs.2asm.js b/test/wasm2js/int_exprs.2asm.js
index ad558ec..bd627c1 100644
--- a/test/wasm2js/int_exprs.2asm.js
+++ b/test/wasm2js/int_exprs.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x, y) {
@@ -230,15 +229,15 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var i32_no_fold_cmp_s_offset = retasmFunc.i32_no_fold_cmp_s_offset;
 export var i32_no_fold_cmp_u_offset = retasmFunc.i32_no_fold_cmp_u_offset;
 export var i64_no_fold_cmp_s_offset = retasmFunc.i64_no_fold_cmp_s_offset;
 export var i64_no_fold_cmp_u_offset = retasmFunc.i64_no_fold_cmp_u_offset;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -249,9 +248,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(x, x$hi) {
@@ -315,13 +314,13 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i64_no_fold_wrap_extend_s = retasmFunc.i64_no_fold_wrap_extend_s;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -332,9 +331,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(x, x$hi) {
@@ -397,13 +396,13 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i64_no_fold_wrap_extend_u = retasmFunc.i64_no_fold_wrap_extend_u;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -414,9 +413,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(x) {
@@ -591,16 +590,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_shl_shr_s = retasmFunc.i32_no_fold_shl_shr_s;
 export var i32_no_fold_shl_shr_u = retasmFunc.i32_no_fold_shl_shr_u;
 export var i64_no_fold_shl_shr_s = retasmFunc.i64_no_fold_shl_shr_s;
 export var i64_no_fold_shl_shr_u = retasmFunc.i64_no_fold_shl_shr_u;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -611,9 +610,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(x) {
@@ -788,16 +787,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_shr_s_shl = retasmFunc.i32_no_fold_shr_s_shl;
 export var i32_no_fold_shr_u_shl = retasmFunc.i32_no_fold_shr_u_shl;
 export var i64_no_fold_shr_s_shl = retasmFunc.i64_no_fold_shr_s_shl;
 export var i64_no_fold_shr_u_shl = retasmFunc.i64_no_fold_shr_u_shl;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -808,9 +807,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -1162,34 +1161,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -1371,134 +1368,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -1604,16 +1599,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_div_s_mul = retasmFunc.i32_no_fold_div_s_mul;
 export var i32_no_fold_div_u_mul = retasmFunc.i32_no_fold_div_u_mul;
 export var i64_no_fold_div_s_mul = retasmFunc.i64_no_fold_div_s_mul;
 export var i64_no_fold_div_u_mul = retasmFunc.i64_no_fold_div_u_mul;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -1624,9 +1619,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -1900,34 +1895,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
               }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -2109,134 +2102,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -2326,16 +2317,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_div_s_self = retasmFunc.i32_no_fold_div_s_self;
 export var i32_no_fold_div_u_self = retasmFunc.i32_no_fold_div_u_self;
 export var i64_no_fold_div_s_self = retasmFunc.i64_no_fold_div_s_self;
 export var i64_no_fold_div_u_self = retasmFunc.i64_no_fold_div_u_self;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -2346,9 +2337,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -2602,34 +2593,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
               }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -2811,134 +2800,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -3030,16 +3017,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_rem_s_self = retasmFunc.i32_no_fold_rem_s_self;
 export var i32_no_fold_rem_u_self = retasmFunc.i32_no_fold_rem_u_self;
 export var i64_no_fold_rem_s_self = retasmFunc.i64_no_fold_rem_s_self;
 export var i64_no_fold_rem_u_self = retasmFunc.i64_no_fold_rem_u_self;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -3050,9 +3037,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -3404,34 +3391,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -3613,134 +3598,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -3846,16 +3829,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_mul_div_s = retasmFunc.i32_no_fold_mul_div_s;
 export var i32_no_fold_mul_div_u = retasmFunc.i32_no_fold_mul_div_u;
 export var i64_no_fold_mul_div_s = retasmFunc.i64_no_fold_mul_div_s;
 export var i64_no_fold_mul_div_u = retasmFunc.i64_no_fold_mul_div_u;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -3866,9 +3849,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -4096,34 +4079,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
               }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -4305,134 +4286,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -4504,14 +4483,14 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_div_s_2 = retasmFunc.i32_no_fold_div_s_2;
 export var i64_no_fold_div_s_2 = retasmFunc.i64_no_fold_div_s_2;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -4522,9 +4501,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -4732,34 +4711,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -4941,134 +4918,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -5142,14 +5117,14 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_rem_s_2 = retasmFunc.i32_no_fold_rem_s_2;
 export var i64_no_fold_rem_s_2 = retasmFunc.i64_no_fold_rem_s_2;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -5160,9 +5135,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -5436,34 +5411,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
               }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -5645,134 +5618,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -5862,16 +5833,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_div_s_0 = retasmFunc.i32_div_s_0;
 export var i32_div_u_0 = retasmFunc.i32_div_u_0;
 export var i64_div_s_0 = retasmFunc.i64_div_s_0;
 export var i64_div_u_0 = retasmFunc.i64_div_u_0;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -5882,9 +5853,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -6158,34 +6129,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -6367,134 +6336,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -6584,16 +6551,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_div_s_3 = retasmFunc.i32_div_s_3;
 export var i32_div_u_3 = retasmFunc.i32_div_u_3;
 export var i64_div_s_3 = retasmFunc.i64_div_s_3;
 export var i64_div_u_3 = retasmFunc.i64_div_u_3;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -6604,9 +6571,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -6880,34 +6847,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
               }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -7089,134 +7054,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -7306,16 +7269,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_div_s_5 = retasmFunc.i32_div_s_5;
 export var i32_div_u_5 = retasmFunc.i32_div_u_5;
 export var i64_div_s_5 = retasmFunc.i64_div_s_5;
 export var i64_div_u_5 = retasmFunc.i64_div_u_5;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -7326,9 +7289,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -7602,34 +7565,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
               }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -7811,134 +7772,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -8028,16 +7987,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_div_s_7 = retasmFunc.i32_div_s_7;
 export var i32_div_u_7 = retasmFunc.i32_div_u_7;
 export var i64_div_s_7 = retasmFunc.i64_div_s_7;
 export var i64_div_u_7 = retasmFunc.i64_div_u_7;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -8048,9 +8007,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -8304,34 +8263,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -8513,134 +8470,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -8732,16 +8687,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_rem_s_3 = retasmFunc.i32_rem_s_3;
 export var i32_rem_u_3 = retasmFunc.i32_rem_u_3;
 export var i64_rem_s_3 = retasmFunc.i64_rem_s_3;
 export var i64_rem_u_3 = retasmFunc.i64_rem_u_3;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -8752,9 +8707,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -9008,34 +8963,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
               }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -9217,134 +9170,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -9436,16 +9387,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_rem_s_5 = retasmFunc.i32_rem_s_5;
 export var i32_rem_u_5 = retasmFunc.i32_rem_u_5;
 export var i64_rem_s_5 = retasmFunc.i64_rem_s_5;
 export var i64_rem_u_5 = retasmFunc.i64_rem_u_5;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -9456,9 +9407,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -9712,34 +9663,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -9921,134 +9870,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -10140,16 +10087,16 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_rem_s_7 = retasmFunc.i32_rem_s_7;
 export var i32_rem_u_7 = retasmFunc.i32_rem_u_7;
 export var i64_rem_s_7 = retasmFunc.i64_rem_s_7;
 export var i64_rem_u_7 = retasmFunc.i64_rem_u_7;
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10160,9 +10107,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var __wasm_intrinsics_temp_i64 = 0;
  var __wasm_intrinsics_temp_i64$hi = 0;
@@ -10390,34 +10337,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
               }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
+              }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -10599,134 +10544,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -10798,8 +10741,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_no_fold_div_neg1 = retasmFunc.i32_no_fold_div_neg1;
 export var i64_no_fold_div_neg1 = retasmFunc.i64_no_fold_div_neg1;
diff --git a/test/wasm2js/labels.2asm.js b/test/wasm2js/labels.2asm.js
index c12eaf7..f63a2f2 100644
--- a/test/wasm2js/labels.2asm.js
+++ b/test/wasm2js/labels.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -116,56 +115,52 @@ function asmFunc(env) {
  function $7() {
   var i = 0;
   i = 0;
-  block : {
-   l : {
-    break l;
-   }
-   i = i + 1 | 0;
-   l1 : {
-    break l1;
-   }
-   i = i + 1 | 0;
-   l2 : {
-    break l2;
-   }
-   i = i + 1 | 0;
-   l3 : {
-    break l3;
-   }
-   i = i + 1 | 0;
-   l4 : {
-    break l4;
-   }
-   i = i + 1 | 0;
+  l : {
+   break l;
+  }
+  i = i + 1 | 0;
+  l1 : {
+   break l1;
+  }
+  i = i + 1 | 0;
+  l2 : {
+   break l2;
   }
+  i = i + 1 | 0;
+  l3 : {
+   break l3;
+  }
+  i = i + 1 | 0;
+  l4 : {
+   break l4;
+  }
+  i = i + 1 | 0;
   return i | 0;
  }
  
  function $8() {
   var i = 0;
   i = 0;
-  block : {
-   if_ : {
-    break if_;
-   }
-   i = i + 1 | 0;
-   if5 : {
-    break if5;
-   }
-   i = i + 1 | 0;
-   if6 : {
-    break if6;
-   }
-   i = i + 1 | 0;
-   if7 : {
-    break if7;
-   }
-   i = i + 1 | 0;
-   if8 : {
-    break if8;
-   }
-   i = i + 1 | 0;
+  if_ : {
+   break if_;
+  }
+  i = i + 1 | 0;
+  if5 : {
+   break if5;
+  }
+  i = i + 1 | 0;
+  if6 : {
+   break if6;
+  }
+  i = i + 1 | 0;
+  if7 : {
+   break if7;
+  }
+  i = i + 1 | 0;
+  if8 : {
+   break if8;
   }
+  i = i + 1 | 0;
   return i | 0;
  }
  
@@ -342,8 +337,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var block = retasmFunc.block;
 export var loop1 = retasmFunc.loop1;
 export var loop2 = retasmFunc.loop2;
diff --git a/test/wasm2js/left-to-right.2asm.js b/test/wasm2js/left-to-right.2asm.js
index 8027956..fc091fd 100644
--- a/test/wasm2js/left-to-right.2asm.js
+++ b/test/wasm2js/left-to-right.2asm.js
@@ -26,7 +26,7 @@
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -46,7 +46,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var __wasm_intrinsics_temp_i64 = 0;
@@ -1572,34 +1571,32 @@ function asmFunc(env) {
              }
              var$2 = $37_1;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38_1 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38_1 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38_1;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38_1 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38_1 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38_1;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -1781,134 +1778,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45_1 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45_1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45_1 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45_1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45_1;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46_1 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46_1 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46_1;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47_1 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47_1 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47_1;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45_1;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48_1 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48_1 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48_1;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46_1 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46_1 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46_1;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47_1 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47_1 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47_1;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48_1 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48_1 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48_1;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -2167,8 +2162,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var i32_add = retasmFunc.i32_add;
 export var i32_sub = retasmFunc.i32_sub;
 export var i32_mul = retasmFunc.i32_mul;
diff --git a/test/wasm2js/minified-memory.2asm.js b/test/wasm2js/minified-memory.2asm.js
index 657ef01..bdbeb52 100644
--- a/test/wasm2js/minified-memory.2asm.js
+++ b/test/wasm2js/minified-memory.2asm.js
@@ -1,5 +1,6 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.a;
  var buffer = memory.buffer;
  memory.grow = __wasm_memory_grow;
@@ -21,7 +22,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -60,7 +60,9 @@ function asmFunc(env) {
 }
 
 var memasmFunc = new ArrayBuffer(65536);
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
+var retasmFunc = asmFunc({
+  "env": {
     a: { buffer : memasmFunc }
-  });
+  },
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/minified-memory.2asm.js.opt b/test/wasm2js/minified-memory.2asm.js.opt
index 6a3e3a5..12d8521 100644
--- a/test/wasm2js/minified-memory.2asm.js.opt
+++ b/test/wasm2js/minified-memory.2asm.js.opt
@@ -1,5 +1,6 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var memory = env.a;
  var buffer = memory.buffer;
  memory.grow = __wasm_memory_grow;
@@ -21,7 +22,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -60,7 +60,9 @@ function asmFunc(env) {
 }
 
 var memasmFunc = new ArrayBuffer(65536);
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
+var retasmFunc = asmFunc({
+  "env": {
     a: { buffer : memasmFunc }
-  });
+  },
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/minus_minus.2asm.js b/test/wasm2js/minus_minus.2asm.js
index 569cbbc..5420df7 100644
--- a/test/wasm2js/minus_minus.2asm.js
+++ b/test/wasm2js/minus_minus.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -26,6 +25,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var func_44_invoker = retasmFunc.func_44_invoker;
diff --git a/test/wasm2js/minus_minus.2asm.js.opt b/test/wasm2js/minus_minus.2asm.js.opt
index 9eea558..823345f 100644
--- a/test/wasm2js/minus_minus.2asm.js.opt
+++ b/test/wasm2js/minus_minus.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1() {
@@ -22,6 +21,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var func_44_invoker = retasmFunc.func_44_invoker;
diff --git a/test/wasm2js/nested-selects.2asm.js b/test/wasm2js/nested-selects.2asm.js
index 458eb74..c6c48fa 100644
--- a/test/wasm2js/nested-selects.2asm.js
+++ b/test/wasm2js/nested-selects.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0) {
@@ -23,6 +22,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var sign = retasmFunc.sign;
diff --git a/test/wasm2js/nested-selects.2asm.js.opt b/test/wasm2js/nested-selects.2asm.js.opt
index 1bdeb97..4fb7e5e 100644
--- a/test/wasm2js/nested-selects.2asm.js.opt
+++ b/test/wasm2js/nested-selects.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0) {
@@ -23,6 +22,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var sign = retasmFunc.sign;
diff --git a/test/wasm2js/ordering.2asm.js b/test/wasm2js/ordering.2asm.js
index 00a003e..c2d4d8a 100644
--- a/test/wasm2js/ordering.2asm.js
+++ b/test/wasm2js/ordering.2asm.js
@@ -1,6 +1,7 @@
-import { table } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var FUNCTION_TABLE = env.table;
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
@@ -12,7 +13,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function main() {
@@ -54,7 +54,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    table
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var main = retasmFunc.main;
diff --git a/test/wasm2js/ordering.2asm.js.opt b/test/wasm2js/ordering.2asm.js.opt
index c36f622..3e45117 100644
--- a/test/wasm2js/ordering.2asm.js.opt
+++ b/test/wasm2js/ordering.2asm.js.opt
@@ -1,6 +1,7 @@
-import { table } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
+ var env = imports.env;
  var FUNCTION_TABLE = env.table;
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
@@ -12,7 +13,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function main() {
@@ -45,7 +45,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    table
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var main = retasmFunc.main;
diff --git a/test/wasm2js/reinterpret.2asm.js b/test/wasm2js/reinterpret.2asm.js
index 446f234..ada2041 100644
--- a/test/wasm2js/reinterpret.2asm.js
+++ b/test/wasm2js/reinterpret.2asm.js
@@ -29,7 +29,7 @@
     return f32ScratchView[2];
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -40,7 +40,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0) {
@@ -97,7 +96,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var i32_roundtrip = retasmFunc.i32_roundtrip;
 export var i64_roundtrip = retasmFunc.i64_roundtrip;
diff --git a/test/wasm2js/reinterpret.2asm.js.opt b/test/wasm2js/reinterpret.2asm.js.opt
index f8d2710..b14ea3c 100644
--- a/test/wasm2js/reinterpret.2asm.js.opt
+++ b/test/wasm2js/reinterpret.2asm.js.opt
@@ -21,7 +21,7 @@
     f64ScratchView[0] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -32,7 +32,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $1($0) {
@@ -55,7 +54,7 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var i32_roundtrip = retasmFunc.i32_roundtrip;
 export var i64_roundtrip = retasmFunc.i64_roundtrip;
diff --git a/test/wasm2js/reinterpret_scratch.2asm.js b/test/wasm2js/reinterpret_scratch.2asm.js
index b2c2a74..d5f0987 100644
--- a/test/wasm2js/reinterpret_scratch.2asm.js
+++ b/test/wasm2js/reinterpret_scratch.2asm.js
@@ -18,7 +18,7 @@
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -38,7 +38,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -61,6 +60,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/reinterpret_scratch.2asm.js.opt b/test/wasm2js/reinterpret_scratch.2asm.js.opt
index cf0bb9a..8f48bc9 100644
--- a/test/wasm2js/reinterpret_scratch.2asm.js.opt
+++ b/test/wasm2js/reinterpret_scratch.2asm.js.opt
@@ -14,7 +14,7 @@
     f64ScratchView[0] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -34,7 +34,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0() {
@@ -56,6 +55,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/set_local.2asm.js b/test/wasm2js/set_local.2asm.js
index 01d504a..1ebf653 100644
--- a/test/wasm2js/set_local.2asm.js
+++ b/test/wasm2js/set_local.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -57,9 +57,6 @@ function asmFunc(env) {
   $3_1 = $3_1 | 0;
   $4_1 = $4_1 | 0;
   var i64toi32_i32$0 = 0;
-  i64toi32_i32$0 = 0;
-  i64toi32_i32$0 = 0;
-  i64toi32_i32$0 = 0;
  }
  
  function $9($0_1, $0$hi, $1_1, $2_1, $3_1, $4_1) {
@@ -220,9 +217,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var type_local_i32 = retasmFunc.type_local_i32;
 export var type_local_i64 = retasmFunc.type_local_i64;
 export var type_local_f32 = retasmFunc.type_local_f32;
diff --git a/test/wasm2js/sign_ext.2asm.js b/test/wasm2js/sign_ext.2asm.js
index c6c1ce7..ec3ae37 100644
--- a/test/wasm2js/sign_ext.2asm.js
+++ b/test/wasm2js/sign_ext.2asm.js
@@ -1,5 +1,6 @@
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,9 +11,11 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
+ var setTempRet0 = env.setTempRet0;
+ var i64toi32_i32$HIGH_BITS = 0;
  function $0(x) {
   x = x | 0;
   return x << 24 >> 24 | 0;
@@ -23,13 +26,185 @@ function asmFunc(env) {
   return x << 16 >> 16 | 0;
  }
  
+ function $2(x, x$hi) {
+  x = x | 0;
+  x$hi = x$hi | 0;
+  var i64toi32_i32$2 = 0, i64toi32_i32$1 = 0;
+  i64toi32_i32$2 = x << 24 >> 24;
+  i64toi32_i32$1 = i64toi32_i32$2 >> 31 | 0;
+  i64toi32_i32$HIGH_BITS = i64toi32_i32$1;
+  return i64toi32_i32$2 | 0;
+ }
+ 
+ function $3(x, x$hi) {
+  x = x | 0;
+  x$hi = x$hi | 0;
+  var i64toi32_i32$2 = 0, i64toi32_i32$1 = 0;
+  i64toi32_i32$2 = x << 16 >> 16;
+  i64toi32_i32$1 = i64toi32_i32$2 >> 31 | 0;
+  i64toi32_i32$HIGH_BITS = i64toi32_i32$1;
+  return i64toi32_i32$2 | 0;
+ }
+ 
+ function $4(x, x$hi) {
+  x = x | 0;
+  x$hi = x$hi | 0;
+  var i64toi32_i32$2 = 0, i64toi32_i32$1 = 0;
+  i64toi32_i32$2 = x;
+  i64toi32_i32$1 = i64toi32_i32$2 >> 31 | 0;
+  i64toi32_i32$HIGH_BITS = i64toi32_i32$1;
+  return i64toi32_i32$2 | 0;
+ }
+ 
+ function legalstub$2($0_1, $1_1) {
+  $0_1 = $0_1 | 0;
+  $1_1 = $1_1 | 0;
+  var i64toi32_i32$2 = 0, i64toi32_i32$4 = 0, i64toi32_i32$0 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $12 = 0, $13 = 0, $4_1 = 0, $4$hi = 0, $7$hi = 0, $2_1 = 0, $2$hi = 0;
+  i64toi32_i32$0 = 0;
+  $4_1 = $0_1;
+  $4$hi = i64toi32_i32$0;
+  i64toi32_i32$0 = 0;
+  i64toi32_i32$2 = $1_1;
+  i64toi32_i32$1 = 0;
+  i64toi32_i32$3 = 32;
+  i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+  if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
+   i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$4 | 0;
+   $12 = 0;
+  } else {
+   i64toi32_i32$1 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$0 << i64toi32_i32$4 | 0) | 0;
+   $12 = i64toi32_i32$2 << i64toi32_i32$4 | 0;
+  }
+  $7$hi = i64toi32_i32$1;
+  i64toi32_i32$1 = $4$hi;
+  i64toi32_i32$0 = $4_1;
+  i64toi32_i32$2 = $7$hi;
+  i64toi32_i32$3 = $12;
+  i64toi32_i32$2 = i64toi32_i32$1 | i64toi32_i32$2 | 0;
+  i64toi32_i32$2 = $2(i64toi32_i32$0 | i64toi32_i32$3 | 0 | 0, i64toi32_i32$2 | 0) | 0;
+  i64toi32_i32$0 = i64toi32_i32$HIGH_BITS;
+  $2_1 = i64toi32_i32$2;
+  $2$hi = i64toi32_i32$0;
+  i64toi32_i32$1 = i64toi32_i32$2;
+  i64toi32_i32$2 = 0;
+  i64toi32_i32$3 = 32;
+  i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+  if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
+   i64toi32_i32$2 = 0;
+   $13 = i64toi32_i32$0 >>> i64toi32_i32$4 | 0;
+  } else {
+   i64toi32_i32$2 = i64toi32_i32$0 >>> i64toi32_i32$4 | 0;
+   $13 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$0 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$1 >>> i64toi32_i32$4 | 0) | 0;
+  }
+  setTempRet0($13 | 0);
+  i64toi32_i32$2 = $2$hi;
+  return $2_1 | 0;
+ }
+ 
+ function legalstub$3($0_1, $1_1) {
+  $0_1 = $0_1 | 0;
+  $1_1 = $1_1 | 0;
+  var i64toi32_i32$2 = 0, i64toi32_i32$4 = 0, i64toi32_i32$0 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $12 = 0, $13 = 0, $4_1 = 0, $4$hi = 0, $7$hi = 0, $2_1 = 0, $2$hi = 0;
+  i64toi32_i32$0 = 0;
+  $4_1 = $0_1;
+  $4$hi = i64toi32_i32$0;
+  i64toi32_i32$0 = 0;
+  i64toi32_i32$2 = $1_1;
+  i64toi32_i32$1 = 0;
+  i64toi32_i32$3 = 32;
+  i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+  if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
+   i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$4 | 0;
+   $12 = 0;
+  } else {
+   i64toi32_i32$1 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$0 << i64toi32_i32$4 | 0) | 0;
+   $12 = i64toi32_i32$2 << i64toi32_i32$4 | 0;
+  }
+  $7$hi = i64toi32_i32$1;
+  i64toi32_i32$1 = $4$hi;
+  i64toi32_i32$0 = $4_1;
+  i64toi32_i32$2 = $7$hi;
+  i64toi32_i32$3 = $12;
+  i64toi32_i32$2 = i64toi32_i32$1 | i64toi32_i32$2 | 0;
+  i64toi32_i32$2 = $3(i64toi32_i32$0 | i64toi32_i32$3 | 0 | 0, i64toi32_i32$2 | 0) | 0;
+  i64toi32_i32$0 = i64toi32_i32$HIGH_BITS;
+  $2_1 = i64toi32_i32$2;
+  $2$hi = i64toi32_i32$0;
+  i64toi32_i32$1 = i64toi32_i32$2;
+  i64toi32_i32$2 = 0;
+  i64toi32_i32$3 = 32;
+  i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+  if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
+   i64toi32_i32$2 = 0;
+   $13 = i64toi32_i32$0 >>> i64toi32_i32$4 | 0;
+  } else {
+   i64toi32_i32$2 = i64toi32_i32$0 >>> i64toi32_i32$4 | 0;
+   $13 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$0 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$1 >>> i64toi32_i32$4 | 0) | 0;
+  }
+  setTempRet0($13 | 0);
+  i64toi32_i32$2 = $2$hi;
+  return $2_1 | 0;
+ }
+ 
+ function legalstub$4($0_1, $1_1) {
+  $0_1 = $0_1 | 0;
+  $1_1 = $1_1 | 0;
+  var i64toi32_i32$2 = 0, i64toi32_i32$4 = 0, i64toi32_i32$0 = 0, i64toi32_i32$1 = 0, i64toi32_i32$3 = 0, $12 = 0, $13 = 0, $4_1 = 0, $4$hi = 0, $7$hi = 0, $2_1 = 0, $2$hi = 0;
+  i64toi32_i32$0 = 0;
+  $4_1 = $0_1;
+  $4$hi = i64toi32_i32$0;
+  i64toi32_i32$0 = 0;
+  i64toi32_i32$2 = $1_1;
+  i64toi32_i32$1 = 0;
+  i64toi32_i32$3 = 32;
+  i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+  if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
+   i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$4 | 0;
+   $12 = 0;
+  } else {
+   i64toi32_i32$1 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$0 << i64toi32_i32$4 | 0) | 0;
+   $12 = i64toi32_i32$2 << i64toi32_i32$4 | 0;
+  }
+  $7$hi = i64toi32_i32$1;
+  i64toi32_i32$1 = $4$hi;
+  i64toi32_i32$0 = $4_1;
+  i64toi32_i32$2 = $7$hi;
+  i64toi32_i32$3 = $12;
+  i64toi32_i32$2 = i64toi32_i32$1 | i64toi32_i32$2 | 0;
+  i64toi32_i32$2 = $4(i64toi32_i32$0 | i64toi32_i32$3 | 0 | 0, i64toi32_i32$2 | 0) | 0;
+  i64toi32_i32$0 = i64toi32_i32$HIGH_BITS;
+  $2_1 = i64toi32_i32$2;
+  $2$hi = i64toi32_i32$0;
+  i64toi32_i32$1 = i64toi32_i32$2;
+  i64toi32_i32$2 = 0;
+  i64toi32_i32$3 = 32;
+  i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+  if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
+   i64toi32_i32$2 = 0;
+   $13 = i64toi32_i32$0 >>> i64toi32_i32$4 | 0;
+  } else {
+   i64toi32_i32$2 = i64toi32_i32$0 >>> i64toi32_i32$4 | 0;
+   $13 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$0 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$1 >>> i64toi32_i32$4 | 0) | 0;
+  }
+  setTempRet0($13 | 0);
+  i64toi32_i32$2 = $2$hi;
+  return $2_1 | 0;
+ }
+ 
  return {
   "test8": $0, 
-  "test16": $1
+  "test16": $1, 
+  "test8_i64": legalstub$2, 
+  "test16_i64": legalstub$3, 
+  "test32_i64": legalstub$4
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var test8 = retasmFunc.test8;
 export var test16 = retasmFunc.test16;
+export var test8_i64 = retasmFunc.test8_i64;
+export var test16_i64 = retasmFunc.test16_i64;
+export var test32_i64 = retasmFunc.test32_i64;
diff --git a/test/wasm2js/sign_ext.2asm.js.opt b/test/wasm2js/sign_ext.2asm.js.opt
index 31dbdc9..64f9f07 100644
--- a/test/wasm2js/sign_ext.2asm.js.opt
+++ b/test/wasm2js/sign_ext.2asm.js.opt
@@ -1,5 +1,6 @@
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,9 +11,11 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
+ var setTempRet0 = env.setTempRet0;
+ var i64toi32_i32$HIGH_BITS = 0;
  function $0($0_1) {
   $0_1 = $0_1 | 0;
   return $0_1 << 24 >> 24;
@@ -23,13 +26,40 @@ function asmFunc(env) {
   return $0_1 << 16 >> 16;
  }
  
+ function legalstub$2($0_1, $1_1) {
+  $0_1 = $0_1 << 24 >> 24;
+  i64toi32_i32$HIGH_BITS = $0_1 >> 31;
+  setTempRet0(i64toi32_i32$HIGH_BITS | 0);
+  return $0_1;
+ }
+ 
+ function legalstub$3($0_1, $1_1) {
+  $0_1 = $0_1 << 16 >> 16;
+  i64toi32_i32$HIGH_BITS = $0_1 >> 31;
+  setTempRet0(i64toi32_i32$HIGH_BITS | 0);
+  return $0_1;
+ }
+ 
+ function legalstub$4($0_1, $1_1) {
+  i64toi32_i32$HIGH_BITS = $0_1 >> 31;
+  setTempRet0(i64toi32_i32$HIGH_BITS | 0);
+  return $0_1;
+ }
+ 
  return {
   "test8": $0, 
-  "test16": $1
+  "test16": $1, 
+  "test8_i64": legalstub$2, 
+  "test16_i64": legalstub$3, 
+  "test32_i64": legalstub$4
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var test8 = retasmFunc.test8;
 export var test16 = retasmFunc.test16;
+export var test8_i64 = retasmFunc.test8_i64;
+export var test16_i64 = retasmFunc.test16_i64;
+export var test32_i64 = retasmFunc.test32_i64;
diff --git a/test/wasm2js/sign_ext.wast b/test/wasm2js/sign_ext.wast
index 7dcb039..825502a 100644
--- a/test/wasm2js/sign_ext.wast
+++ b/test/wasm2js/sign_ext.wast
@@ -5,4 +5,13 @@
   (func "test16" (param $x i32) (result i32)
     (i32.extend16_s (local.get $x))
   )
+  (func "test8_i64" (param $x i64) (result i64)
+    (i64.extend8_s (local.get $x))
+  )
+  (func "test16_i64" (param $x i64) (result i64)
+    (i64.extend16_s (local.get $x))
+  )
+  (func "test32_i64" (param $x i64) (result i64)
+    (i64.extend32_s (local.get $x))
+  )
 )
diff --git a/test/wasm2js/stack-modified.2asm.js b/test/wasm2js/stack-modified.2asm.js
index 3b5ed59..91bfded 100644
--- a/test/wasm2js/stack-modified.2asm.js
+++ b/test/wasm2js/stack-modified.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(var$0, var$0$hi) {
@@ -82,24 +82,22 @@ function asmFunc(env) {
     if ((i64toi32_i32$2 | 0) == (i64toi32_i32$3 | 0) & (i64toi32_i32$0 | 0) == (i64toi32_i32$1 | 0) | 0) {
      break label$1
     } else {
-     block : {
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$2 = var$2$hi;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$0 = var$2$hi;
-      i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
-      i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
-      var$2 = i64toi32_i32$0;
-      var$2$hi = i64toi32_i32$2;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$3 = var$1;
-      i64toi32_i32$0 = 0;
-      i64toi32_i32$1 = 1;
-      i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
-      i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
-      var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
-      var$1$hi = i64toi32_i32$5;
-     }
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$2 = var$2$hi;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$0 = var$2$hi;
+     i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
+     i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
+     var$2 = i64toi32_i32$0;
+     var$2$hi = i64toi32_i32$2;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$3 = var$1;
+     i64toi32_i32$0 = 0;
+     i64toi32_i32$1 = 1;
+     i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
+     i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
+     var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
+     var$1$hi = i64toi32_i32$5;
     }
     continue label$2;
    };
@@ -129,24 +127,22 @@ function asmFunc(env) {
     if ((i64toi32_i32$2 | 0) == (i64toi32_i32$3 | 0) & (i64toi32_i32$0 | 0) == (i64toi32_i32$1 | 0) | 0) {
      break label$1
     } else {
-     block : {
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$2 = var$2$hi;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$0 = var$2$hi;
-      i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
-      i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
-      var$2 = i64toi32_i32$0;
-      var$2$hi = i64toi32_i32$2;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$3 = var$1;
-      i64toi32_i32$0 = 0;
-      i64toi32_i32$1 = 1;
-      i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
-      i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
-      var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
-      var$1$hi = i64toi32_i32$5;
-     }
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$2 = var$2$hi;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$0 = var$2$hi;
+     i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
+     i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
+     var$2 = i64toi32_i32$0;
+     var$2$hi = i64toi32_i32$2;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$3 = var$1;
+     i64toi32_i32$0 = 0;
+     i64toi32_i32$1 = 1;
+     i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
+     i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
+     var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
+     var$1$hi = i64toi32_i32$5;
     }
     continue label$2;
    };
@@ -176,24 +172,22 @@ function asmFunc(env) {
     if ((i64toi32_i32$2 | 0) == (i64toi32_i32$3 | 0) & (i64toi32_i32$0 | 0) == (i64toi32_i32$1 | 0) | 0) {
      break label$1
     } else {
-     block : {
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$2 = var$2$hi;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$0 = var$2$hi;
-      i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
-      i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
-      var$2 = i64toi32_i32$0;
-      var$2$hi = i64toi32_i32$2;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$3 = var$1;
-      i64toi32_i32$0 = 0;
-      i64toi32_i32$1 = 1;
-      i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
-      i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
-      var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
-      var$1$hi = i64toi32_i32$5;
-     }
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$2 = var$2$hi;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$0 = var$2$hi;
+     i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
+     i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
+     var$2 = i64toi32_i32$0;
+     var$2$hi = i64toi32_i32$2;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$3 = var$1;
+     i64toi32_i32$0 = 0;
+     i64toi32_i32$1 = 1;
+     i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
+     i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
+     var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
+     var$1$hi = i64toi32_i32$5;
     }
     continue label$2;
    };
@@ -223,24 +217,22 @@ function asmFunc(env) {
     if ((i64toi32_i32$2 | 0) == (i64toi32_i32$3 | 0) & (i64toi32_i32$0 | 0) == (i64toi32_i32$1 | 0) | 0) {
      break label$1
     } else {
-     block : {
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$2 = var$2$hi;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$0 = var$2$hi;
-      i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
-      i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
-      var$2 = i64toi32_i32$0;
-      var$2$hi = i64toi32_i32$2;
-      i64toi32_i32$2 = var$1$hi;
-      i64toi32_i32$3 = var$1;
-      i64toi32_i32$0 = 0;
-      i64toi32_i32$1 = 1;
-      i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
-      i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
-      var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
-      var$1$hi = i64toi32_i32$5;
-     }
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$2 = var$2$hi;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$0 = var$2$hi;
+     i64toi32_i32$0 = __wasm_i64_mul(var$1 | 0, i64toi32_i32$2 | 0, var$2 | 0, i64toi32_i32$0 | 0) | 0;
+     i64toi32_i32$2 = i64toi32_i32$HIGH_BITS;
+     var$2 = i64toi32_i32$0;
+     var$2$hi = i64toi32_i32$2;
+     i64toi32_i32$2 = var$1$hi;
+     i64toi32_i32$3 = var$1;
+     i64toi32_i32$0 = 0;
+     i64toi32_i32$1 = 1;
+     i64toi32_i32$5 = (i64toi32_i32$3 >>> 0 < i64toi32_i32$1 >>> 0) + i64toi32_i32$0 | 0;
+     i64toi32_i32$5 = i64toi32_i32$2 - i64toi32_i32$5 | 0;
+     var$1 = i64toi32_i32$3 - i64toi32_i32$1 | 0;
+     var$1$hi = i64toi32_i32$5;
     }
     continue label$2;
    };
@@ -571,9 +563,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var fac_expr = retasmFunc.fac_expr;
 export var fac_stack = retasmFunc.fac_stack;
 export var fac_stack_raw = retasmFunc.fac_stack_raw;
diff --git a/test/wasm2js/stack-modified.2asm.js.opt b/test/wasm2js/stack-modified.2asm.js.opt
index 63d2e48..45203a5 100644
--- a/test/wasm2js/stack-modified.2asm.js.opt
+++ b/test/wasm2js/stack-modified.2asm.js.opt
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,39 +11,41 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function legalstub$0($0, $1) {
-  var $2 = 0, $3 = 0, $4 = 0, $5 = 0, $6 = 0, $7 = 0, $8 = 0, $9 = 0;
-  $4 = $1;
+  var $2 = 0, $3 = 0;
   $2 = 1;
   while (1) {
-   if ($0 | $4) {
-    $1 = $2 >>> 16 | 0;
-    $3 = $0 >>> 16 | 0;
-    $9 = Math_imul($1, $3);
-    $5 = $2 & 65535;
-    $6 = $0 & 65535;
-    $7 = Math_imul($5, $6);
-    $3 = ($7 >>> 16 | 0) + Math_imul($3, $5) | 0;
-    $1 = ($3 & 65535) + Math_imul($1, $6) | 0;
-    i64toi32_i32$HIGH_BITS = (Math_imul($2, $4) + $9 | 0) + Math_imul($0, $8) + ($3 >>> 16) + ($1 >>> 16) | 0;
-    $2 = $7 & 65535 | $1 << 16;
-    $8 = i64toi32_i32$HIGH_BITS;
-    $1 = $0;
-    $0 = $1 - 1 | 0;
-    $4 = $4 - !$1 | 0;
+   if ($0 | $1) {
+    $2 = __wasm_i64_mul($0, $1, $2, $3);
+    $3 = i64toi32_i32$HIGH_BITS;
+    $1 = $1 - !$0 | 0;
+    $0 = $0 - 1 | 0;
     continue;
    }
    break;
   };
-  i64toi32_i32$HIGH_BITS = $8;
-  $0 = $2;
+  i64toi32_i32$HIGH_BITS = $3;
   setTempRet0(i64toi32_i32$HIGH_BITS | 0);
-  return $0;
+  return $2;
+ }
+ 
+ function __wasm_i64_mul($0, $1, $2, $3) {
+  var $4 = 0, $5 = 0, $6 = 0, $7 = 0, $8 = 0, $9 = 0;
+  $4 = $2 >>> 16 | 0;
+  $5 = $0 >>> 16 | 0;
+  $9 = Math_imul($4, $5);
+  $6 = $2 & 65535;
+  $7 = $0 & 65535;
+  $8 = Math_imul($6, $7);
+  $5 = ($8 >>> 16 | 0) + Math_imul($5, $6) | 0;
+  $4 = ($5 & 65535) + Math_imul($4, $7) | 0;
+  i64toi32_i32$HIGH_BITS = (Math_imul($1, $2) + $9 | 0) + Math_imul($0, $3) + ($5 >>> 16) + ($4 >>> 16) | 0;
+  return $8 & 65535 | $4 << 16;
  }
  
  return {
@@ -55,9 +57,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var fac_expr = retasmFunc.fac_expr;
 export var fac_stack = retasmFunc.fac_stack;
 export var fac_stack_raw = retasmFunc.fac_stack_raw;
diff --git a/test/wasm2js/start_func.2asm.js b/test/wasm2js/start_func.2asm.js
index f369470..629f58a 100644
--- a/test/wasm2js/start_func.2asm.js
+++ b/test/wasm2js/start_func.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -19,7 +19,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo() {
@@ -57,5 +56,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/start_func.2asm.js.opt b/test/wasm2js/start_func.2asm.js.opt
index 17e43c8..7b692ac 100644
--- a/test/wasm2js/start_func.2asm.js.opt
+++ b/test/wasm2js/start_func.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -19,7 +19,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function foo() {
@@ -57,5 +56,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/switch.2asm.js b/test/wasm2js/switch.2asm.js
index f0ad663..219fb33 100644
--- a/test/wasm2js/switch.2asm.js
+++ b/test/wasm2js/switch.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0(i) {
@@ -183,9 +183,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var stmt = retasmFunc.stmt;
 export var expr = retasmFunc.expr;
 export var arg = retasmFunc.arg;
diff --git a/test/wasm2js/tee_local.2asm.js b/test/wasm2js/tee_local.2asm.js
index ad6fc58..871131a 100644
--- a/test/wasm2js/tee_local.2asm.js
+++ b/test/wasm2js/tee_local.2asm.js
@@ -1,6 +1,6 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -11,9 +11,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -67,9 +67,6 @@ function asmFunc(env) {
   $3_1 = $3_1 | 0;
   $4_1 = $4_1 | 0;
   var i64toi32_i32$0 = 0;
-  i64toi32_i32$0 = 0;
-  i64toi32_i32$0 = 0;
-  i64toi32_i32$0 = 0;
  }
  
  function $9($0_1, $0$hi, $1_1, $2_1, $3_1, $4_1) {
@@ -327,9 +324,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var type_local_i32 = retasmFunc.type_local_i32;
 export var type_local_i64 = retasmFunc.type_local_i64;
 export var type_local_f32 = retasmFunc.type_local_f32;
diff --git a/test/wasm2js/traps.2asm.js b/test/wasm2js/traps.2asm.js
index 5ca565d..ea76eff 100644
--- a/test/wasm2js/traps.2asm.js
+++ b/test/wasm2js/traps.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var __wasm_intrinsics_temp_i64 = 0;
@@ -303,34 +302,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -512,134 +509,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -729,14 +724,14 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var no_dce_i32_div_s = retasmFunc.no_dce_i32_div_s;
 export var no_dce_i32_div_u = retasmFunc.no_dce_i32_div_u;
 export var no_dce_i64_div_s = retasmFunc.no_dce_i64_div_s;
 export var no_dce_i64_div_u = retasmFunc.no_dce_i64_div_u;
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -747,7 +742,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var __wasm_intrinsics_temp_i64 = 0;
@@ -1020,34 +1014,32 @@ function asmFunc(env) {
              }
              var$2 = $37;
              if (var$2) {
-              block : {
-               i64toi32_i32$1 = var$1$hi;
-               var$3 = var$1;
-               if (!var$3) {
-                break label$11
-               }
-               i64toi32_i32$1 = var$1$hi;
-               i64toi32_i32$0 = var$1;
+              i64toi32_i32$1 = var$1$hi;
+              var$3 = var$1;
+              if (!var$3) {
+               break label$11
+              }
+              i64toi32_i32$1 = var$1$hi;
+              i64toi32_i32$0 = var$1;
+              i64toi32_i32$2 = 0;
+              i64toi32_i32$3 = 32;
+              i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
+              if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
                i64toi32_i32$2 = 0;
-               i64toi32_i32$3 = 32;
-               i64toi32_i32$4 = i64toi32_i32$3 & 31 | 0;
-               if (32 >>> 0 <= (i64toi32_i32$3 & 63 | 0) >>> 0) {
-                i64toi32_i32$2 = 0;
-                $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-               } else {
-                i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
-                $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
-               }
-               var$4 = $38;
-               if (!var$4) {
-                break label$9
-               }
-               var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
-               if (var$2 >>> 0 <= 31 >>> 0) {
-                break label$8
-               }
-               break label$2;
+               $38 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+              } else {
+               i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$4 | 0;
+               $38 = (((1 << i64toi32_i32$4 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$4 | 0) | 0 | (i64toi32_i32$0 >>> i64toi32_i32$4 | 0) | 0;
+              }
+              var$4 = $38;
+              if (!var$4) {
+               break label$9
               }
+              var$2 = Math_clz32(var$4) - Math_clz32(var$2) | 0;
+              if (var$2 >>> 0 <= 31 >>> 0) {
+               break label$8
+              }
+              break label$2;
              }
              i64toi32_i32$2 = var$1$hi;
              i64toi32_i32$1 = var$1;
@@ -1229,134 +1221,132 @@ function asmFunc(env) {
     var$0$hi = i64toi32_i32$2;
     label$13 : {
      if (var$2) {
-      block3 : {
-       i64toi32_i32$2 = var$1$hi;
-       i64toi32_i32$1 = var$1;
-       i64toi32_i32$3 = -1;
-       i64toi32_i32$0 = -1;
-       i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
-       i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
-       if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
-        i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      i64toi32_i32$2 = var$1$hi;
+      i64toi32_i32$1 = var$1;
+      i64toi32_i32$3 = -1;
+      i64toi32_i32$0 = -1;
+      i64toi32_i32$4 = i64toi32_i32$1 + i64toi32_i32$0 | 0;
+      i64toi32_i32$5 = i64toi32_i32$2 + i64toi32_i32$3 | 0;
+      if (i64toi32_i32$4 >>> 0 < i64toi32_i32$0 >>> 0) {
+       i64toi32_i32$5 = i64toi32_i32$5 + 1 | 0
+      }
+      var$8 = i64toi32_i32$4;
+      var$8$hi = i64toi32_i32$5;
+      label$15 : while (1) {
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$2 = var$5;
+       i64toi32_i32$1 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
+        $45 = 0;
+       } else {
+        i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
+        $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
        }
-       var$8 = i64toi32_i32$4;
-       var$8$hi = i64toi32_i32$5;
-       label$15 : while (1) {
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$2 = var$5;
-        i64toi32_i32$1 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$1 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-         $45 = 0;
-        } else {
-         i64toi32_i32$1 = ((1 << i64toi32_i32$3 | 0) - 1 | 0) & (i64toi32_i32$2 >>> (32 - i64toi32_i32$3 | 0) | 0) | 0 | (i64toi32_i32$5 << i64toi32_i32$3 | 0) | 0;
-         $45 = i64toi32_i32$2 << i64toi32_i32$3 | 0;
-        }
-        $140 = $45;
-        $140$hi = i64toi32_i32$1;
-        i64toi32_i32$1 = var$0$hi;
-        i64toi32_i32$5 = var$0;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = 0;
-         $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
-         $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
-        }
-        $142$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = $140$hi;
-        i64toi32_i32$1 = $140;
-        i64toi32_i32$5 = $142$hi;
-        i64toi32_i32$0 = $46;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
-        var$5$hi = i64toi32_i32$5;
-        $144 = var$5;
-        $144$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$5 = var$5$hi;
-        i64toi32_i32$5 = var$8$hi;
-        i64toi32_i32$2 = var$8;
-        i64toi32_i32$1 = var$5$hi;
-        i64toi32_i32$0 = var$5;
-        i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
-        i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
-        i64toi32_i32$5 = i64toi32_i32$3;
-        i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 63;
-        i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
-         $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-        } else {
-         i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
-         $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
-        }
-        var$6 = $47;
-        var$6$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$1$hi;
-        i64toi32_i32$2 = var$6$hi;
-        i64toi32_i32$4 = var$6;
-        i64toi32_i32$5 = var$1$hi;
-        i64toi32_i32$0 = var$1;
-        i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
-        $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
-        $151$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = $144$hi;
-        i64toi32_i32$2 = $144;
-        i64toi32_i32$4 = $151$hi;
-        i64toi32_i32$0 = $151;
-        i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
-        i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
-        i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
-        i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
-        var$5 = i64toi32_i32$1;
-        var$5$hi = i64toi32_i32$3;
-        i64toi32_i32$3 = var$0$hi;
-        i64toi32_i32$5 = var$0;
+       $140 = $45;
+       $140$hi = i64toi32_i32$1;
+       i64toi32_i32$1 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$3 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
         i64toi32_i32$2 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
-        if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
-         i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-         $48 = 0;
-        } else {
-         i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
-         $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
-        }
-        $154$hi = i64toi32_i32$2;
-        i64toi32_i32$2 = var$7$hi;
-        i64toi32_i32$2 = $154$hi;
-        i64toi32_i32$3 = $48;
-        i64toi32_i32$5 = var$7$hi;
-        i64toi32_i32$0 = var$7;
-        i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
-        var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
-        var$0$hi = i64toi32_i32$5;
-        i64toi32_i32$5 = var$6$hi;
-        i64toi32_i32$2 = var$6;
-        i64toi32_i32$3 = 0;
-        i64toi32_i32$0 = 1;
-        i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
-        var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
-        var$6$hi = i64toi32_i32$3;
-        var$7 = var$6;
-        var$7$hi = i64toi32_i32$3;
-        var$2 = var$2 + -1 | 0;
-        if (var$2) {
-         continue label$15
-        }
-        break label$15;
-       };
-       break label$13;
-      }
+        $46 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$1 >>> i64toi32_i32$3 | 0;
+        $46 = (((1 << i64toi32_i32$3 | 0) - 1 | 0) & i64toi32_i32$1 | 0) << (32 - i64toi32_i32$3 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$3 | 0) | 0;
+       }
+       $142$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = $140$hi;
+       i64toi32_i32$1 = $140;
+       i64toi32_i32$5 = $142$hi;
+       i64toi32_i32$0 = $46;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$5 = i64toi32_i32$1 | i64toi32_i32$0 | 0;
+       var$5$hi = i64toi32_i32$5;
+       $144 = var$5;
+       $144$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$5 = var$5$hi;
+       i64toi32_i32$5 = var$8$hi;
+       i64toi32_i32$2 = var$8;
+       i64toi32_i32$1 = var$5$hi;
+       i64toi32_i32$0 = var$5;
+       i64toi32_i32$3 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$4 = i64toi32_i32$6 + i64toi32_i32$1 | 0;
+       i64toi32_i32$4 = i64toi32_i32$5 - i64toi32_i32$4 | 0;
+       i64toi32_i32$5 = i64toi32_i32$3;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 63;
+       i64toi32_i32$1 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$4 >> 31 | 0;
+        $47 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+       } else {
+        i64toi32_i32$2 = i64toi32_i32$4 >> i64toi32_i32$1 | 0;
+        $47 = (((1 << i64toi32_i32$1 | 0) - 1 | 0) & i64toi32_i32$4 | 0) << (32 - i64toi32_i32$1 | 0) | 0 | (i64toi32_i32$5 >>> i64toi32_i32$1 | 0) | 0;
+       }
+       var$6 = $47;
+       var$6$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$1$hi;
+       i64toi32_i32$2 = var$6$hi;
+       i64toi32_i32$4 = var$6;
+       i64toi32_i32$5 = var$1$hi;
+       i64toi32_i32$0 = var$1;
+       i64toi32_i32$5 = i64toi32_i32$2 & i64toi32_i32$5 | 0;
+       $151 = i64toi32_i32$4 & i64toi32_i32$0 | 0;
+       $151$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = $144$hi;
+       i64toi32_i32$2 = $144;
+       i64toi32_i32$4 = $151$hi;
+       i64toi32_i32$0 = $151;
+       i64toi32_i32$1 = i64toi32_i32$2 - i64toi32_i32$0 | 0;
+       i64toi32_i32$6 = i64toi32_i32$2 >>> 0 < i64toi32_i32$0 >>> 0;
+       i64toi32_i32$3 = i64toi32_i32$6 + i64toi32_i32$4 | 0;
+       i64toi32_i32$3 = i64toi32_i32$5 - i64toi32_i32$3 | 0;
+       var$5 = i64toi32_i32$1;
+       var$5$hi = i64toi32_i32$3;
+       i64toi32_i32$3 = var$0$hi;
+       i64toi32_i32$5 = var$0;
+       i64toi32_i32$2 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$4 = i64toi32_i32$0 & 31 | 0;
+       if (32 >>> 0 <= (i64toi32_i32$0 & 63 | 0) >>> 0) {
+        i64toi32_i32$2 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+        $48 = 0;
+       } else {
+        i64toi32_i32$2 = ((1 << i64toi32_i32$4 | 0) - 1 | 0) & (i64toi32_i32$5 >>> (32 - i64toi32_i32$4 | 0) | 0) | 0 | (i64toi32_i32$3 << i64toi32_i32$4 | 0) | 0;
+        $48 = i64toi32_i32$5 << i64toi32_i32$4 | 0;
+       }
+       $154$hi = i64toi32_i32$2;
+       i64toi32_i32$2 = var$7$hi;
+       i64toi32_i32$2 = $154$hi;
+       i64toi32_i32$3 = $48;
+       i64toi32_i32$5 = var$7$hi;
+       i64toi32_i32$0 = var$7;
+       i64toi32_i32$5 = i64toi32_i32$2 | i64toi32_i32$5 | 0;
+       var$0 = i64toi32_i32$3 | i64toi32_i32$0 | 0;
+       var$0$hi = i64toi32_i32$5;
+       i64toi32_i32$5 = var$6$hi;
+       i64toi32_i32$2 = var$6;
+       i64toi32_i32$3 = 0;
+       i64toi32_i32$0 = 1;
+       i64toi32_i32$3 = i64toi32_i32$5 & i64toi32_i32$3 | 0;
+       var$6 = i64toi32_i32$2 & i64toi32_i32$0 | 0;
+       var$6$hi = i64toi32_i32$3;
+       var$7 = var$6;
+       var$7$hi = i64toi32_i32$3;
+       var$2 = var$2 + -1 | 0;
+       if (var$2) {
+        continue label$15
+       }
+       break label$15;
+      };
+      break label$13;
      }
     }
     i64toi32_i32$3 = var$5$hi;
@@ -1448,14 +1438,14 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var no_dce_i32_rem_s = retasmFunc.no_dce_i32_rem_s;
 export var no_dce_i32_rem_u = retasmFunc.no_dce_i32_rem_u;
 export var no_dce_i64_rem_s = retasmFunc.no_dce_i64_rem_s;
 export var no_dce_i64_rem_u = retasmFunc.no_dce_i64_rem_u;
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -1466,7 +1456,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(x) {
@@ -1569,8 +1558,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var no_dce_i32_trunc_f32_s = retasmFunc.no_dce_i32_trunc_f32_s;
 export var no_dce_i32_trunc_f32_u = retasmFunc.no_dce_i32_trunc_f32_u;
 export var no_dce_i32_trunc_f64_s = retasmFunc.no_dce_i32_trunc_f64_s;
@@ -1580,7 +1569,7 @@ export var no_dce_i64_trunc_f32_u = retasmFunc.no_dce_i64_trunc_f32_u;
 export var no_dce_i64_trunc_f64_s = retasmFunc.no_dce_i64_trunc_f64_s;
 export var no_dce_i64_trunc_f64_u = retasmFunc.no_dce_i64_trunc_f64_u;
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -1600,7 +1589,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  function $0(i) {
@@ -1722,8 +1710,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var no_dce_i32_load = retasmFunc.no_dce_i32_load;
 export var no_dce_i32_load16_s = retasmFunc.no_dce_i32_load16_s;
 export var no_dce_i32_load16_u = retasmFunc.no_dce_i32_load16_u;
diff --git a/test/wasm2js/unaligned.2asm.js b/test/wasm2js/unaligned.2asm.js
index 8f8f367..bfcffad 100644
--- a/test/wasm2js/unaligned.2asm.js
+++ b/test/wasm2js/unaligned.2asm.js
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
   var bufferView;
 
@@ -31,7 +31,7 @@ import { setTempRet0 } from 'env';
     f32ScratchView[2] = value;
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -51,9 +51,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -177,9 +177,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_load = retasmFunc.i32_load;
 export var i64_load = retasmFunc.i64_load;
 export var f32_load = retasmFunc.f32_load;
diff --git a/test/wasm2js/unaligned.2asm.js.opt b/test/wasm2js/unaligned.2asm.js.opt
index 1f0fd6f..701a839 100644
--- a/test/wasm2js/unaligned.2asm.js.opt
+++ b/test/wasm2js/unaligned.2asm.js.opt
@@ -1,4 +1,4 @@
-import { setTempRet0 } from 'env';
+import * as env from 'env';
 
   var bufferView;
 
@@ -27,7 +27,7 @@ import { setTempRet0 } from 'env';
     return f32ScratchView[2];
   }
       
-function asmFunc(env) {
+function asmFunc(imports) {
  var buffer = new ArrayBuffer(65536);
  var HEAP8 = new Int8Array(buffer);
  var HEAP16 = new Int16Array(buffer);
@@ -47,9 +47,9 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
+ var env = imports.env;
  var setTempRet0 = env.setTempRet0;
  var i64toi32_i32$HIGH_BITS = 0;
  function $0() {
@@ -126,9 +126,9 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); },
-    setTempRet0
-  });
+var retasmFunc = asmFunc({
+  "env": env,
+});
 export var i32_load = retasmFunc.i32_load;
 export var i64_load = retasmFunc.i64_load;
 export var f32_load = retasmFunc.f32_load;
diff --git a/test/wasm2js/unary-ops.2asm.js b/test/wasm2js/unary-ops.2asm.js
index 703aaf3..f00d4a3 100644
--- a/test/wasm2js/unary-ops.2asm.js
+++ b/test/wasm2js/unary-ops.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var i64toi32_i32$HIGH_BITS = 0;
@@ -517,8 +516,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var i32_popcnt = retasmFunc.i32_popcnt;
 export var check_popcnt_i64 = retasmFunc.check_popcnt_i64;
 export var check_extend_ui32 = retasmFunc.check_extend_ui32;
diff --git a/test/wasm2js/unary-ops.2asm.js.opt b/test/wasm2js/unary-ops.2asm.js.opt
index 7bb08bd..bca6508 100644
--- a/test/wasm2js/unary-ops.2asm.js.opt
+++ b/test/wasm2js/unary-ops.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var i64toi32_i32$HIGH_BITS = 0;
@@ -49,7 +48,7 @@ function asmFunc(env) {
   while (1) {
    if ($1_1 | $4) {
     $0 = $4;
-    $4 = $4 - 1 & $4;
+    $4 = $0 & $0 - 1;
     $1_1 = $1_1 - !$0 & $1_1;
     $5 = $5 + 1 | 0;
     $6_1 = $5 ? $6_1 : $6_1 + 1 | 0;
@@ -114,8 +113,8 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var i32_popcnt = retasmFunc.i32_popcnt;
 export var check_popcnt_i64 = retasmFunc.check_popcnt_i64;
 export var check_extend_ui32 = retasmFunc.check_extend_ui32;
diff --git a/test/wasm2js/unreachable-get-cycle.2asm.js b/test/wasm2js/unreachable-get-cycle.2asm.js
index 3278e98..91dfa17 100644
--- a/test/wasm2js/unreachable-get-cycle.2asm.js
+++ b/test/wasm2js/unreachable-get-cycle.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/unreachable-get-cycle.2asm.js.opt b/test/wasm2js/unreachable-get-cycle.2asm.js.opt
index 3278e98..91dfa17 100644
--- a/test/wasm2js/unreachable-get-cycle.2asm.js.opt
+++ b/test/wasm2js/unreachable-get-cycle.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/unreachable-insts.2asm.js b/test/wasm2js/unreachable-insts.2asm.js
index 3278e98..91dfa17 100644
--- a/test/wasm2js/unreachable-insts.2asm.js
+++ b/test/wasm2js/unreachable-insts.2asm.js
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/unreachable-insts.2asm.js.opt b/test/wasm2js/unreachable-insts.2asm.js.opt
index 3278e98..91dfa17 100644
--- a/test/wasm2js/unreachable-insts.2asm.js.opt
+++ b/test/wasm2js/unreachable-insts.2asm.js.opt
@@ -1,5 +1,5 @@
 
-function asmFunc(env) {
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +10,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  return {
@@ -18,5 +17,5 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
diff --git a/test/wasm2js/unreachable-later.2asm.js b/test/wasm2js/unreachable-later.2asm.js
index 0259a04..e3fd358 100644
--- a/test/wasm2js/unreachable-later.2asm.js
+++ b/test/wasm2js/unreachable-later.2asm.js
@@ -1,5 +1,7 @@
 
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +12,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var global$0 = 10;
@@ -55,7 +56,7 @@ function asmFunc(env) {
   if (!$29) {
    return -255 | 0
   } else {
-   abort()
+   wasm2js_trap()
   }
  }
  
@@ -64,6 +65,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var func_50 = retasmFunc.func_50;
diff --git a/test/wasm2js/unreachable-later.2asm.js.opt b/test/wasm2js/unreachable-later.2asm.js.opt
index b557a16..c6a2de6 100644
--- a/test/wasm2js/unreachable-later.2asm.js.opt
+++ b/test/wasm2js/unreachable-later.2asm.js.opt
@@ -1,5 +1,7 @@
 
-function asmFunc(env) {
+function wasm2js_trap() { throw new Error('abort'); }
+
+function asmFunc(imports) {
  var Math_imul = Math.imul;
  var Math_fround = Math.fround;
  var Math_abs = Math.abs;
@@ -10,7 +12,6 @@ function asmFunc(env) {
  var Math_ceil = Math.ceil;
  var Math_trunc = Math.trunc;
  var Math_sqrt = Math.sqrt;
- var abort = env.abort;
  var nan = NaN;
  var infinity = Infinity;
  var global$0 = 10;
@@ -27,7 +28,7 @@ function asmFunc(env) {
    if (global$0) {
     break folding_inner0
    }
-   abort();
+   wasm2js_trap();
   }
   return $0_1 | 0;
  }
@@ -37,6 +38,6 @@ function asmFunc(env) {
  };
 }
 
-var retasmFunc = asmFunc(  { abort: function() { throw new Error('abort'); }
-  });
+var retasmFunc = asmFunc({
+});
 export var func_50 = retasmFunc.func_50;
diff --git a/third_party/llvm-project/DWARFEmitter.cpp b/third_party/llvm-project/DWARFEmitter.cpp
index 1dc59ed..7c66a82 100644
--- a/third_party/llvm-project/DWARFEmitter.cpp
+++ b/third_party/llvm-project/DWARFEmitter.cpp
@@ -197,7 +197,8 @@ protected:
   void onEndCompileUnit(const DWARFYAML::Unit &CU) {
     size_t EndPos = OS.tell();
     if (EndPos - StartPos != CU.Length.getLength() && !CU.AddrSizeChanged) {
-      llvm_unreachable("compile unit size was incorrect");
+      llvm_unreachable("compile unit size was incorrect "
+                       "(this may be an unsupported version of DWARF)");
     }
   }
 
diff --git a/third_party/llvm-project/include/llvm/include/llvm/DebugInfo/DWARFContext.h b/third_party/llvm-project/include/llvm/include/llvm/DebugInfo/DWARFContext.h
index 2dec107..510cf40 100644
--- a/third_party/llvm-project/include/llvm/include/llvm/DebugInfo/DWARFContext.h
+++ b/third_party/llvm-project/include/llvm/include/llvm/DebugInfo/DWARFContext.h
@@ -332,7 +332,8 @@ public:
 
   bool isLittleEndian() const { return DObj->isLittleEndian(); }
   static bool isSupportedVersion(unsigned version) {
-    return version == 2 || version == 3 || version == 4 || version == 5;
+    // XXX BINARYEN: removed version 5, which we do not support
+    return version == 2 || version == 3 || version == 4;
   }
 
   std::shared_ptr<DWARFContext> getDWOContext(StringRef AbsolutePath);

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/debug/.build-id/08/970389b43323b7a91786097c9dd50bb4d6fc65.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/14/a2d48a708820bfdc97cdff907045f5910e41fe.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/1c/40110d9ed453e621ca226ab01e11633b7b94dc.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/3b/5919cce40da3f31f087bfc027325545b4e5ea4.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/4a/7af84aa5be349c8f8f1dba396d0de9c217a53b.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/61/a99054a2a4527109645ec7d7fa229874d34ff7.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/6a/90749744abe029f8ac2ea8e66d7291a18ad4cc.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/84/28f70da39b747a79c14e73bacf13878ad0f351.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/d5/8fb228035552e55dd1dfdf6e5bbe645ec7314c.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/d9/68bd2230760e44c939afcbc731768205f9ec0e.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/dd/a4f326e28e9068a74f85451388629f152b79b5.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/de/ad7d092741008953ea552efc069c973fa8974b.debug

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/debug/.build-id/10/fa9f66957755f5d4dea0772893275e9faacc7b.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/1d/e0ec7c530df5a4abc0003b047bf00976d8f499.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/33/96f18e898cfc09eb394459e801092570663185.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/3f/079a334e41f5ff8beba6a8c1bcdb9b571b058e.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/42/8174cb6d943d042b6bfb796ca11a817563f5a5.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/42/8d3259447ace595402563d300925a893ffc2f8.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/46/068e2232b669d25504b27ff614027f8646fb36.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/49/65a37e3a77e53adf648a27918731686233a5e2.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/60/33fce9a7be1a594b25bad1f9e8c00e3eafdf2f.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/72/e5e541f8ecded019c5b8606cf9509d6998369c.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/80/7af1d0cb1de94633c0ac53b21bf7171cd18ec5.debug
-rw-r--r--  root/root   /usr/lib/debug/.build-id/f0/d821620215ae6401a41632cc6c384bac35e16e.debug

Control files of package binaryen: lines which differ (wdiff format)

  • Depends: libc6 (>= 2.34), libgcc-s1 (>= 3.4), libstdc++6 (>= 12.2.0-10) 12.2.0-12)

Control files of package binaryen-dbgsym: lines which differ (wdiff format)

  • Build-Ids: 10fa9f66957755f5d4dea0772893275e9faacc7b 1de0ec7c530df5a4abc0003b047bf00976d8f499 3396f18e898cfc09eb394459e801092570663185 3f079a334e41f5ff8beba6a8c1bcdb9b571b058e 428174cb6d943d042b6bfb796ca11a817563f5a5 428d3259447ace595402563d300925a893ffc2f8 46068e2232b669d25504b27ff614027f8646fb36 4965a37e3a77e53adf648a27918731686233a5e2 6033fce9a7be1a594b25bad1f9e8c00e3eafdf2f 72e5e541f8ecded019c5b8606cf9509d6998369c 807af1d0cb1de94633c0ac53b21bf7171cd18ec5 f0d821620215ae6401a41632cc6c384bac35e16e 08970389b43323b7a91786097c9dd50bb4d6fc65 14a2d48a708820bfdc97cdff907045f5910e41fe 1c40110d9ed453e621ca226ab01e11633b7b94dc 3b5919cce40da3f31f087bfc027325545b4e5ea4 4a7af84aa5be349c8f8f1dba396d0de9c217a53b 61a99054a2a4527109645ec7d7fa229874d34ff7 6a90749744abe029f8ac2ea8e66d7291a18ad4cc 8428f70da39b747a79c14e73bacf13878ad0f351 d58fb228035552e55dd1dfdf6e5bbe645ec7314c d968bd2230760e44c939afcbc731768205f9ec0e dda4f326e28e9068a74f85451388629f152b79b5 dead7d092741008953ea552efc069c973fa8974b

More details

Full run details