add a wasm bazel toolchain (#603)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index d0d6af5..8b39596 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -124,6 +124,26 @@
             docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
             make -C ./docker version=${CIRCLE_TAG} alias=latest push
 
+  test-bazel:
+    executor: bionic
+    steps:
+      - checkout
+      - run: apt-get install -q -y curl gnupg
+      - run: curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
+      - run: mv bazel.gpg /etc/apt/trusted.gpg.d/
+      - run: echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list
+      - run:
+          name: install pip
+          command: |
+            apt-get update -q
+            apt-get install -q -y python3-pip
+      - run: pip3 install absl-py
+      - run:
+          name: install bazel
+          command: |
+            apt-get install -q -y bazel
+      - run: scripts/test_bazel.sh
+
 workflows:
   flake8:
     jobs:
@@ -146,3 +166,6 @@
               ignore: /.*/
             tags:
               only: /.*/
+  test-bazel:
+    jobs:
+      - test-bazel
diff --git a/bazel/README.md b/bazel/README.md
new file mode 100644
index 0000000..17e9264
--- /dev/null
+++ b/bazel/README.md
@@ -0,0 +1,63 @@
+# Bazel Emscripten toolchain
+
+## Setup Instructions
+
+1. Merge the `WORKSPACE` file in with your own at the root of your bazel
+directory structure. If you don't have one, simply copy the file.
+2. Merge the `bazelrc` file in with your `.bazelrc` file at the root of your
+bazel directory structure. If you don't have one, simply copy the file and
+rename it to `.bazelrc`. (Note the `.`)
+3. Copy the `emscripten_toolchain` folder along with its contents to the root of
+your bazel directory.
+
+Your directory structure should look like this:
+```
+bazel_root/
+├── .bazelrc
+├── WORKSPACE
+├── emscripten_toolchain/
+│   ├── BUILD.bazel
+│   ├── builddefs.bzl
+│   ├── crosstool.bzl
+│   ├── emar.sh
+│   ├── emcc.sh
+│   ├── emcc_link.sh
+│   ├── emscripten.BUILD
+│   ├── emscripten_config
+│   ├── env.sh
+│   ├── link_wrapper.py
+│   ├── wasm_binary.py
+│   ├── wasm_cc_binary.bzl
+│   ├── wasm_rules.bzl
+├── your_project_folder/
+│   ├── your_project.file
+```
+
+## Building
+
+### Using --config=wasm
+Simply pass `--config=wasm` when building a normal `cc_binary`. The result of
+this build will be a tar archive containing any files produced by emscripten.
+
+### Using wasm_cc_binary
+First, write a new rule wrapping your `cc_binary`.
+
+```
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
+
+cc_binary(
+    name = "hello-world",
+    srcs = ["hello-world.cc"],
+)
+
+wasm_cc_binary(
+    name = "hello-world-wasm",
+    cc_target = ":hello-world",
+)
+```
+
+Now you can run `bazel build :hello-world-wasm`. The result of this build will
+be the individual files produced by emscripten. Note that some of these files
+may be empty. This is because bazel has no concept of optional outputs for
+rules.
diff --git a/bazel/WORKSPACE b/bazel/WORKSPACE
new file mode 100644
index 0000000..971c714
--- /dev/null
+++ b/bazel/WORKSPACE
@@ -0,0 +1,25 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+    name = "build_bazel_rules_nodejs",
+    sha256 = "0f2de53628e848c1691e5729b515022f5a77369c76a09fbe55611e12731c90e3",
+    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.0.1/rules_nodejs-2.0.1.tar.gz"],
+)
+
+load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")
+
+# emscripten 2.0.2
+http_archive(
+    name = "emscripten",
+    sha256 = "9febbc252372efee82e62d548f510dfc85ade6661f5f3be28823848d4757d614",
+    strip_prefix = "install",
+    url = "https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/ede25d889a0abe63360d4c5d420087c8753b8bbe/wasm-binaries.tbz2",
+    build_file = "//emscripten_toolchain:emscripten.BUILD",
+    type = "tar.bz2",
+)
+
+npm_install(
+    name = "npm",
+    package_json = "@emscripten//:emscripten/package.json",
+    package_lock_json = "@emscripten//:emscripten/package-lock.json",
+)
diff --git a/bazel/bazelrc b/bazel/bazelrc
new file mode 100644
index 0000000..85801e8
--- /dev/null
+++ b/bazel/bazelrc
@@ -0,0 +1,5 @@
+build:wasm --crosstool_top=//emscripten_toolchain:everything
+
+build:wasm --cpu=wasm
+
+build:wasm --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
diff --git a/bazel/emscripten_toolchain/BUILD.bazel b/bazel/emscripten_toolchain/BUILD.bazel
new file mode 100644
index 0000000..89fc320
--- /dev/null
+++ b/bazel/emscripten_toolchain/BUILD.bazel
@@ -0,0 +1,80 @@
+load(":crosstool.bzl", "emscripten_cc_toolchain_config_rule")
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "common-script-includes",
+    srcs = [
+        "emar.sh",
+        "emcc.sh",
+        "emscripten_config",
+        "env.sh",
+        "@emscripten//:all",
+        "@nodejs//:node_files",
+        "@npm//:node_modules",
+    ],
+)
+
+filegroup(
+    name = "compile-emscripten",
+    srcs = [":common-script-includes"],
+)
+
+filegroup(
+    name = "link-emscripten",
+    srcs = [
+        "emcc_link.sh",
+        "link_wrapper.py",
+        ":common-script-includes",
+        "@emscripten//:all",
+        "@nodejs//:node_files",
+    ],
+)
+
+filegroup(
+    name = "every-file",
+    srcs = [
+        ":compile-emscripten",
+        ":link-emscripten",
+        "@emscripten//:all",
+        "@nodejs//:node_files",
+    ],
+)
+
+filegroup(name = "empty")
+
+# dlmalloc.bc is implictly added by the emscripten toolchain
+cc_library(name = "malloc")
+
+emscripten_cc_toolchain_config_rule(
+    name = "wasm",
+    cpu = "wasm",
+    emscripten_version = "emscripten",
+)
+
+cc_toolchain(
+    name = "cc-compiler-wasm",
+    all_files = ":every-file",
+    ar_files = ":common-script-includes",
+    as_files = ":empty",
+    compiler_files = ":compile-emscripten",
+    dwp_files = ":empty",
+    linker_files = ":link-emscripten",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    toolchain_config = "wasm",
+    toolchain_identifier = "emscripten-wasm",
+)
+
+cc_toolchain_suite(
+    name = "everything",
+    toolchains = {
+        "wasm": ":cc-compiler-wasm",
+        "wasm|emscripten": ":cc-compiler-wasm",
+    },
+)
+
+py_binary(
+    name = "wasm_binary",
+    srcs = ["wasm_binary.py"],
+)
diff --git a/bazel/emscripten_toolchain/crosstool.bzl b/bazel/emscripten_toolchain/crosstool.bzl
new file mode 100644
index 0000000..31b516e
--- /dev/null
+++ b/bazel/emscripten_toolchain/crosstool.bzl
@@ -0,0 +1,1126 @@
+"""This module encapsulates logic to create emscripten_cc_toolchain_config rule."""
+
+load(
+    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
+    "action_config",
+    "env_entry",
+    "env_set",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "tool",
+    "tool_path",
+    "variable_with_value",
+    "with_feature_set",
+    _flag_set = "flag_set",
+)
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+
+def flag_set(flags = None, features = None, not_features = None, **kwargs):
+    """Extension to flag_set which allows for a "simple" form.
+
+    The simple form allows specifying flags as a simple list instead of a flag_group
+    if enable_if or expand_if semantics are not required.
+
+    Similarly, the simple form allows passing features/not_features if they are a simple
+    list of semantically "and" features.
+    (i.e. "asan" and "dbg", rather than "asan" or "dbg")
+
+    Args:
+      flags: list, set of flags
+      features: list, set of features required to be enabled.
+      not_features: list, set of features required to not be enabled.
+      **kwargs: The rest of the args for flag_set.
+
+    Returns:
+      flag_set
+    """
+    if flags:
+        if kwargs.get("flag_groups"):
+            fail("Cannot set flags and flag_groups")
+        else:
+            kwargs["flag_groups"] = [flag_group(flags = flags)]
+
+    if features or not_features:
+        if kwargs.get("with_features"):
+            fail("Cannot set features/not_feature and with_features")
+        kwargs["with_features"] = [with_feature_set(
+            features = features or [],
+            not_features = not_features or [],
+        )]
+    return _flag_set(**kwargs)
+
+CROSSTOOL_DEFAULT_WARNINGS = [
+    "-Wall",
+]
+
+def _impl(ctx):
+    target_cpu = ctx.attr.cpu
+    toolchain_identifier = "emscripten-" + target_cpu
+    target_system_name = target_cpu + "-unknown-emscripten"
+
+    host_system_name = "i686-unknown-linux-gnu"
+
+    target_libc = "musl/js"
+
+    abi_version = "emscripten_syscalls"
+
+    compiler = "emscripten"
+    abi_libc_version = "default"
+
+    cc_target_os = "emscripten"
+    emscripten_version = ctx.attr.emscripten_version
+    emscripten_root = "external/emscripten/" + emscripten_version
+
+    builtin_sysroot = None
+
+    ################################################################
+    # Tools
+    ################################################################
+    clang_tool = tool(path = "emcc.sh")
+    clif_match_tool = tool(path = "dummy_clif_matcher")
+    link_tool = tool(path = "emcc_link.sh")
+    archive_tool = tool(path = "emar.sh")
+    strip_tool = tool(path = "NOT_USED_STRIP_TOOL")
+
+    #### Legacy tool paths (much of this is redundant with action_configs, but
+    #### these are still used for some things)
+    tool_paths = [
+        tool_path(name = "ar", path = "emar.sh"),
+        tool_path(name = "cpp", path = "/bin/false"),
+        tool_path(name = "gcc", path = "emcc.sh"),
+        tool_path(name = "gcov", path = "/bin/false"),
+        tool_path(name = "ld", path = "emcc_link.sh"),
+        tool_path(name = "nm", path = "NOT_USED"),
+        tool_path(name = "objdump", path = "/bin/false"),
+        tool_path(name = "strip", path = "NOT_USED"),
+    ]
+
+    ################################################################
+    # Action Configs
+    ################################################################
+
+    cpp_compile_action = action_config(
+        action_name = ACTION_NAMES.cpp_compile,
+        tools = [clang_tool],
+    )
+
+    cpp_module_compile_action = action_config(
+        action_name = ACTION_NAMES.cpp_module_compile,
+        tools = [clang_tool],
+    )
+
+    cpp_module_codegen_action = action_config(
+        action_name = ACTION_NAMES.cpp_module_codegen,
+        tools = [clang_tool],
+    )
+
+    clif_match_action = action_config(
+        action_name = ACTION_NAMES.clif_match,
+        tools = [clif_match_tool],
+    )
+
+    cpp_link_dynamic_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_dynamic_library,
+        tools = [link_tool],
+    )
+
+    strip_action = action_config(
+        action_name = ACTION_NAMES.strip,
+        tools = [strip_tool],
+    )
+
+    preprocess_assemble_action = action_config(
+        action_name = ACTION_NAMES.preprocess_assemble,
+        tools = [clang_tool],
+    )
+
+    cpp_header_parsing_action = action_config(
+        action_name = ACTION_NAMES.cpp_header_parsing,
+        tools = [clang_tool],
+    )
+
+    cpp_link_static_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_static_library,
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                flag_groups = [
+                    flag_group(
+                        flags = ["rcsD", "%{output_execpath}"],
+                        expand_if_available = "output_execpath",
+                    ),
+                ],
+            ),
+            flag_set(
+                flag_groups = [
+                    flag_group(
+                        iterate_over = "libraries_to_link",
+                        flag_groups = [
+                            flag_group(
+                                flags = ["%{libraries_to_link.name}"],
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "object_file",
+                                ),
+                            ),
+                            flag_group(
+                                flags = ["%{libraries_to_link.object_files}"],
+                                iterate_over = "libraries_to_link.object_files",
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "object_file_group",
+                                ),
+                            ),
+                        ],
+                        expand_if_available = "libraries_to_link",
+                    ),
+                ],
+            ),
+            flag_set(
+                flag_groups = [
+                    flag_group(
+                        flags = ["@%{linker_param_file}"],
+                        expand_if_available = "linker_param_file",
+                    ),
+                ],
+            ),
+        ],
+        tools = [archive_tool],
+    )
+
+    c_compile_action = action_config(
+        action_name = ACTION_NAMES.c_compile,
+        tools = [clang_tool],
+    )
+
+    linkstamp_compile_action = action_config(
+        action_name = ACTION_NAMES.linkstamp_compile,
+        tools = [clang_tool],
+    )
+
+    assemble_action = action_config(
+        action_name = ACTION_NAMES.assemble,
+        tools = [clang_tool],
+    )
+
+    cpp_link_executable_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_executable,
+        tools = [link_tool],
+    )
+
+    cpp_link_nodeps_dynamic_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+        tools = [link_tool],
+    )
+
+    action_configs = [
+        strip_action,
+        c_compile_action,
+        cpp_compile_action,
+        linkstamp_compile_action,
+        assemble_action,
+        preprocess_assemble_action,
+        cpp_header_parsing_action,
+        cpp_module_compile_action,
+        cpp_module_codegen_action,
+        cpp_link_executable_action,
+        cpp_link_dynamic_library_action,
+        cpp_link_nodeps_dynamic_library_action,
+        cpp_link_static_library_action,
+        clif_match_action,
+    ]
+
+    all_compile_actions = [
+        ACTION_NAMES.c_compile,
+        ACTION_NAMES.cpp_compile,
+        ACTION_NAMES.linkstamp_compile,
+        ACTION_NAMES.assemble,
+        ACTION_NAMES.preprocess_assemble,
+        ACTION_NAMES.cpp_header_parsing,
+        ACTION_NAMES.cpp_module_compile,
+        ACTION_NAMES.cpp_module_codegen,
+        ACTION_NAMES.clif_match,
+        ACTION_NAMES.lto_backend,
+    ]
+
+    all_cpp_compile_actions = [
+        ACTION_NAMES.cpp_compile,
+        ACTION_NAMES.linkstamp_compile,
+        ACTION_NAMES.cpp_header_parsing,
+        ACTION_NAMES.cpp_module_compile,
+        ACTION_NAMES.cpp_module_codegen,
+        ACTION_NAMES.clif_match,
+    ]
+
+    preprocessor_compile_actions = [
+        ACTION_NAMES.c_compile,
+        ACTION_NAMES.cpp_compile,
+        ACTION_NAMES.linkstamp_compile,
+        ACTION_NAMES.preprocess_assemble,
+        ACTION_NAMES.cpp_header_parsing,
+        ACTION_NAMES.cpp_module_compile,
+        ACTION_NAMES.clif_match,
+    ]
+
+    all_link_actions = [
+        ACTION_NAMES.cpp_link_executable,
+        ACTION_NAMES.cpp_link_dynamic_library,
+        ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+    ]
+
+    ################################################################
+    # Features
+    ################################################################
+
+    features = [
+        # This set of magic "feature"s are important configuration information for blaze.
+        feature(name = "no_legacy_features", enabled = True),
+        feature(
+            name = "has_configured_linker_path",
+            enabled = True,
+        ),
+
+        # Blaze requests this feature by default, but we don't care.
+        feature(name = "dependency_file"),
+
+        # Blaze requests this feature by default, but we don't care.
+        feature(name = "random_seed"),
+
+        # Formerly "needsPic" attribute
+        feature(name = "supports_pic", enabled = False),
+
+        # Blaze requests this feature by default.
+        # Blaze also tests if this feature is supported, before setting the "pic" build-variable.
+        feature(name = "pic"),
+
+        # Blaze requests this feature if fission is requested
+        # Blaze also tests if it's supported to see if we support fission.
+        feature(name = "per_object_debug_info"),
+
+        # Blaze requests this feature by default.
+        # Blaze also tests if this feature is supported before setting preprocessor_defines
+        # (...but why?)
+        feature(name = "preprocessor_defines"),
+
+        # Blaze requests this feature by default.
+        # Blaze also tests if this feature is supported before setting includes. (...but why?)
+        feature(name = "include_paths"),
+
+        # Blaze tests if this feature is enabled in order to create implicit
+        # "nodeps" .so outputs from cc_library rules.
+        feature(name = "supports_dynamic_linker", enabled = False),
+
+        # Blaze requests this feature when linking a cc_binary which is
+        # "dynamic" aka linked against nodeps-dynamic-library cc_library
+        # outputs.
+        feature(name = "dynamic_linking_mode"),
+
+        #### Configuration features
+        feature(
+            name = "crosstool_cpu",
+            enabled = True,
+            implies = ["crosstool_cpu_" + target_cpu],
+        ),
+        feature(
+            name = "crosstool_cpu_asmjs",
+            provides = ["variant:crosstool_cpu"],
+        ),
+        feature(
+            name = "crosstool_cpu_wasm",
+            provides = ["variant:crosstool_cpu"],
+        ),
+
+        # These 3 features will be automatically enabled by blaze in the
+        # corresponding build mode.
+        feature(
+            name = "opt",
+            provides = ["variant:crosstool_build_mode"],
+        ),
+        feature(
+            name = "dbg",
+            provides = ["variant:crosstool_build_mode"],
+        ),
+        feature(
+            name = "fastbuild",
+            provides = ["variant:crosstool_build_mode"],
+        ),
+
+        #### User-settable features
+
+        # Set if enabling exceptions.
+        feature(name = "exceptions"),
+
+        # This feature overrides the default optimization to prefer execution speed
+        # over binary size (like clang -O3).
+        feature(
+            name = "optimized_for_speed",
+            provides = ["variant:crosstool_optimization_mode"],
+        ),
+
+        # This feature overrides the default optimization to prefer binary size over
+        # execution speed (like clang -Oz).
+        feature(
+            name = "optimized_for_size",
+            provides = ["variant:crosstool_optimization_mode"],
+        ),
+
+        # Convenience aliases / alt-spellings.
+        feature(
+            name = "optimize_for_speed",
+            implies = ["optimized_for_speed"],
+        ),
+        feature(
+            name = "optimize_for_size",
+            implies = ["optimized_for_size"],
+        ),
+
+        # This feature allows easier use of profiling tools by preserving mangled
+        # C++ names. This does everything profiling_funcs does and more.
+        feature(name = "profiling"),
+
+        # This feature emits only enough debug info for function names to appear
+        # in profiles.
+        feature(name = "profiling_funcs"),
+
+        # This feature allows source maps to be generated.
+        feature(
+            name = "source_maps",
+            implies = ["full_debug_info"],
+        ),
+        feature(
+            name = "dwarf_debug_info",
+            implies = ["profiling"],
+        ),
+
+        # Turns on full debug info (-g4).
+        feature(name = "full_debug_info"),
+
+        # Enables the use of "Emscripten" Pthread implementation.
+        # https://kripken.github.io/emscripten-site/docs/porting/pthreads.html
+        # https://github.com/kripken/emscripten/wiki/Pthreads-with-WebAssembly
+        feature(name = "use_pthreads"),
+
+        # If enabled, the runtime will exit when main() completes.
+        feature(name = "exit_runtime"),
+
+        # Primarily for toolchain maintainers:
+        feature(name = "emcc_debug"),
+        feature(name = "emcc_debug_link"),
+        feature(
+            name = "llvm_backend",
+            requires = [feature_set(features = ["crosstool_cpu_wasm"])],
+            enabled = True,
+        ),
+
+        # Remove once flag is flipped.
+        # See https://github.com/bazelbuild/bazel/issues/7687
+        feature(
+            name = "do_not_split_linking_cmdline",
+        ),
+
+        # Adds simd support, only available with the llvm backend.
+        feature(
+            name = "wasm_simd",
+            requires = [feature_set(features = ["llvm_backend"])],
+        ),
+        feature(
+            name = "precise_long_double_printf",
+            enabled = True,
+        ),
+        feature(
+            name = "wasm_warnings_as_errors",
+            enabled = True,
+        ),
+
+        # ASan and UBSan. See also:
+        # https://emscripten.org/docs/debugging/Sanitizers.html
+        feature(name = "wasm_asan"),
+        feature(name = "wasm_ubsan"),
+    ]
+
+    crosstool_default_flag_sets = [
+        # Compile, Link, and CC_FLAGS make variable
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+                ACTION_NAMES.cpp_link_executable,
+                ACTION_NAMES.cpp_link_dynamic_library,
+                ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["--sysroot=%{sysroot}"],
+                    expand_if_available = "sysroot",
+                ),
+            ],
+        ),
+        # Compile + Link
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+                ACTION_NAMES.cpp_link_executable,
+                ACTION_NAMES.cpp_link_dynamic_library,
+                ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+            ],
+            # This forces color diagnostics even on Forge (where we don't have an
+            # attached terminal).
+            flags = [
+                "-fdiagnostics-color",
+            ],
+        ),
+        # C++ compiles (and implicitly link)
+        flag_set(
+            actions = all_cpp_compile_actions,
+            flags = [
+                "-fno-exceptions",
+            ],
+            not_features = ["exceptions"],
+        ),
+        flag_set(
+            actions = all_cpp_compile_actions,
+            flags = [
+                "-fexceptions",
+            ],
+            features = ["exceptions"],
+        ),
+        # All compiles (and implicitly link)
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-fno-strict-aliasing",
+                "-funsigned-char",
+                "-no-canonical-prefixes",
+            ],
+        ),
+        # Language Features
+        flag_set(
+            actions = all_cpp_compile_actions,
+            flags = [
+                "-std=gnu++17",
+                "-nostdinc",
+                "-Xclang",
+                "-nobuiltininc",
+                "-Xclang",
+                "-nostdsysteminc",
+            ],
+        ),
+
+        # Emscripten-specific settings:
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-s", "WASM=0"],
+            features = ["crosstool_cpu_asmjs"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-s", "USE_PTHREADS=1"],
+            features = ["use_pthreads"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-s", "EXIT_RUNTIME=1"],
+            features = ["exit_runtime"],
+        ),
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-pthread"],
+            features = ["llvm_backend", "use_pthreads"],
+        ),
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-msimd128"],
+            features = ["wasm_simd"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-s", "PRINTF_LONG_DOUBLE=1"],
+            features = ["precise_long_double_printf"],
+        ),
+
+        # Opt
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flags = ["-DNDEBUG"],
+            features = ["opt"],
+        ),
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-fomit-frame-pointer"],
+            features = ["opt"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-O3"],
+            features = ["opt"],
+        ),
+        # Users can override opt-level with semantic names...
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-Oz"],
+            features = ["optimized_for_size", "opt"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-O3"],
+            features = ["optimized_for_speed", "opt"],
+        ),
+
+        # Fastbuild
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-fomit-frame-pointer"],
+            features = ["fastbuild"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-O2"],
+            features = ["fastbuild"],
+        ),
+
+        # Dbg
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-fno-omit-frame-pointer"],
+            features = ["dbg"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-g", "-O0"],
+            features = ["dbg"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-g4",
+                "-fsanitize=address",
+                "-O1",
+                "-DADDRESS_SANITIZER=1",
+                "-fno-omit-frame-pointer",
+            ],
+            features = ["wasm_asan"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-g4",
+                "-fsanitize=undefined",
+                "-O1",
+                "-DUNDEFINED_BEHAVIOR_SANITIZER=1",
+                "-fno-omit-frame-pointer",
+                "-fno-sanitize=vptr",
+            ],
+            features = ["wasm_ubsan"],
+        ),
+
+        # Profiling provides full debug info and a special --profiling flag
+        # to control name mangling
+        flag_set(
+            actions = all_link_actions,
+            flags = ["--profiling"],
+            features = ["profiling"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["--profiling_funcs"],
+            features = ["profiling_funcs"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-g4"],
+            features = ["full_debug_info"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-gseparate-dwarf"],
+            features = ["dwarf_debug_info"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-fdebug-compilation-dir=."],
+            features = ["dwarf_debug_info"],
+        ),
+        # Generic warning flag list
+        flag_set(
+            actions = all_compile_actions,
+            flags = CROSSTOOL_DEFAULT_WARNINGS,
+        ),
+
+        # Defines and Includes and Paths and such
+        flag_set(
+            actions = all_compile_actions,
+            flag_groups = [
+                flag_group(flags = ["-fPIC"], expand_if_available = "pic"),
+            ],
+        ),
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-D%{preprocessor_defines}"],
+                    iterate_over = "preprocessor_defines",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-include", "%{includes}"],
+                    iterate_over = "includes",
+                    expand_if_available = "includes",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-iquote", "%{quote_include_paths}"],
+                    iterate_over = "quote_include_paths",
+                ),
+                flag_group(
+                    flags = ["-I%{include_paths}"],
+                    iterate_over = "include_paths",
+                ),
+                flag_group(
+                    flags = ["-isystem", "%{system_include_paths}"],
+                    iterate_over = "system_include_paths",
+                ),
+            ],
+        ),
+
+        ## Linking options (not libs -- those go last)
+
+        # Generic link options
+        flag_set(
+            actions = [
+                ACTION_NAMES.cpp_link_dynamic_library,
+                ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+            ],
+            flags = ["-shared"],
+        ),
+
+        # Linker search paths and objects:
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    iterate_over = "runtime_library_search_directories",
+                    flag_groups = [
+                        flag_group(
+                            flags = [
+                                "-Wl,-rpath,$EXEC_ORIGIN/%{runtime_library_search_directories}",
+                            ],
+                            expand_if_true = "is_cc_test",
+                        ),
+                        flag_group(
+                            flags = [
+                                "-Wl,-rpath,$ORIGIN/%{runtime_library_search_directories}",
+                            ],
+                            expand_if_false = "is_cc_test",
+                        ),
+                    ],
+                    expand_if_available = "runtime_library_search_directories",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-L%{library_search_directories}"],
+                    iterate_over = "library_search_directories",
+                    expand_if_available = "library_search_directories",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    # This is actually a list of object files from the linkstamp steps
+                    flags = ["%{linkstamp_paths}"],
+                    iterate_over = "linkstamp_paths",
+                    expand_if_available = "linkstamp_paths",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["@%{thinlto_param_file}"],
+                    expand_if_available = "libraries_to_link",
+                    expand_if_true = "thinlto_param_file",
+                ),
+                flag_group(
+                    iterate_over = "libraries_to_link",
+                    flag_groups = [
+                        flag_group(
+                            flags = ["-Wl,--start-lib"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file_group",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-Wl,-whole-archive"],
+                            expand_if_true = "libraries_to_link.is_whole_archive",
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.object_files}"],
+                            iterate_over = "libraries_to_link.object_files",
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file_group",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "interface_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "static_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-l%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "dynamic_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-l:%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "versioned_dynamic_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-Wl,-no-whole-archive"],
+                            expand_if_true = "libraries_to_link.is_whole_archive",
+                        ),
+                        flag_group(
+                            flags = ["-Wl,--end-lib"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file_group",
+                            ),
+                        ),
+                    ],
+                    expand_if_available = "libraries_to_link",
+                ),
+            ],
+        ),
+
+        # Configure the header parsing and preprocessing.
+        flag_set(
+            actions = [ACTION_NAMES.cpp_header_parsing],
+            flags = ["-xc++-header", "-fsyntax-only"],
+            features = ["parse_headers"],
+        ),
+
+        # Note: user compile flags should be nearly last -- you probably
+        # don't want to put any more features after this!
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["%{user_compile_flags}"],
+                    iterate_over = "user_compile_flags",
+                    expand_if_available = "user_compile_flags",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["%{user_link_flags}"],
+                    iterate_over = "user_link_flags",
+                    expand_if_available = "user_link_flags",
+                ),
+            ],
+        ),
+        ## Options which need to go late -- after all the user options -- go here.
+        flag_set(
+            # One might hope that these options would only be needed for C++
+            # compiles. But, sadly, users compile ".c" files with custom
+            # copts=["-x", "c++"], and expect that to be able to find C++ stdlib
+            # headers. It might be worth pondering how blaze could support this sort
+            # of use-case better.
+            actions = preprocessor_compile_actions +
+                      [ACTION_NAMES.cc_flags_make_variable],
+            flags = [
+                "-isystem",
+                emscripten_root + "/system/lib/libc/musl/arch/emscripten",
+                "-isystem",
+                emscripten_root + "/system/lib/libc/musl/arch/js",
+                "-isystem",
+                emscripten_root + "/system/local/include",
+                "-isystem",
+                emscripten_root + "/system/include/compat",
+                "-isystem",
+                emscripten_root + "/system/include",
+                "-isystem",
+                emscripten_root + "/system/include/libcxx",
+                "-isystem",
+                emscripten_root + "/system/lib/libcxxabi/include",
+                "-isystem",
+                emscripten_root + "/system/lib/compiler-rt/include",
+                "-isystem",
+                emscripten_root + "/system/include/libc",
+                "-isystem",
+                emscripten_root + "/system/include/gfx",
+                "-isystem",
+                emscripten_root + "/system/include/SDL",
+                "-isystem",
+                emscripten_root + "/lib/clang/12.0.0/include",
+            ],
+        ),
+        # Inputs and outputs
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["-MD", "-MF", "%{dependency_file}"],
+                    expand_if_available = "dependency_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["-c", "%{source_file}"],
+                    expand_if_available = "source_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["-S"],
+                    expand_if_available = "output_assembly_file",
+                ),
+                flag_group(
+                    flags = ["-E"],
+                    expand_if_available = "output_preprocess_file",
+                ),
+                flag_group(
+                    flags = ["-o", "%{output_file}"],
+                    expand_if_available = "output_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-o", "%{output_execpath}"],
+                    expand_if_available = "output_execpath",
+                ),
+            ],
+        ),
+        # And finally, the params file!
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["@%{linker_param_file}"],
+                    expand_if_available = "linker_param_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_compile_actions,
+            flags = [
+                "-Wno-builtin-macro-redefined",
+                # Genrules may not escape quotes enough for these, so
+                # don't put them into $(CC_FLAGS):
+                '-D__DATE__="redacted"',
+                '-D__TIMESTAMP__="redacted"',
+                '-D__TIME__="redacted"',
+            ],
+        ),
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-Werror"],
+            features = ["wasm_warnings_as_errors"],
+        ),
+    ]
+
+    crosstool_default_env_sets = [
+        # Use llvm backend.  Off by default, enabled via --features=llvm_backend
+        env_set(
+            actions = all_compile_actions +
+                      all_link_actions +
+                      [ACTION_NAMES.cpp_link_static_library],
+            env_entries = [env_entry(key = "EMCC_WASM_BACKEND", value = "1")],
+            with_features = [with_feature_set(features = ["llvm_backend"])],
+        ),
+        # Debug compile and link. Off by default, enabled via --features=emcc_debug
+        env_set(
+            actions = all_compile_actions,
+            env_entries = [env_entry(key = "EMCC_DEBUG", value = "1")],
+            with_features = [with_feature_set(features = ["emcc_debug"])],
+        ),
+
+        # Debug only link step. Off by default, enabled via --features=emcc_debug_link
+        env_set(
+            actions = all_link_actions,
+            env_entries = [env_entry(key = "EMCC_DEBUG", value = "1")],
+            with_features = [
+                with_feature_set(features = ["emcc_debug"]),
+                with_feature_set(features = ["emcc_debug_link"]),
+            ],
+        ),
+    ]
+
+    crosstool_default_flags_feature = feature(
+        name = "crosstool_default_flags",
+        enabled = True,
+        flag_sets = crosstool_default_flag_sets,
+        env_sets = crosstool_default_env_sets,
+    )
+
+    features.append(crosstool_default_flags_feature)
+
+    cxx_builtin_include_directories = [
+        emscripten_version + "/system/lib/libc/musl/arch/emscripten",
+        emscripten_version + "/system/lib/libc/musl/arch/js",
+        emscripten_version + "/system/local/include",
+        emscripten_version + "/system/include/compat",
+        emscripten_version + "/system/include",
+        emscripten_version + "/system/include/libcxx",
+        emscripten_version + "/system/lib/compiler-rt/include",
+        emscripten_version + "/system/lib/libcxxabi/include",
+        emscripten_version + "/system/include/libc",
+        emscripten_version + "/system/include/gfx",
+        emscripten_version + "/system/include/SDL",
+        emscripten_version + "/lib/clang/12.0.0/include",
+    ]
+
+    artifact_name_patterns = []
+
+    make_variables = []
+
+    out = ctx.actions.declare_file(ctx.label.name)
+    ctx.actions.write(out, "Fake executable")
+    return [
+        cc_common.create_cc_toolchain_config_info(
+            ctx = ctx,
+            features = features,
+            action_configs = action_configs,
+            artifact_name_patterns = artifact_name_patterns,
+            cxx_builtin_include_directories = cxx_builtin_include_directories,
+            toolchain_identifier = toolchain_identifier,
+            host_system_name = host_system_name,
+            target_system_name = target_system_name,
+            target_cpu = target_cpu,
+            target_libc = target_libc,
+            compiler = compiler,
+            abi_version = abi_version,
+            abi_libc_version = abi_libc_version,
+            tool_paths = tool_paths,
+            make_variables = make_variables,
+            builtin_sysroot = builtin_sysroot,
+            cc_target_os = cc_target_os,
+        ),
+        DefaultInfo(
+            executable = out,
+        ),
+    ]
+
+emscripten_cc_toolchain_config_rule = rule(
+    implementation = _impl,
+    attrs = {
+        "cpu": attr.string(mandatory = True, values = ["asmjs", "wasm"]),
+        "emscripten_version": attr.string(mandatory = True),
+    },
+    provides = [CcToolchainConfigInfo],
+    executable = True,
+)
diff --git a/bazel/emscripten_toolchain/emar.sh b/bazel/emscripten_toolchain/emar.sh
new file mode 100755
index 0000000..965442e
--- /dev/null
+++ b/bazel/emscripten_toolchain/emar.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source emscripten_toolchain/env.sh
+
+exec python3 $EMSCRIPTEN/emar.py "$@"
diff --git a/bazel/emscripten_toolchain/emcc.sh b/bazel/emscripten_toolchain/emcc.sh
new file mode 100755
index 0000000..66d00b6
--- /dev/null
+++ b/bazel/emscripten_toolchain/emcc.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source emscripten_toolchain/env.sh
+
+exec python3 external/emscripten/emscripten/emcc.py "$@"
diff --git a/bazel/emscripten_toolchain/emcc_link.sh b/bazel/emscripten_toolchain/emcc_link.sh
new file mode 100755
index 0000000..9d0f8e2
--- /dev/null
+++ b/bazel/emscripten_toolchain/emcc_link.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source emscripten_toolchain/env.sh
+
+exec python3 emscripten_toolchain/link_wrapper.py "$@"
diff --git a/bazel/emscripten_toolchain/emscripten.BUILD b/bazel/emscripten_toolchain/emscripten.BUILD
new file mode 100644
index 0000000..6f11852
--- /dev/null
+++ b/bazel/emscripten_toolchain/emscripten.BUILD
@@ -0,0 +1,6 @@
+package(default_visibility = ['//visibility:public'])
+
+filegroup(
+    name = "all",
+    srcs = glob(["**"]),
+)
diff --git a/bazel/emscripten_toolchain/emscripten_config b/bazel/emscripten_toolchain/emscripten_config
new file mode 100644
index 0000000..4a77344
--- /dev/null
+++ b/bazel/emscripten_toolchain/emscripten_config
@@ -0,0 +1,9 @@
+import os
+
+ROOT_DIR = os.environ["ROOT_DIR"]
+EMSCRIPTEN_ROOT = os.environ["EMSCRIPTEN"]
+
+LLVM_ROOT = ROOT_DIR + "/external/emscripten/bin"
+EMSCRIPTEN_NATIVE_OPTIMIZER = LLVM_ROOT + "/optimizer"
+NODE_JS = ROOT_DIR + "/external/nodejs_linux_amd64/bin/node"
+BINARYEN_ROOT = ROOT_DIR + "/external/emscripten"
diff --git a/bazel/emscripten_toolchain/env.sh b/bazel/emscripten_toolchain/env.sh
new file mode 100755
index 0000000..005fa87
--- /dev/null
+++ b/bazel/emscripten_toolchain/env.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+export ROOT_DIR=`(pwd -P)`
+export EMSCRIPTEN=${ROOT_DIR}/external/emscripten/emscripten
+
+export EM_CONFIG=${ROOT_DIR}/emscripten_toolchain/emscripten_config
+export EM_CACHE=${ROOT_DIR}/emscripten_toolchain/cache
diff --git a/bazel/emscripten_toolchain/link_wrapper.py b/bazel/emscripten_toolchain/link_wrapper.py
new file mode 100644
index 0000000..a746ae8
--- /dev/null
+++ b/bazel/emscripten_toolchain/link_wrapper.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+"""wrapper around emcc link step.
+
+This wrapper currently serves the following purposes.
+
+1. Ensures we always link to file with .js extension. The upstream default
+   it to link to an llvm bitcode file which is never (AFAICT) want to do that.
+
+2. When building with --config=wasm the final output is multiple files, usually
+   at least one .js and one .wasm file. Since the cc_binary link step only
+   allows a single output, we must tar up the outputs into a single file.
+
+3. Add quotes around arguments that need them in the response file to work
+   around a bazel quirk.
+"""
+
+from __future__ import print_function
+
+import os
+import subprocess
+import sys
+
+# Only argument should be @path/to/parameter/file
+assert sys.argv[1][0] == '@'
+param_filename = sys.argv[1][1:]
+param_file_args = [l.strip() for l in open(param_filename, 'r').readlines()]
+
+output_index = param_file_args.index('-o') + 1
+orig_output = js_output = param_file_args[output_index]
+outdir = os.path.dirname(orig_output)
+
+# google3-only(TODO(b/139440956): Default to False once the bug is fixed)
+replace_response_file = any(' ' in a for a in param_file_args)
+
+if not os.path.splitext(orig_output)[1]:
+  js_output = orig_output + '.js'
+  param_file_args[output_index] = js_output
+  replace_response_file = True
+
+# Re-write response file if needed.
+if replace_response_file:
+  new_param_filename = param_filename + '.modified'
+  with open(new_param_filename, 'w') as f:
+    for param in param_file_args:
+      if ' ' in param:
+        f.write('"%s"' % param)
+      else:
+        f.write(param)
+      f.write('\n')
+  sys.argv[1] = '@' + new_param_filename
+
+emcc_py = os.path.join(os.environ['EMSCRIPTEN'], 'emcc.py')
+rtn = subprocess.call(['python3', emcc_py] + sys.argv[1:])
+if rtn != 0:
+  sys.exit(1)
+
+js_name = os.path.basename(js_output)
+base_name = os.path.splitext(js_name)[0]
+
+files = []
+extensions = [
+    '.js',
+    '.wasm',
+    '.wasm.map',
+    '.js.mem',
+    '.fetch.js',
+    '.worker.js',
+    '.data',
+    '.js.symbols',
+    '.wasm.debug.wasm'
+]
+
+for ext in extensions:
+  filename = base_name + ext
+  if os.path.exists(os.path.join(outdir, filename)):
+    files.append(filename)
+
+wasm_base = os.path.join(outdir, base_name + '.wasm')
+if os.path.exists(wasm_base + '.debug.wasm') and os.path.exists(wasm_base):
+  # If we have a .wasm.debug.wasm file and a .wasm file, we need to rewrite the
+  # section in the .wasm file that refers to it. The path that's in there
+  # is the blaze output path; we want it to be just the filename.
+
+  llvm_objcopy = os.path.join(
+      os.environ['EMSCRIPTEN'], 'llvm-bin/llvm-objcopy')
+  # First, check to make sure the .wasm file has the header that needs to be
+  # rewritten.
+  rtn = subprocess.call([
+      llvm_objcopy,
+      '--dump-section=external_debug_info=/dev/null',
+      wasm_base], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  if rtn == 0:
+    # If llvm-objcopy did not return an error, the external_debug_info section
+    # must exist, so we're good to continue.
+
+    # Next we need to convert length of the filename to LEB128.
+    # Start by converting the length of the filename to a bit string.
+    bit_string = '{0:b}'.format(len(base_name + '.wasm.debug.wasm'))
+
+    # Pad the bit string with 0s so that its length is a multiple of 7.
+    while len(bit_string) % 7 != 0:
+      bit_string = '0' + bit_string
+
+    # Break up our bit string into chunks of 7.
+    # We do this backwards because the final format is little-endian.
+    final_bytes = bytearray()
+    for i in reversed(range(0, len(bit_string), 7)):
+      binary_part = bit_string[i:i + 7]
+      if i != 0:
+        # Every chunk except the last one needs to be prepended with '1'.
+        # The length of each chunk is 7, so that one has an implicit '0'.
+        binary_part = '1' + binary_part
+      final_bytes.append(int(binary_part, 2))
+    # Finally, add the actual filename.
+    final_bytes.extend(base_name + '.wasm.debug.wasm')
+
+    # Write our length + filename bytes to a temp file.
+    with open('debugsection.tmp', 'wb+') as f:
+      f.write(final_bytes)
+      f.close()
+
+    # First delete the old section.
+    subprocess.check_call([
+        llvm_objcopy,
+        wasm_base,
+        '--remove-section=external_debug_info'])
+    # Rewrite section with the new size and filename from the temp file.
+    subprocess.check_call([
+        llvm_objcopy,
+        wasm_base,
+        '--add-section=external_debug_info=debugsection.tmp'])
+
+# If we have more than one output file then create tarball
+if len(files) > 1:
+  cmd = ['tar', 'cf', 'tmp.tar'] + files
+  subprocess.check_call(cmd, cwd=outdir)
+  os.rename(os.path.join(outdir, 'tmp.tar'), orig_output)
+elif len(files) == 1:
+  # Otherwise, if only have a single output than move it to the expected name
+  if files[0] != os.path.basename(orig_output):
+    os.rename(os.path.join(outdir, files[0]), orig_output)
+else:
+  print('emcc.py did not appear to output any known files!')
+  sys.exit(1)
+
+sys.exit(0)
diff --git a/bazel/emscripten_toolchain/wasm_binary.py b/bazel/emscripten_toolchain/wasm_binary.py
new file mode 100644
index 0000000..6bb30db
--- /dev/null
+++ b/bazel/emscripten_toolchain/wasm_binary.py
@@ -0,0 +1,84 @@
+"""Unpackages a bazel emscripten archive for use in a bazel BUILD rule.
+
+This script will take a tar archive containing the output of the emscripten
+toolchain. This file contains any output files produced by a wasm_cc_binary or a
+cc_binary built with --config=wasm. The files are extracted into the given
+output path.
+
+The name of archive is expected to be of the format `foo` or `foo.XXX` and
+the contents are expected to be foo.js and foo.wasm.
+
+Several optional files may also be in the archive, including but not limited to
+foo.js.mem, pthread-main.js, and foo.wasm.map.
+
+If the file is not a tar archive, the passed file will simply be copied to its
+destination.
+
+This script and its accompanying Bazel rule should allow you to extract a
+WebAssembly binary into a larger web application.
+"""
+
+import os
+import subprocess
+import sys
+
+from absl import app
+from absl import flags
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('archive', None, 'The the archive to extract from.')
+flags.DEFINE_string('output_path', None, 'The path to extract into.')
+
+
+def ensure(f):
+  if not os.path.exists(f):
+    with open(f, 'w'):
+      pass
+
+
+def check(f):
+  if not os.path.exists(f):
+    raise Exception('Expected file in archive: %s' % f)
+
+
+def main(argv):
+  basename = os.path.basename(FLAGS.archive)
+  stem = basename.split('.')[0]
+
+  # Check the type of the input file
+  mimetype_bytes = subprocess.check_output(['file', '-Lib', FLAGS.archive])
+  mimetype = mimetype_bytes.decode(sys.stdout.encoding)
+
+  # If we have a tar, extract all files. If we have just a single file, copy it.
+  if 'tar' in mimetype:
+    subprocess.check_call(
+        ['tar', 'xf', FLAGS.archive, '-C', FLAGS.output_path])
+  elif 'binary' in mimetype:
+    subprocess.check_call([
+        'cp',
+        FLAGS.archive,
+        os.path.join(FLAGS.output_path, stem + '.wasm')])
+  elif 'text' in mimetype:
+    subprocess.check_call([
+        'cp',
+        FLAGS.archive,
+        os.path.join(FLAGS.output_path, stem + '.js')])
+  else:
+    subprocess.check_call(['cp', FLAGS.archive, FLAGS.output_path])
+
+  # At least one of these two files should exist at this point.
+  ensure(os.path.join(FLAGS.output_path, stem + '.js'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.wasm'))
+
+  # And can optionally contain these extra files.
+  ensure(os.path.join(FLAGS.output_path, stem + '.wasm.map'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.worker.js'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.js.mem'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.data'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.fetch.js'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.js.symbols'))
+  ensure(os.path.join(FLAGS.output_path, stem + '.wasm.debug.wasm'))
+
+
+if __name__ == '__main__':
+  app.run(main)
diff --git a/bazel/emscripten_toolchain/wasm_cc_binary.bzl b/bazel/emscripten_toolchain/wasm_cc_binary.bzl
new file mode 100644
index 0000000..01780e2
--- /dev/null
+++ b/bazel/emscripten_toolchain/wasm_cc_binary.bzl
@@ -0,0 +1,150 @@
+"""wasm_cc_binary rule for compiling C++ targets to WebAssembly.
+"""
+
+def _wasm_transition_impl(settings, attr):
+    _ignore = (settings, attr)
+
+    features = list(settings["//command_line_option:features"])
+    linkopts = list(settings["//command_line_option:linkopt"])
+
+    if attr.threads == "emscripten":
+        # threads enabled
+        features.append("use_pthreads")
+    elif attr.threads == "off":
+        # threads disabled
+        features.append("-use_pthreads")
+
+    if attr.exit_runtime == True:
+        features.append("exit_runtime")
+
+    if attr.backend == "llvm":
+        features.append("llvm_backend")
+    elif attr.backend == "emscripten":
+        features.append("-llvm_backend")
+
+    if attr.simd:
+        features.append("wasm_simd")
+
+    return {
+        "//command_line_option:compiler": "emscripten",
+        "//command_line_option:crosstool_top": "//emscripten_toolchain:everything",
+        "//command_line_option:cpu": "wasm",
+        "//command_line_option:features": features,
+        "//command_line_option:dynamic_mode": "off",
+        "//command_line_option:linkopt": linkopts,
+        "//command_line_option:platforms": [],
+        "//command_line_option:custom_malloc": "//emscripten_toolchain:malloc",
+    }
+
+_wasm_transition = transition(
+    implementation = _wasm_transition_impl,
+    inputs = [
+        "//command_line_option:features",
+        "//command_line_option:linkopt",
+    ],
+    outputs = [
+        "//command_line_option:compiler",
+        "//command_line_option:cpu",
+        "//command_line_option:crosstool_top",
+        "//command_line_option:features",
+        "//command_line_option:dynamic_mode",
+        "//command_line_option:linkopt",
+        "//command_line_option:platforms",
+        "//command_line_option:custom_malloc",
+    ],
+)
+
+def _wasm_binary_impl(ctx):
+    cc_target = ctx.attr.cc_target[0]
+
+    args = [
+        "--output_path={}".format(ctx.outputs.loader.dirname),
+    ] + [
+        ctx.expand_location("--archive=$(location {})".format(
+            cc_target.label,
+        ), [cc_target]),
+    ]
+    outputs = [
+        ctx.outputs.loader,
+        ctx.outputs.wasm,
+        ctx.outputs.map,
+        ctx.outputs.mem,
+        ctx.outputs.fetch,
+        ctx.outputs.worker,
+        ctx.outputs.data,
+        ctx.outputs.symbols,
+        ctx.outputs.dwarf,
+    ]
+
+    ctx.actions.run(
+        inputs = ctx.files.cc_target,
+        outputs = outputs,
+        arguments = args,
+        executable = ctx.executable._wasm_binary_extractor,
+    )
+
+    return DefaultInfo(
+        files = depset(outputs),
+        # This is needed since rules like web_test usually have a data
+        # dependency on this target.
+        data_runfiles = ctx.runfiles(transitive_files = depset(outputs)),
+    )
+
+def _wasm_binary_outputs(name, cc_target):
+    basename = cc_target.name
+    basename = basename.split(".")[0]
+    outputs = {
+        "loader": "{}/{}.js".format(name, basename),
+        "wasm": "{}/{}.wasm".format(name, basename),
+        "map": "{}/{}.wasm.map".format(name, basename),
+        "mem": "{}/{}.js.mem".format(name, basename),
+        "fetch": "{}/{}.fetch.js".format(name, basename),
+        "worker": "{}/{}.worker.js".format(name, basename),
+        "data": "{}/{}.data".format(name, basename),
+        "symbols": "{}/{}.js.symbols".format(name, basename),
+        "dwarf": "{}/{}.wasm.debug.wasm".format(name, basename),
+    }
+
+    return outputs
+
+# Wraps a C++ Blaze target, extracting the appropriate files.
+#
+# This rule will transition to the emscripten toolchain in order
+# to build the the cc_target as a WebAssembly binary.
+#
+# Args:
+#   name: The name of the rule.
+#   cc_target: The cc_binary or cc_library to extract files from.
+wasm_cc_binary = rule(
+    implementation = _wasm_binary_impl,
+    attrs = {
+        "backend": attr.string(
+            default = "_default",
+            values = ["_default", "emscripten", "llvm"],
+        ),
+        "cc_target": attr.label(
+            cfg = _wasm_transition,
+            mandatory = True,
+        ),
+        "exit_runtime": attr.bool(
+            default = False,
+        ),
+        "threads": attr.string(
+            default = "_default",
+            values = ["_default", "emscripten", "off"],
+        ),
+        "simd": attr.bool(
+            default = False,
+        ),
+        "_allowlist_function_transition": attr.label(
+            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+        ),
+        "_wasm_binary_extractor": attr.label(
+            executable = True,
+            allow_files = True,
+            cfg = "exec",
+            default = Label("//emscripten_toolchain:wasm_binary"),
+        ),
+    },
+    outputs = _wasm_binary_outputs,
+)
diff --git a/bazel/emscripten_toolchain/wasm_rules.bzl b/bazel/emscripten_toolchain/wasm_rules.bzl
new file mode 100644
index 0000000..1c1c409
--- /dev/null
+++ b/bazel/emscripten_toolchain/wasm_rules.bzl
@@ -0,0 +1,6 @@
+"""Rules related to C++ and WebAssembly.
+"""
+
+load("//emscripten_toolchain:wasm_cc_binary.bzl", _wasm_cc_binary = "wasm_cc_binary")
+
+wasm_cc_binary = _wasm_cc_binary
diff --git a/bazel/hello-world/BUILD b/bazel/hello-world/BUILD
new file mode 100644
index 0000000..d0ef7cd
--- /dev/null
+++ b/bazel/hello-world/BUILD
@@ -0,0 +1,12 @@
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
+
+cc_binary(
+    name = "hello-world",
+    srcs = ["hello-world.cc"],
+)
+
+wasm_cc_binary(
+    name = "hello-world-wasm",
+    cc_target = ":hello-world",
+)
diff --git a/bazel/hello-world/hello-world.cc b/bazel/hello-world/hello-world.cc
new file mode 100644
index 0000000..ee72c53
--- /dev/null
+++ b/bazel/hello-world/hello-world.cc
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main(int argc, char** argv) {
+  std::cout << "hello world!" << std::endl;
+  return 0;
+}
diff --git a/scripts/test_bazel.sh b/scripts/test_bazel.sh
new file mode 100755
index 0000000..f2a5e3c
--- /dev/null
+++ b/scripts/test_bazel.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+echo "test bazel"
+
+set -x
+set -e
+
+cd bazel
+bazel build //hello-world:hello-world-wasm
diff --git a/scripts/update_bazel_workspace.sh b/scripts/update_bazel_workspace.sh
new file mode 100755
index 0000000..fb58f04
--- /dev/null
+++ b/scripts/update_bazel_workspace.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# This script will update emsdk/bazel/WORKSPACE to the latest version of
+# emscripten. It reads emsdk/emscripten-releases-tags.txt to get the latest
+# version number. Then, it downloads the prebuilts for that version and computes
+# the sha256sum for the archive. It then puts all this information into the
+# emsdk/bazel/WORKSPACE file.
+
+ERR=0
+# Attempt to change to the emsdk root directory
+cd $(dirname $0)/..
+
+# If the previous command succeeded. We are in the emsdk root. Check to make
+# sure the files and directories we need are present.
+if [[ $? = 0 ]]; then
+  if [[ ! -f emscripten-releases-tags.txt ]]; then
+    echo "Cannot find emscripten-releases-tags.txt."
+    ERR=1
+  fi
+
+  if [[ ! -d bazel ]]; then
+    echo "Cannot find the bazel directory."
+    ERR=1
+  elif [[ ! -f bazel/WORKSPACE ]]; then
+    echo "Cannot find bazel/WORKSPACE."
+    ERR=1
+  fi
+else
+  ERR=1
+fi
+
+if [[ $ERR = 1 ]]; then
+  echo "Unable to cd into the emsdk root directory."
+  exit 1
+fi
+
+URL1=https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/
+URL2=/wasm-binaries.tbz2
+
+# Get the latest version number from emscripten-releases-tag.txt.
+VER=$(grep -oP '(?<=latest\": \")([\d\.]+)(?=\")' \
+        emscripten-releases-tags.txt \
+      | sed --expression "s/\./\\\./g")
+# Based on the latest version number, get the commit hash for that version.
+HASH=$(grep "${VER}" emscripten-releases-tags.txt \
+      | grep -v latest \
+      | cut -f4 -d\")
+# Download and compute the sha256sum for the archive with the prebuilts.
+SHA=$(curl "${URL1}${HASH}${URL2}" 2>/dev/null \
+      | sha256sum \
+      | awk '{print $1}')
+# Get the line number on which the sha256 sum lives for emscripten.
+# This will always be one line after the name of the rule.
+SHALINE=$(($(grep -n 'name = "emscripten"' bazel/WORKSPACE \
+      | sed 's/^\([[:digit:]]*\).*$/\1/')+1))
+
+# Insert the new commit hash into the url.
+sed -i "s!\(${URL1}\)\([[:alnum:]]*\)\(${URL2}\)!\1${HASH}\3!" bazel/WORKSPACE
+# Insert the new version number.
+sed -i "s!\(# emscripten \)\(.*\)!\1${VER}!" bazel/WORKSPACE
+# Insert the new sha256 sum.
+sed -i "${SHALINE}s!\"[[:alnum:]]*\"!\"${SHA}\"!" bazel/WORKSPACE
+
+echo "Done!"