Repository: bytecodealliance/wasmtime
Pull request #8229 (2024-03-24 18:53:54)

Use a multi-threaded Tokio runtime as a fallback
This commit fixes a bug that was introduced in #8190 and brought up on [Zulip]. In #8190 there was a subtle change in how Tokio and the CLI are managed, namely that a Tokio runtime is installed at the start of the CLI to avoid entering/exiting the runtime on each blocking call. This had a small performance improvement relative to entering/exiting on each blocking call. This meant, though, that calls previously blocked with `Runtime::block_on` and now block with `Handle::block_on`. The [documentation of `Handle::block_on`][doc] has a clause that says that for single-threaded runtimes I/O and timers won't work.

To fix this issue I've switch the fallback runtime to a multi-threaded runtime to ensure that I/O and timers continue to work.

[Zulip]: https://bytecodealliance.zulipchat.com/#narrow/stream/219900-wasi/topic/Wasi-http.20requests.20hang/near/429187256
[doc]: https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.html#method.block_on

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 70e96cf2e..a0ce5f1a5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -820,7 +820,6 @@ dependencies = [
  "cranelift-native",
  "cranelift-object",
  "cranelift-reader",
- "cranelift-wasm",
  "filecheck",
  "fxhash",
  "indicatif",
@@ -831,11 +830,9 @@ dependencies = [
  "serde",
  "similar",
  "target-lexicon",
- "termcolor",
  "thiserror",
  "toml",
  "walkdir",
- "wat",
 ]
 
 [[package]]
diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml
index 31dd54578..8a86ab8cf 100644
--- a/cranelift/Cargo.toml
+++ b/cranelift/Cargo.toml
@@ -28,7 +28,6 @@ cranelift-entity = { workspace = true }
 cranelift-interpreter = { workspace = true }
 cranelift-reader = { workspace = true }
 cranelift-frontend = { workspace = true }
-cranelift-wasm = { workspace = true, optional = true }
 cranelift-native = { workspace = true }
 cranelift-filetests = { workspace = true }
 cranelift-module = { workspace = true }
@@ -37,9 +36,7 @@ cranelift-jit = { workspace = true }
 cranelift = { workspace = true }
 filecheck = { workspace = true }
 log = { workspace = true }
-termcolor = "1.1.2"
 capstone = { workspace = true, optional = true }
-wat = { workspace = true, optional = true }
 target-lexicon = { workspace = true, features = ["std"] }
 pretty_env_logger = { workspace = true }
 rayon = { version = "1", optional = true }
@@ -57,8 +54,7 @@ fxhash = "0.2.1"
 regalloc2 = { workspace = true, features = ["trace-log"] }
 
 [features]
-default = ["disas", "wasm", "cranelift-codegen/all-arch", "cranelift-codegen/trace-log", "souper-harvest"]
+default = ["disas", "cranelift-codegen/all-arch", "cranelift-codegen/trace-log", "souper-harvest"]
 disas = ["capstone"]
-wasm = ["wat", "cranelift-wasm"]
 souper-harvest = ["cranelift-codegen/souper-harvest", "rayon"]
 all-arch = ["cranelift-codegen/all-arch"]
diff --git a/cranelift/fuzzgen/src/cranelift_arbitrary.rs b/cranelift/fuzzgen/src/cranelift_arbitrary.rs
index 77820d0fb..8b93e021d 100644
--- a/cranelift/fuzzgen/src/cranelift_arbitrary.rs
+++ b/cranelift/fuzzgen/src/cranelift_arbitrary.rs
@@ -64,11 +64,8 @@ impl<'a> CraneliftArbitrary for &mut Unstructured<'a> {
             allowed_callconvs.push(CallConv::Tail);
         }
 
-        // The winch calling convention is supposed to work on x64 and aarch64
-        if matches!(
-            architecture,
-            Architecture::X86_64 | Architecture::Aarch64(_)
-        ) {
+        // The winch calling convention is supposed to work on x64.
+        if matches!(architecture, Architecture::X86_64) {
             allowed_callconvs.push(CallConv::Winch);
         }
 
diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs
index 12b740e09..da3e164bc 100644
--- a/cranelift/src/clif-util.rs
+++ b/cranelift/src/clif-util.rs
@@ -13,9 +13,6 @@ mod utils;
 #[cfg(feature = "souper-harvest")]
 mod souper_harvest;
 
-#[cfg(feature = "wasm")]
-mod wasm;
-
 /// Cranelift code generator utility.
 #[derive(Parser)]
 enum Commands {
@@ -28,11 +25,6 @@ enum Commands {
     Pass(PassOptions),
     Bugpoint(bugpoint::Options),
 
-    #[cfg(feature = "wasm")]
-    Wasm(wasm::Options),
-    #[cfg(not(feature = "wasm"))]
-    Wasm(CompiledWithoutSupportOptions),
-
     #[cfg(feature = "souper-harvest")]
     SouperHarvest(souper_harvest::Options),
     #[cfg(not(feature = "souper-harvest"))]
@@ -92,11 +84,6 @@ fn main() -> anyhow::Result<()> {
         Commands::Compile(c) => compile::run(&c)?,
         Commands::Bugpoint(b) => bugpoint::run(&b)?,
 
-        #[cfg(feature = "wasm")]
-        Commands::Wasm(w) => wasm::run(&w)?,
-        #[cfg(not(feature = "wasm"))]
-        Commands::Wasm(_) => anyhow::bail!("Error: clif-util was compiled without wasm support."),
-
         #[cfg(feature = "souper-harvest")]
         Commands::SouperHarvest(s) => souper_harvest::run(&s)?,
         #[cfg(not(feature = "souper-harvest"))]
diff --git a/cranelift/src/souper_harvest.rs b/cranelift/src/souper_harvest.rs
index 50b55c2ad..6d74bb63b 100644
--- a/cranelift/src/souper_harvest.rs
+++ b/cranelift/src/souper_harvest.rs
@@ -1,17 +1,16 @@
+use crate::utils::{iterate_files, read_to_string};
 use anyhow::{Context as _, Result};
 use clap::Parser;
 use cranelift_codegen::control::ControlPlane;
+use cranelift_codegen::ir::Function;
 use cranelift_codegen::Context;
 use cranelift_reader::parse_sets_and_triple;
-use cranelift_wasm::DummyEnvironment;
-use rayon::iter::{IntoParallelIterator, ParallelIterator};
+use rayon::iter::{IntoParallelIterator, ParallelBridge, ParallelIterator};
 use std::collections::HashSet;
 use std::io::Write;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 use std::{fs, io};
 
-static WASM_MAGIC: &[u8] = &[0x00, 0x61, 0x73, 0x6D];
-
 /// Harvest candidates for superoptimization from a Wasm or Clif file.
 ///
 /// Candidates are emitted in Souper's text format:
@@ -19,7 +18,7 @@ static WASM_MAGIC: &[u8] = &[0x00, 0x61, 0x73, 0x6D];
 #[derive(Parser)]
 pub struct Options {
     /// Specify an input file to be used. Use '-' for stdin.
-    input: PathBuf,
+    input: Vec<PathBuf>,
 
     /// Specify the directory where harvested left-hand side files should be
     /// written to.
@@ -48,15 +47,6 @@ pub fn run(options: &Options) -> Result<()> {
         anyhow::bail!("`souper-harvest` requires a target isa");
     }
 
-    let stdin = io::stdin();
-    let mut input: Box<dyn io::BufRead> = if options.input == Path::new("-") {
-        Box::new(stdin.lock())
-    } else {
-        Box::new(io::BufReader::new(
-            fs::File::open(&options.input).context("failed to open input file")?,
-        ))
-    };
-
     match fs::create_dir_all(&options.output_dir) {
         Ok(_) => {}
         Err(e)
@@ -77,26 +67,6 @@ pub fn run(options: &Options) -> Result<()> {
         }
     }
 
-    let mut contents = vec![];
-    input
-        .read_to_end(&mut contents)
-        .context("failed to read input file")?;
-
-    let funcs = if &contents[..WASM_MAGIC.len()] == WASM_MAGIC {
-        let mut dummy_environ = DummyEnvironment::new(fisa.isa.unwrap().frontend_config());
-        cranelift_wasm::translate_module(&contents, &mut dummy_environ)
-            .context("failed to translate Wasm module to clif")?;
-        dummy_environ
-            .info
-            .function_bodies
-            .iter()
-            .map(|(_, f)| f.clone())
-            .collect()
-    } else {
-        let contents = String::from_utf8(contents)?;
-        cranelift_reader::parse_functions(&contents)?
-    };
-
     let (send, recv) = std::sync::mpsc::channel::<String>();
 
     let writing_thread = std::thread::spawn({
@@ -128,20 +98,31 @@ pub fn run(options: &Options) -> Result<()> {
         }
     });
 
-    funcs
-        .into_par_iter()
-        .map_with(send, move |send, func| {
-            let mut ctx = Context::new();
-            ctx.func = func;
+    iterate_files(&options.input)
+        .par_bridge()
+        .flat_map(|path| {
+            parse_input(path)
+                .unwrap_or_else(|e| {
+                    println!("{e:?}");
+                    Vec::new()
+                })
+                .into_par_iter()
+        })
+        .map_init(
+            move || (send.clone(), Context::new()),
+            move |(send, ctx), func| {
+                ctx.clear();
+                ctx.func = func;
 
-            ctx.optimize(fisa.isa.unwrap(), &mut ControlPlane::default())
-                .context("failed to run optimizations")?;
+                ctx.optimize(fisa.isa.unwrap(), &mut ControlPlane::default())
+                    .context("failed to run optimizations")?;
 
-            ctx.souper_harvest(send)
-                .context("failed to run souper harvester")?;
+                ctx.souper_harvest(send)
+                    .context("failed to run souper harvester")?;
 
-            Ok(())
-        })
+                Ok(())
+            },
+        )
         .collect::<Result<()>>()?;
 
     match writing_thread.join() {
@@ -151,3 +132,10 @@ pub fn run(options: &Options) -> Result<()> {
 
     Ok(())
 }
+
+fn parse_input(path: PathBuf) -> Result<Vec<Function>> {
+    let contents = read_to_string(&path)?;
+    let funcs = cranelift_reader::parse_functions(&contents)
+        .with_context(|| format!("parse error in {}", path.display()))?;
+    Ok(funcs)
+}
diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs
deleted file mode 100644
index d1eb99c7b..000000000
--- a/cranelift/src/wasm.rs
+++ /dev/null
@@ -1,321 +0,0 @@
-//! CLI tool to use the functions provided by the [cranelift-wasm](../cranelift_wasm/index.html)
-//! crate.
-//!
-//! Reads Wasm binary/text files, translates the functions' code to Cranelift IR.
-
-use crate::disasm::print_all;
-use anyhow::{Context as _, Result};
-use clap::Parser;
-use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error};
-use cranelift_codegen::settings::FlagsOrIsa;
-use cranelift_codegen::timing;
-use cranelift_codegen::Context;
-use cranelift_entity::EntityRef;
-use cranelift_reader::parse_sets_and_triple;
-use cranelift_wasm::{translate_module, DummyEnvironment, FuncIndex};
-use std::io::Read;
-use std::path::Path;
-use std::path::PathBuf;
-use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
-
-/// For verbose printing: only print if the `$x` expression is true.
-macro_rules! vprintln {
-    ($x: expr, $($tts:tt)*) => {
-        if $x {
-            println!($($tts)*);
-        }
-    };
-}
-/// For verbose printing: prints in color if the `$x` expression is true.
-macro_rules! vcprintln {
-    ($x: expr, $use_color: expr, $term: ident, $color: expr, $($tts:tt)*) => {
-        if $x {
-            if $use_color {
-                $term.set_color(ColorSpec::new().set_fg(Some($color)))?;
-            }
-            println!($($tts)*);
-            if $use_color {
-                $term.reset()?;
-            }
-        }
-    };
-}
-/// For verbose printing: prints in color (without an appended newline) if the `$x` expression is true.
-macro_rules! vcprint {
-    ($x: expr, $use_color: expr, $term: ident, $color: expr, $($tts:tt)*) => {
-        if $x {
-            if $use_color {
-                $term.set_color(ColorSpec::new().set_fg(Some($color)))?;
-            }
-            print!($($tts)*);
-            if $use_color {
-                $term.reset()?;
-            }
-        }
-    };
-}
-
-/// Compiles Wasm binary/text into Cranelift IR and then into target language
-#[derive(Parser)]
-pub struct Options {
-    /// Be more verbose
-    #[arg(short, long)]
-    verbose: bool,
-
-    /// Print the resulting Cranelift IR
-    #[arg(short)]
-    print: bool,
-
-    /// Print pass timing report
-    #[arg(short = 'T')]
-    report_times: bool,
-
-    /// Print machine code disassembly
-    #[arg(short = 'D', long)]
-    disasm: bool,
-
-    /// Configure Cranelift settings
-    #[arg(long = "set")]
-    settings: Vec<String>,
-
-    /// Specify the Cranelift target
-    #[arg(long = "target")]
-    target: String,
-
-    /// Specify an input file to be used. Use '-' for stdin.
-    files: Vec<PathBuf>,
-
-    /// Print bytecode size
-    #[arg(short = 'X')]
-    print_size: bool,
-
-    /// Just decode Wasm into Cranelift IR, don't compile it to native code
-    #[arg(short = 't')]
-    just_decode: bool,
-
-    /// Just checks the correctness of Cranelift IR translated from Wasm
-    #[arg(short = 'c')]
-    check_translation: bool,
-
-    /// Use colors in output? [options: auto/never/always; default: auto]
-    #[arg(long = "color", default_value("auto"))]
-    color: ColorOpt,
-}
-
-#[derive(PartialEq, Eq, Clone)]
-enum ColorOpt {
-    Auto,
-    Never,
-    Always,
-}
-
-impl std::str::FromStr for ColorOpt {
-    type Err = String;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let s = s.to_lowercase();
-        match s.as_str() {
-            "auto" => Ok(ColorOpt::Auto),
-            "never" => Ok(ColorOpt::Never),
-            "always" => Ok(ColorOpt::Always),
-            _ => Err(format!("expected auto/never/always, found: {}", s)),
-        }
-    }
-}
-
-pub fn run(options: &Options) -> Result<()> {
-    let parsed = parse_sets_and_triple(&options.settings, &options.target)?;
-    for path in &options.files {
-        let name = String::from(path.as_os_str().to_string_lossy());
-        handle_module(options, path, &name, parsed.as_fisa())?;
-    }
-    Ok(())
-}
-
-fn handle_module(options: &Options, path: &Path, name: &str, fisa: FlagsOrIsa) -> Result<()> {
-    let color_choice = match options.color {
-        ColorOpt::Auto => ColorChoice::Auto,
-        ColorOpt::Always => ColorChoice::Always,
-        ColorOpt::Never => ColorChoice::Never,
-    };
-    let mut terminal = StandardStream::stdout(color_choice);
-    let use_color = terminal.supports_color() && options.color == ColorOpt::Auto
-        || options.color == ColorOpt::Always;
-    vcprint!(
-        options.verbose,
-        use_color,
-        terminal,
-        Color::Yellow,
-        "Handling: "
-    );
-    vprintln!(options.verbose, "\"{}\"", name);
-    vcprint!(
-        options.verbose,
-        use_color,
-        terminal,
-        Color::Magenta,
-        "Translating... "
-    );
-
-    let module_binary = if path.to_str() == Some("-") {
-        let stdin = std::io::stdin();
-        let mut buf = Vec::new();
-        stdin
-            .lock()
-            .read_to_end(&mut buf)
-            .context("failed to read stdin")?;
-        wat::parse_bytes(&buf)?.into()
-    } else {
-        wat::parse_file(path)?
-    };
-
-    let isa = match fisa.isa {
-        Some(isa) => isa,
-        None => {
-            anyhow::bail!("Error: the wasm command requires an explicit isa.");
-        }
-    };
-
-    let mut dummy_environ = DummyEnvironment::new(isa.frontend_config());
-    translate_module(&module_binary, &mut dummy_environ)?;
-
-    vcprintln!(options.verbose, use_color, terminal, Color::Green, "ok");
-
-    if options.just_decode {
-        if !options.print {
-            return Ok(());
-        }
-
-        let num_func_imports = dummy_environ.get_num_func_imports();
-        for (def_index, func) in dummy_environ.info.function_bodies.iter() {
-            let func_index = num_func_imports + def_index.index();
-            let mut context = Context::new();
-            context.func = func.clone();
-            if let Some(start_func) = dummy_environ.info.start_func {
-                if func_index == start_func.index() {
-                    println!("; Selected as wasm start function");
-                }
-            }
-            vprintln!(options.verbose, "");
-            for export_name in
-                &dummy_environ.info.functions[FuncIndex::new(func_index)].export_names
-            {
-                println!("; Exported as \"{}\"", export_name);
-            }
-            println!("{}", context.func.display());
-            vprintln!(options.verbose, "");
-        }
-        let _ = terminal.reset();
-        return Ok(());
-    }
-
-    if options.check_translation {
-        vcprint!(
-            options.verbose,
-            use_color,
-            terminal,
-            Color::Magenta,
-            "Checking... "
-        );
-    } else {
-        vcprint!(
-            options.verbose,
-            use_color,
-            terminal,
-            Color::Magenta,
-            "Compiling... "
-        );
-    }
-
-    if options.print_size {
-        vprintln!(options.verbose, "");
-    }
-
-    let num_func_imports = dummy_environ.get_num_func_imports();
-    let mut total_module_code_size = 0;
-    let mut context = Context::new();
-    for (def_index, func) in dummy_environ.info.function_bodies.iter() {
-        context.func = func.clone();
-
-        let mut saved_size = None;
-        let func_index = num_func_imports + def_index.index();
-        let mut mem = vec![];
-        let (relocs, traps, stack_maps) = if options.check_translation {
-            if let Err(errors) = context.verify(fisa) {
-                anyhow::bail!("{}", pretty_verifier_error(&context.func, None, errors));
-            }
-            (vec![], vec![], vec![])
-        } else {
-            let compiled_code = context
-                .compile_and_emit(isa, &mut mem, &mut Default::default())
-                .map_err(|err| anyhow::anyhow!("{}", pretty_error(&err.func, err.inner)))?;
-            let code_info = compiled_code.code_info();
-
-            if options.print_size {
-                println!(
-                    "Function #{} code size: {} bytes",
-                    func_index, code_info.total_size,
-                );
-                total_module_code_size += code_info.total_size;
-                println!(
-                    "Function #{} bytecode size: {} bytes",
-                    func_index,
-                    dummy_environ.func_bytecode_sizes[def_index.index()]
-                );
-            }
-
-            if options.disasm {
-                saved_size = Some(code_info.total_size);
-            }
-            (
-                compiled_code.buffer.relocs().to_vec(),
-                compiled_code.buffer.traps().to_vec(),
-                compiled_code.buffer.stack_maps().to_vec(),
-            )
-        };
-
-        if options.print {
-            vprintln!(options.verbose, "");
-            if let Some(start_func) = dummy_environ.info.start_func {
-                if func_index == start_func.index() {
-                    println!("; Selected as wasm start function");
-                }
-            }
-            for export_name in
-                &dummy_environ.info.functions[FuncIndex::new(func_index)].export_names
-            {
-                println!("; Exported as \"{}\"", export_name);
-            }
-            println!("{}", context.func.display());
-            vprintln!(options.verbose, "");
-        }
-
-        if let Some(total_size) = saved_size {
-            print_all(
-                isa,
-                &context.func.params,
-                &mem,
-                total_size,
-                options.print,
-                &relocs,
-                &traps,
-                &stack_maps,
-            )?;
-        }
-
-        context.clear();
-    }
-
-    if !options.check_translation && options.print_size {
-        println!("Total module code size: {} bytes", total_module_code_size);
-        let total_bytecode_size: usize = dummy_environ.func_bytecode_sizes.iter().sum();
-        println!("Total module bytecode size: {} bytes", total_bytecode_size);
-    }
-
-    if options.report_times {
-        println!("{}", timing::take_current());
-    }
-
-    vcprintln!(options.verbose, use_color, terminal, Color::Green, "ok");
-    Ok(())
-}
diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs
deleted file mode 100644
index eeecefc71..000000000
--- a/cranelift/wasm/src/environ/dummy.rs
+++ /dev/null
@@ -1,891 +0,0 @@
-//! "Dummy" implementations of `ModuleEnvironment` and `FuncEnvironment` for testing
-//! wasm translation. For complete implementations of `ModuleEnvironment` and
-//! `FuncEnvironment`, see [wasmtime-environ] in [Wasmtime].
-//!
-//! [wasmtime-environ]: https://crates.io/crates/wasmtime-environ
-//! [Wasmtime]: https://github.com/bytecodealliance/wasmtime
-
-use crate::environ::{FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment};
-use crate::func_translator::FuncTranslator;
-use crate::{
-    DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Heap,
-    HeapData, HeapStyle, Memory, MemoryIndex, Table, TableIndex, TableSize, TypeConvert, TypeIndex,
-    WasmFuncType, WasmHeapType, WasmResult,
-};
-use crate::{TableData, WasmValType};
-use cranelift_codegen::cursor::FuncCursor;
-use cranelift_codegen::ir::immediates::Offset32;
-use cranelift_codegen::ir::{self, InstBuilder};
-use cranelift_codegen::ir::{types::*, UserFuncName};
-use cranelift_codegen::isa::{CallConv, TargetFrontendConfig};
-use cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap};
-use cranelift_frontend::FunctionBuilder;
-use std::boxed::Box;
-use std::string::String;
-use std::vec::Vec;
-use wasmparser::{FuncValidator, FunctionBody, ValidatorResources, WasmFeatures};
-
-/// A collection of names under which a given entity is exported.
-pub struct Exportable<T> {
-    /// A wasm entity.
-    pub entity: T,
-
-    /// Names under which the entity is exported.
-    pub export_names: Vec<String>,
-}
-
-impl<T> Exportable<T> {
-    pub fn new(entity: T) -> Self {
-        Self {
-            entity,
-            export_names: Vec::new(),
-        }
-    }
-}
-
-/// The main state belonging to a `DummyEnvironment`. This is split out from
-/// `DummyEnvironment` to allow it to be borrowed separately from the
-/// `FuncTranslator` field.
-pub struct DummyModuleInfo {
-    /// Target description relevant to frontends producing Cranelift IR.
-    config: TargetFrontendConfig,
-
-    /// Signatures as provided by `declare_signature`.
-    pub signatures: PrimaryMap<TypeIndex, ir::Signature>,
-
-    /// Module and field names of imported functions as provided by `declare_func_import`.
-    pub imported_funcs: Vec<(String, String)>,
-
-    /// Module and field names of imported globals as provided by `declare_global_import`.
-    pub imported_globals: Vec<(String, String)>,
-
-    /// Module and field names of imported tables as provided by `declare_table_import`.
-    pub imported_tables: Vec<(String, String)>,
-
-    /// Module and field names of imported memories as provided by `declare_memory_import`.
-    pub imported_memories: Vec<(String, String)>,
-
-    /// Functions, imported and local.
-    pub functions: PrimaryMap<FuncIndex, Exportable<TypeIndex>>,
-
-    /// Function bodies.
-    pub function_bodies: PrimaryMap<DefinedFuncIndex, ir::Function>,
-
-    /// Tables as provided by `declare_table`.
-    pub tables: PrimaryMap<TableIndex, Exportable<Table>>,
-
-    /// Memories as provided by `declare_memory`.
-    pub memories: PrimaryMap<MemoryIndex, Exportable<Memory>>,
-
-    /// Globals as provided by `declare_global`.
-    pub globals: PrimaryMap<GlobalIndex, Exportable<Global>>,
-
-    /// The start function.
-    pub start_func: Option<FuncIndex>,
-}
-
-impl DummyModuleInfo {
-    /// Creates a new `DummyModuleInfo` instance.
-    pub fn new(config: TargetFrontendConfig) -> Self {
-        Self {
-            config,
-            signatures: PrimaryMap::new(),
-            imported_funcs: Vec::new(),
-            imported_globals: Vec::new(),
-            imported_tables: Vec::new(),
-            imported_memories: Vec::new(),
-            functions: PrimaryMap::new(),
-            function_bodies: PrimaryMap::new(),
-            tables: PrimaryMap::new(),
-            memories: PrimaryMap::new(),
-            globals: PrimaryMap::new(),
-            start_func: None,
-        }
-    }
-}
-
-/// This `ModuleEnvironment` implementation is a "naïve" one, doing essentially nothing and
-/// emitting placeholders when forced to. Don't try to execute code translated for this
-/// environment, essentially here for translation debug purposes.
-pub struct DummyEnvironment {
-    /// Module information.
-    pub info: DummyModuleInfo,
-
-    /// Function translation.
-    pub trans: FuncTranslator,
-
-    /// Vector of wasm bytecode size for each function.
-    pub func_bytecode_sizes: Vec<usize>,
-
-    /// Name of the module from the wasm file.
-    pub module_name: Option<String>,
-
-    /// Function names.
-    function_names: SecondaryMap<FuncIndex, String>,
-}
-
-impl DummyEnvironment {
-    /// Creates a new `DummyEnvironment` instance.
-    pub fn new(config: TargetFrontendConfig) -> Self {
-        Self {
-            info: DummyModuleInfo::new(config),
-            trans: FuncTranslator::new(),
-            func_bytecode_sizes: Vec::new(),
-            module_name: None,
-            function_names: SecondaryMap::new(),
-        }
-    }
-
-    /// Return a `DummyFuncEnvironment` for translating functions within this
-    /// `DummyEnvironment`.
-    pub fn func_env(&self) -> DummyFuncEnvironment {
-        DummyFuncEnvironment::new(&self.info)
-    }
-
-    /// Get the type for the function at the given index.
-    pub fn get_func_type(&self, func_index: FuncIndex) -> TypeIndex {
-        self.info.functions[func_index].entity
-    }
-
-    /// Return the number of imported functions within this `DummyEnvironment`.
-    pub fn get_num_func_imports(&self) -> usize {
-        self.info.imported_funcs.len()
-    }
-
-    /// Return the name of the function, if a name for the function with
-    /// the corresponding index exists.
-    pub fn get_func_name(&self, func_index: FuncIndex) -> Option<&str> {
-        self.function_names.get(func_index).map(String::as_ref)
-    }
-}
-
-/// The `FuncEnvironment` implementation for use by the `DummyEnvironment`.
-pub struct DummyFuncEnvironment<'dummy_environment> {
-    /// This function environment's module info.
-    pub mod_info: &'dummy_environment DummyModuleInfo,
-
-    /// Heaps we have created to implement Wasm linear memories.
-    pub heaps: PrimaryMap<Heap, HeapData>,
-
-    /// Cranelift tables we have created to implement Wasm tables.
-    tables: SecondaryMap<TableIndex, Option<TableData>>,
-}
-
-impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
-    /// Construct a new `DummyFuncEnvironment`.
-    pub fn new(mod_info: &'dummy_environment DummyModuleInfo) -> Self {
-        Self {
-            mod_info,
-            heaps: Default::default(),
-            tables: Default::default(),
-        }
-    }
-
-    /// Create a signature for `sigidx` amended with a `vmctx` argument after
-    /// the standard wasm arguments.
-    pub fn vmctx_sig(&self, sigidx: TypeIndex) -> ir::Signature {
-        let mut sig = self.mod_info.signatures[sigidx].clone();
-        sig.params.push(ir::AbiParam::special(
-            self.pointer_type(),
-            ir::ArgumentPurpose::VMContext,
-        ));
-        sig
-    }
-
-    fn reference_type(&self) -> ir::Type {
-        match self.pointer_type() {
-            ir::types::I32 => ir::types::R32,
-            ir::types::I64 => ir::types::R64,
-            _ => panic!("unsupported pointer type"),
-        }
-    }
-
-    fn ensure_table_exists(&mut self, func: &mut ir::Function, index: TableIndex) {
-        if self.tables[index].is_some() {
-            return;
-        }
-
-        // Create a table whose base address is stored at `vmctx+0`.
-        let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
-        let base_gv = func.create_global_value(ir::GlobalValueData::Load {
-            base: vmctx,
-            offset: Offset32::new(0),
-            global_type: self.pointer_type(),
-            // When tables in wasm become "growable", revisit whether this can be readonly or not.
-            flags: ir::MemFlags::trusted().with_readonly(),
-        });
-
-        let table = &self.mod_info.tables[index].entity;
-
-        let bound = if Some(table.minimum) == table.maximum {
-            TableSize::Static {
-                bound: table.minimum,
-            }
-        } else {
-            TableSize::Dynamic {
-                bound_gv: func.create_global_value(ir::GlobalValueData::Load {
-                    base: vmctx,
-                    offset: Offset32::new(0),
-                    global_type: I32,
-                    flags: ir::MemFlags::trusted().with_readonly(),
-                }),
-            }
-        };
-
-        self.tables[index] = Some(TableData {
-            base_gv,
-            bound,
-            element_size: u32::from(self.pointer_bytes()) * 2,
-        });
-    }
-}
-
-impl<'dummy_environment> TypeConvert for DummyFuncEnvironment<'dummy_environment> {
-    fn lookup_heap_type(&self, _index: wasmparser::UnpackedIndex) -> WasmHeapType {
-        unimplemented!()
-    }
-}
-
-impl<'dummy_environment> TargetEnvironment for DummyFuncEnvironment<'dummy_environment> {
-    fn target_config(&self) -> TargetFrontendConfig {
-        self.mod_info.config
-    }
-
-    fn heap_access_spectre_mitigation(&self) -> bool {
-        false
-    }
-
-    fn proof_carrying_code(&self) -> bool {
-        false
-    }
-}
-
-impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environment> {
-    fn make_global(
-        &mut self,
-        func: &mut ir::Function,
-        index: GlobalIndex,
-    ) -> WasmResult<GlobalVariable> {
-        // Just create a dummy `vmctx` global.
-        let offset = i32::try_from((index.index() * 8) + 8).unwrap().into();
-        let vmctx = func.create_global_value(ir::GlobalValueData::VMContext {});
-        Ok(GlobalVariable::Memory {
-            gv: vmctx,
-            offset,
-            ty: match self.mod_info.globals[index].entity.wasm_ty {
-                WasmValType::I32 => ir::types::I32,
-                WasmValType::I64 => ir::types::I64,
-                WasmValType::F32 => ir::types::F32,
-                WasmValType::F64 => ir::types::F64,
-                WasmValType::V128 => ir::types::I8X16,
-                WasmValType::Ref(_) => ir::types::R64,
-            },
-        })
-    }
-
-    fn heaps(&self) -> &PrimaryMap<Heap, HeapData> {
-        &self.heaps
-    }
-
-    fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> WasmResult<Heap> {
-        // Create a static heap whose base address is stored at `vmctx+0`.
-        let addr = func.create_global_value(ir::GlobalValueData::VMContext);
-        let gv = func.create_global_value(ir::GlobalValueData::Load {
-            base: addr,
-            offset: Offset32::new(0),
-            global_type: self.pointer_type(),
-            flags: ir::MemFlags::trusted().with_readonly(),
-        });
-
-        Ok(self.heaps.push(HeapData {
-            base: gv,
-            min_size: 0,
-            max_size: None,
-            offset_guard_size: 0x8000_0000,
-            style: HeapStyle::Static {
-                bound: 0x1_0000_0000,
-            },
-            index_type: I32,
-            memory_type: None,
-        }))
-    }
-
-    fn make_indirect_sig(
-        &mut self,
-        func: &mut ir::Function,
-        index: TypeIndex,
-    ) -> WasmResult<ir::SigRef> {
-        // A real implementation would probably change the calling convention and add `vmctx` and
-        // signature index arguments.
-        Ok(func.import_signature(self.vmctx_sig(index)))
-    }
-
-    fn make_direct_func(
-        &mut self,
-        func: &mut ir::Function,
-        index: FuncIndex,
-    ) -> WasmResult<ir::FuncRef> {
-        let sigidx = self.mod_info.functions[index].entity;
-        // A real implementation would probably add a `vmctx` argument.
-        // And maybe attempt some signature de-duplication.
-        let signature = func.import_signature(self.vmctx_sig(sigidx));
-        let name =
-            ir::ExternalName::User(func.declare_imported_user_function(ir::UserExternalName {
-                namespace: 0,
-                index: index.as_u32(),
-            }));
-        Ok(func.import_function(ir::ExtFuncData {
-            name,
-            signature,
-            colocated: false,
-        }))
-    }
-
-    fn translate_call_indirect(
-        &mut self,
-        builder: &mut FunctionBuilder,
-        _table_index: TableIndex,
-        _sig_index: TypeIndex,
-        sig_ref: ir::SigRef,
-        callee: ir::Value,
-        call_args: &[ir::Value],
-    ) -> WasmResult<Option<ir::Inst>> {
-        // Pass the current function's vmctx parameter on to the callee.
-        let vmctx = builder
-            .func
-            .special_param(ir::ArgumentPurpose::VMContext)
-            .expect("Missing vmctx parameter");
-
-        // The `callee` value is an index into a table of function pointers.
-        // Apparently, that table is stored at absolute address 0 in this dummy environment.
-        // TODO: Generate bounds checking code.
-        let ptr = self.pointer_type();
-        let callee_offset = if ptr == I32 {
-            builder.ins().imul_imm(callee, 4)
-        } else {
-            let ext = builder.ins().uextend(I64, callee);
-            builder.ins().imul_imm(ext, 4)
-        };
-        let mflags = ir::MemFlags::trusted();
-        let func_ptr = builder.ins().load(ptr, mflags, callee_offset, 0);
-
-        // Build a value list for the indirect call instruction containing the callee, call_args,
-        // and the vmctx parameter.
-        let mut args = ir::ValueList::default();
-        args.push(func_ptr, &mut builder.func.dfg.value_lists);
-        args.extend(call_args.iter().cloned(), &mut builder.func.dfg.value_lists);
-        args.push(vmctx, &mut builder.func.dfg.value_lists);
-
-        Ok(Some(
-            builder
-                .ins()
-                .CallIndirect(ir::Opcode::CallIndirect, INVALID, sig_ref, args)
-                .0,
-        ))
-    }
-
-    fn translate_return_call_indirect(
-        &mut self,
-        _builder: &mut FunctionBuilder,
-        _table_index: TableIndex,
-        _sig_index: TypeIndex,
-        _sig_ref: ir::SigRef,
-        _callee: ir::Value,
-        _call_args: &[ir::Value],
-    ) -> WasmResult<()> {
-        unimplemented!()
-    }
-
-    fn translate_return_call_ref(
-        &mut self,
-        _builder: &mut FunctionBuilder,
-        _sig_ref: ir::SigRef,
-        _callee: ir::Value,
-        _call_args: &[ir::Value],
-    ) -> WasmResult<()> {
-        unimplemented!()
-    }
-
-    fn translate_call(
-        &mut self,
-        builder: &mut FunctionBuilder,
-        _callee_index: FuncIndex,
-        callee: ir::FuncRef,
-        call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
-        // Pass the current function's vmctx parameter on to the callee.
-        let vmctx = builder
-            .func
-            .special_param(ir::ArgumentPurpose::VMContext)
-            .expect("Missing vmctx parameter");
-
-        // Build a value list for the call instruction containing the call_args and the vmctx
-        // parameter.
-        let mut args = ir::ValueList::default();
-        args.extend(call_args.iter().cloned(), &mut builder.func.dfg.value_lists);
-        args.push(vmctx, &mut builder.func.dfg.value_lists);
-
-        Ok(builder
-            .ins()
-            .Call(ir::Opcode::Call, INVALID, callee, args)
-            .0)
-    }
-
-    fn translate_call_ref(
-        &mut self,
-        _builder: &mut FunctionBuilder,
-        _sig_ref: ir::SigRef,
-        _callee: ir::Value,
-        _call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
-        todo!("Implement dummy translate_call_ref")
-    }
-
-    fn translate_memory_grow(
-        &mut self,
-        mut pos: FuncCursor,
-        _index: MemoryIndex,
-        _heap: Heap,
-        _val: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, -1i32 as u32 as i64))
-    }
-
-    fn translate_memory_size(
-        &mut self,
-        mut pos: FuncCursor,
-        _index: MemoryIndex,
-        _heap: Heap,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, -1i32 as u32 as i64))
-    }
-
-    fn translate_memory_copy(
-        &mut self,
-        _pos: FuncCursor,
-        _src_index: MemoryIndex,
-        _src_heap: Heap,
-        _dst_index: MemoryIndex,
-        _dst_heap: Heap,
-        _dst: ir::Value,
-        _src: ir::Value,
-        _len: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_memory_fill(
-        &mut self,
-        _pos: FuncCursor,
-        _index: MemoryIndex,
-        _heap: Heap,
-        _dst: ir::Value,
-        _val: ir::Value,
-        _len: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_memory_init(
-        &mut self,
-        _pos: FuncCursor,
-        _index: MemoryIndex,
-        _heap: Heap,
-        _seg_index: u32,
-        _dst: ir::Value,
-        _src: ir::Value,
-        _len: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_data_drop(&mut self, _pos: FuncCursor, _seg_index: u32) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_table_size(
-        &mut self,
-        mut pos: FuncCursor,
-        _index: TableIndex,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, -1i32 as u32 as i64))
-    }
-
-    fn translate_table_grow(
-        &mut self,
-        mut pos: FuncCursor,
-        _table_index: TableIndex,
-        _delta: ir::Value,
-        _init_value: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, -1i32 as u32 as i64))
-    }
-
-    fn translate_table_get(
-        &mut self,
-        builder: &mut FunctionBuilder,
-        table_index: TableIndex,
-        index: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        let pointer_type = self.pointer_type();
-        self.ensure_table_exists(builder.func, table_index);
-        let table = self.tables[table_index].as_ref().unwrap();
-        let (table_entry_addr, flags) =
-            table.prepare_table_addr(builder, index, pointer_type, true);
-        let value = builder
-            .ins()
-            .load(self.reference_type(), flags, table_entry_addr, 0);
-        Ok(value)
-    }
-
-    fn translate_table_set(
-        &mut self,
-        builder: &mut FunctionBuilder,
-        table_index: TableIndex,
-        value: ir::Value,
-        index: ir::Value,
-    ) -> WasmResult<()> {
-        let pointer_type = self.pointer_type();
-        self.ensure_table_exists(builder.func, table_index);
-        let table = self.tables[table_index].as_ref().unwrap();
-        let (table_entry_addr, flags) =
-            table.prepare_table_addr(builder, index, pointer_type, true);
-        builder.ins().store(flags, value, table_entry_addr, 0);
-        Ok(())
-    }
-
-    fn translate_table_copy(
-        &mut self,
-        _pos: FuncCursor,
-        _dst_index: TableIndex,
-        _src_index: TableIndex,
-        _dst: ir::Value,
-        _src: ir::Value,
-        _len: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_table_fill(
-        &mut self,
-        _pos: FuncCursor,
-        _table_index: TableIndex,
-        _dst: ir::Value,
-        _val: ir::Value,
-        _len: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_table_init(
-        &mut self,
-        _pos: FuncCursor,
-        _seg_index: u32,
-        _table_index: TableIndex,
-        _dst: ir::Value,
-        _src: ir::Value,
-        _len: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_elem_drop(&mut self, _pos: FuncCursor, _seg_index: u32) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_ref_func(
-        &mut self,
-        mut pos: FuncCursor,
-        _func_index: FuncIndex,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().null(self.reference_type()))
-    }
-
-    fn translate_custom_global_get(
-        &mut self,
-        mut pos: FuncCursor,
-        _global_index: GlobalIndex,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, -1i32 as u32 as i64))
-    }
-
-    fn translate_custom_global_set(
-        &mut self,
-        _pos: FuncCursor,
-        _global_index: GlobalIndex,
-        _val: ir::Value,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn translate_atomic_wait(
-        &mut self,
-        mut pos: FuncCursor,
-        _index: MemoryIndex,
-        _heap: Heap,
-        _addr: ir::Value,
-        _expected: ir::Value,
-        _timeout: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, -1i32 as u32 as i64))
-    }
-
-    fn translate_atomic_notify(
-        &mut self,
-        mut pos: FuncCursor,
-        _index: MemoryIndex,
-        _heap: Heap,
-        _addr: ir::Value,
-        _count: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        Ok(pos.ins().iconst(I32, 0))
-    }
-}
-
-impl TypeConvert for DummyEnvironment {
-    fn lookup_heap_type(&self, _index: wasmparser::UnpackedIndex) -> WasmHeapType {
-        unimplemented!()
-    }
-}
-
-impl TargetEnvironment for DummyEnvironment {
-    fn target_config(&self) -> TargetFrontendConfig {
-        self.info.config
-    }
-
-    fn heap_access_spectre_mitigation(&self) -> bool {
-        false
-    }
-
-    fn proof_carrying_code(&self) -> bool {
-        false
-    }
-}
-
-impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
-    fn declare_type_func(&mut self, wasm: WasmFuncType) -> WasmResult<()> {
-        let mut sig = ir::Signature::new(CallConv::Fast);
-        let mut cvt = |ty: &WasmValType| {
-            let reference_type = match self.pointer_type() {
-                ir::types::I32 => ir::types::R32,
-                ir::types::I64 => ir::types::R64,
-                _ => panic!("unsupported pointer type"),
-            };
-            ir::AbiParam::new(match ty {
-                WasmValType::I32 => ir::types::I32,
-                WasmValType::I64 => ir::types::I64,
-                WasmValType::F32 => ir::types::F32,
-                WasmValType::F64 => ir::types::F64,
-                WasmValType::V128 => ir::types::I8X16,
-                WasmValType::Ref(_) => reference_type,
-            })
-        };
-        sig.params.extend(wasm.params().iter().map(&mut cvt));
-        sig.returns.extend(wasm.returns().iter().map(&mut cvt));
-        self.info.signatures.push(sig);
-        Ok(())
-    }
-
-    fn declare_func_import(
-        &mut self,
-        index: TypeIndex,
-        module: &'data str,
-        field: &'data str,
-    ) -> WasmResult<()> {
-        assert_eq!(
-            self.info.functions.len(),
-            self.info.imported_funcs.len(),
-            "Imported functions must be declared first"
-        );
-        self.info.functions.push(Exportable::new(index));
-        self.info
-            .imported_funcs
-            .push((String::from(module), String::from(field)));
-        Ok(())
-    }
-
-    fn declare_func_type(&mut self, index: TypeIndex) -> WasmResult<()> {
-        self.info.functions.push(Exportable::new(index));
-        Ok(())
-    }
-
-    fn declare_global(&mut self, global: Global, _init: GlobalInit) -> WasmResult<()> {
-        self.info.globals.push(Exportable::new(global));
-        Ok(())
-    }
-
-    fn declare_global_import(
-        &mut self,
-        global: Global,
-        module: &'data str,
-        field: &'data str,
-    ) -> WasmResult<()> {
-        self.info.globals.push(Exportable::new(global));
-        self.info
-            .imported_globals
-            .push((String::from(module), String::from(field)));
-        Ok(())
-    }
-
-    fn declare_table(&mut self, table: Table) -> WasmResult<()> {
-        self.info.tables.push(Exportable::new(table));
-        Ok(())
-    }
-
-    fn declare_table_import(
-        &mut self,
-        table: Table,
-        module: &'data str,
-        field: &'data str,
-    ) -> WasmResult<()> {
-        self.info.tables.push(Exportable::new(table));
-        self.info
-            .imported_tables
-            .push((String::from(module), String::from(field)));
-        Ok(())
-    }
-
-    fn declare_table_elements(
-        &mut self,
-        _table_index: TableIndex,
-        _base: Option<GlobalIndex>,
-        _offset: u32,
-        _elements: Box<[FuncIndex]>,
-    ) -> WasmResult<()> {
-        // We do nothing
-        Ok(())
-    }
-
-    fn declare_passive_element(
-        &mut self,
-        _elem_index: ElemIndex,
-        _segments: Box<[FuncIndex]>,
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn declare_passive_data(
-        &mut self,
-        _elem_index: DataIndex,
-        _segments: &'data [u8],
-    ) -> WasmResult<()> {
-        Ok(())
-    }
-
-    fn declare_memory(&mut self, memory: Memory) -> WasmResult<()> {
-        self.info.memories.push(Exportable::new(memory));
-        Ok(())
-    }
-
-    fn declare_memory_import(
-        &mut self,
-        memory: Memory,
-        module: &'data str,
-        field: &'data str,
-    ) -> WasmResult<()> {
-        self.info.memories.push(Exportable::new(memory));
-        self.info
-            .imported_memories
-            .push((String::from(module), String::from(field)));
-        Ok(())
-    }
-
-    fn declare_data_initialization(
-        &mut self,
-        _memory_index: MemoryIndex,
-        _base: Option<GlobalIndex>,
-        _offset: u64,
-        _data: &'data [u8],
-    ) -> WasmResult<()> {
-        // We do nothing
-        Ok(())
-    }
-
-    fn declare_func_export(&mut self, func_index: FuncIndex, name: &'data str) -> WasmResult<()> {
-        self.info.functions[func_index]
-            .export_names
-            .push(String::from(name));
-        Ok(())
-    }
-
-    fn declare_table_export(
-        &mut self,
-        table_index: TableIndex,
-        name: &'data str,
-    ) -> WasmResult<()> {
-        self.info.tables[table_index]
-            .export_names
-            .push(String::from(name));
-        Ok(())
-    }
-
-    fn declare_memory_export(
-        &mut self,
-        memory_index: MemoryIndex,
-        name: &'data str,
-    ) -> WasmResult<()> {
-        self.info.memories[memory_index]
-            .export_names
-            .push(String::from(name));
-        Ok(())
-    }
-
-    fn declare_global_export(
-        &mut self,
-        global_index: GlobalIndex,
-        name: &'data str,
-    ) -> WasmResult<()> {
-        self.info.globals[global_index]
-            .export_names
-            .push(String::from(name));
-        Ok(())
-    }
-
-    fn declare_start_func(&mut self, func_index: FuncIndex) -> WasmResult<()> {
-        debug_assert!(self.info.start_func.is_none());
-        self.info.start_func = Some(func_index);
-        Ok(())
-    }
-
-    fn define_function_body(
-        &mut self,
-        mut validator: FuncValidator<ValidatorResources>,
-        body: FunctionBody<'data>,
-    ) -> WasmResult<()> {
-        self.func_bytecode_sizes
-            .push(body.get_binary_reader().bytes_remaining());
-        let func = {
-            let mut func_environ = DummyFuncEnvironment::new(&self.info);
-            let func_index =
-                FuncIndex::new(self.get_num_func_imports() + self.info.function_bodies.len());
-
-            let sig = func_environ.vmctx_sig(self.get_func_type(func_index));
-            let mut func =
-                ir::Function::with_name_signature(UserFuncName::user(0, func_index.as_u32()), sig);
-
-            self.trans
-                .translate_body(&mut validator, body, &mut func, &mut func_environ)?;
-            func
-        };
-        self.info.function_bodies.push(func);
-        Ok(())
-    }
-
-    fn declare_module_name(&mut self, name: &'data str) {
-        self.module_name = Some(String::from(name));
-    }
-
-    fn declare_func_name(&mut self, func_index: FuncIndex, name: &'data str) {
-        self.function_names[func_index] = String::from(name);
-    }
-
-    fn wasm_features(&self) -> WasmFeatures {
-        WasmFeatures {
-            multi_value: true,
-            simd: true,
-            reference_types: true,
-            bulk_memory: true,
-            ..WasmFeatures::default()
-        }
-    }
-}
diff --git a/cranelift/wasm/src/environ/mod.rs b/cranelift/wasm/src/environ/mod.rs
index acb99ebc6..665a17a9d 100644
--- a/cranelift/wasm/src/environ/mod.rs
+++ b/cranelift/wasm/src/environ/mod.rs
@@ -1,10 +1,8 @@
 //! Support for configurable wasm translation.
 
-mod dummy;
 #[macro_use]
 mod spec;
 
-pub use crate::environ::dummy::{DummyEnvironment, DummyFuncEnvironment, DummyModuleInfo};
 pub use crate::environ::spec::{
     FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment,
 };
diff --git a/cranelift/wasm/src/lib.rs b/cranelift/wasm/src/lib.rs
index 34725539d..bd3092fa3 100644
--- a/cranelift/wasm/src/lib.rs
+++ b/cranelift/wasm/src/lib.rs
@@ -4,9 +4,6 @@
 //! [`ModuleEnvironment`](trait.ModuleEnvironment.html)
 //! trait to deal with tables, globals and linear memory.
 //!
-//! The crate provides a `DummyEnvironment` struct that will allow to translate the code of the
-//! functions but will fail at execution.
-//!
 //! The main function of this module is [`translate_module`](fn.translate_module.html).
 
 #![deny(missing_docs)]
@@ -42,10 +39,7 @@ mod state;
 mod table;
 mod translation_utils;
 
-pub use crate::environ::{
-    DummyEnvironment, DummyFuncEnvironment, DummyModuleInfo, FuncEnvironment, GlobalVariable,
-    ModuleEnvironment, TargetEnvironment,
-};
+pub use crate::environ::{FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment};
 pub use crate::func_translator::FuncTranslator;
 pub use crate::heap::{Heap, HeapData, HeapStyle};
 pub use crate::module_translator::translate_module;
diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml
index 6134e7f6e..c5cde3176 100644
--- a/crates/fuzzing/Cargo.toml
+++ b/crates/fuzzing/Cargo.toml
@@ -49,3 +49,6 @@ wasm-spec-interpreter = { path = "./wasm-spec-interpreter", optional = true, fea
 
 [features]
 fuzz-spec-interpreter = ['wasm-spec-interpreter']
+
+# Fuzz proof-carrying code. Off by default.
+fuzz-pcc = []
diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs
index 273b2b598..9f4843958 100644
--- a/crates/fuzzing/src/generators/config.rs
+++ b/crates/fuzzing/src/generators/config.rs
@@ -188,7 +188,9 @@ impl Config {
         // Determine whether we will actually enable PCC -- this is
         // disabled if the module erquires memory64, which is not yet
         // compatible (due to the need for dynamic checks).
-        let pcc = self.wasmtime.pcc && !self.module_config.config.memory64_enabled;
+        let pcc = cfg!(feature = "fuzz-pcc")
+            && self.wasmtime.pcc
+            && !self.module_config.config.memory64_enabled;
 
         // Only set cranelift specific flags when the Cranelift strategy is
         // chosen.
diff --git a/crates/wasi/src/runtime.rs b/crates/wasi/src/runtime.rs
index 31d8135ff..b92509810 100644
--- a/crates/wasi/src/runtime.rs
+++ b/crates/wasi/src/runtime.rs
@@ -25,7 +25,7 @@ use std::task::{Context, Poll};
 
 pub(crate) static RUNTIME: once_cell::sync::Lazy<tokio::runtime::Runtime> =
     once_cell::sync::Lazy::new(|| {
-        tokio::runtime::Builder::new_current_thread()
+        tokio::runtime::Builder::new_multi_thread()
             .enable_time()
             .enable_io()
             .build()


Test patch:
diff --git a/crates/test-programs/src/bin/cli_sleep.rs b/crates/test-programs/src/bin/cli_sleep.rs
new file mode 100644
index 000000000..ff5beef82
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_sleep.rs
@@ -0,0 +1,5 @@
+use std::time::Duration;
+
+fn main() {
+    std::thread::sleep(Duration::from_nanos(100));
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 146f1dd72..3a604b4f0 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1596,6 +1596,13 @@ mod test_programs {
         assert!(output.status.success());
         Ok(())
     }
+
+    #[test]
+    fn cli_sleep() -> Result<()> {
+        run_wasmtime(&["run", CLI_SLEEP])?;
+        run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?;
+        Ok(())
+    }
 }
 
 #[test]