Repository: bytecodealliance/wasmtime
Pull request #9245 (2024-09-13 18:43:41)

`cranelift-wasm`: Remove old dummy environment
Used to be used for testing, now we use the disas test infrastructure instead, but this file was still hanging around.

<!--
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/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs
deleted file mode 100644
index 2c7d1f5f3..000000000
--- a/cranelift/wasm/src/environ/dummy.rs
+++ /dev/null
@@ -1,897 +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};
-use wasmtime_types::wasm_unsupported;
-
-/// 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 {
-        self.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(_) => self.pointer_type(),
-            },
-        })
-    }
-
-    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))
-    }
-
-    fn translate_ref_i31(&mut self, _pos: FuncCursor, _val: ir::Value) -> WasmResult<ir::Value> {
-        Err(wasm_unsupported!("ref.i31"))
-    }
-
-    fn translate_i31_get_s(
-        &mut self,
-        _pos: FuncCursor,
-        _i31ref: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        Err(wasm_unsupported!("i31.get_s"))
-    }
-
-    fn translate_i31_get_u(
-        &mut self,
-        _pos: FuncCursor,
-        _i31ref: ir::Value,
-    ) -> WasmResult<ir::Value> {
-        Err(wasm_unsupported!("i31.get_u"))
-    }
-}
-
-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| {
-            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(_) => self.pointer_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: u64,
-        _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::default()
-    }
-}
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index ceb8eddbb..180852eaf 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -1912,21 +1912,25 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
             ty.is_vmgcref_type(),
             "We only use GlobalVariable::Custom for VMGcRef types"
         );
+        let cranelift_wasm::WasmValType::Ref(ty) = ty else {
+            unreachable!()
+        };
 
-        // TODO: use `GcCompiler::translate_read_gc_reference` for GC-reference
-        // globals instead of a libcall.
-        let libcall = gc::gc_ref_global_get_builtin(ty, self, &mut builder.func)?;
-
-        let vmctx = self.vmctx_val(&mut builder.cursor());
-
-        let global_index_arg = builder.ins().iconst(I32, index.as_u32() as i64);
-        let call_inst = builder.ins().call(libcall, &[vmctx, global_index_arg]);
+        let (gv, offset) = self.get_global_location(builder.func, index);
+        let gv = builder.ins().global_value(self.pointer_type(), gv);
+        let src = builder.ins().iadd_imm(gv, i64::from(offset));
 
-        let val = builder.func.dfg.first_result(call_inst);
-        if ty.is_vmgcref_type_and_not_i31() {
-            builder.declare_value_needs_stack_map(val);
+        if let WasmHeapType::I31 = ty.heap_type {
+            gc::unbarriered_load_gc_ref(self, builder, ty.heap_type, src, ir::MemFlags::trusted())
+        } else {
+            gc::gc_compiler(self).translate_read_gc_reference(
+                self,
+                builder,
+                ty,
+                src,
+                ir::MemFlags::trusted(),
+            )
         }
-        Ok(val)
     }
 
     fn translate_custom_global_set(
@@ -1940,19 +1944,33 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
             ty.is_vmgcref_type(),
             "We only use GlobalVariable::Custom for VMGcRef types"
         );
+        let cranelift_wasm::WasmValType::Ref(ty) = ty else {
+            unreachable!()
+        };
 
-        // TODO: use `GcCompiler::translate_write_gc_reference` for GC-reference
-        // globals instead of a libcall.
-        let libcall = gc::gc_ref_global_set_builtin(ty, self, &mut builder.func)?;
-
-        let vmctx = self.vmctx_val(&mut builder.cursor());
-
-        let global_index_arg = builder.ins().iconst(I32, index.as_u32() as i64);
-        builder
-            .ins()
-            .call(libcall, &[vmctx, global_index_arg, value]);
+        let (gv, offset) = self.get_global_location(builder.func, index);
+        let gv = builder.ins().global_value(self.pointer_type(), gv);
+        let src = builder.ins().iadd_imm(gv, i64::from(offset));
 
-        Ok(())
+        if let WasmHeapType::I31 = ty.heap_type {
+            gc::unbarriered_store_gc_ref(
+                self,
+                builder,
+                ty.heap_type,
+                src,
+                value,
+                ir::MemFlags::trusted(),
+            )
+        } else {
+            gc::gc_compiler(self).translate_write_gc_reference(
+                self,
+                builder,
+                ty,
+                src,
+                value,
+                ir::MemFlags::trusted(),
+            )
+        }
     }
 
     fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult<Heap> {
diff --git a/crates/cranelift/src/gc.rs b/crates/cranelift/src/gc.rs
index 0583dfee0..8e5f5db82 100644
--- a/crates/cranelift/src/gc.rs
+++ b/crates/cranelift/src/gc.rs
@@ -8,7 +8,7 @@
 use crate::func_environ::FuncEnvironment;
 use cranelift_codegen::ir;
 use cranelift_frontend::FunctionBuilder;
-use cranelift_wasm::{WasmHeapType, WasmRefType, WasmResult, WasmValType};
+use cranelift_wasm::{WasmHeapType, WasmRefType, WasmResult};
 
 #[cfg(feature = "gc")]
 mod enabled;
@@ -80,28 +80,6 @@ pub fn gc_ref_table_fill_builtin(
     imp::gc_ref_table_fill_builtin(ty, func_env, func)
 }
 
-/// Get the index and signature of the built-in function for doing `global.get`
-/// on a GC reference global.
-pub fn gc_ref_global_get_builtin(
-    ty: WasmValType,
-    func_env: &mut FuncEnvironment<'_>,
-    func: &mut ir::Function,
-) -> WasmResult<ir::FuncRef> {
-    debug_assert!(ty.is_vmgcref_type());
-    imp::gc_ref_global_get_builtin(ty, func_env, func)
-}
-
-/// Get the index and signature of the built-in function for doing `global.set`
-/// on a GC reference global.
-pub fn gc_ref_global_set_builtin(
-    ty: WasmValType,
-    func_env: &mut FuncEnvironment<'_>,
-    func: &mut ir::Function,
-) -> WasmResult<ir::FuncRef> {
-    debug_assert!(ty.is_vmgcref_type());
-    imp::gc_ref_global_set_builtin(ty, func_env, func)
-}
-
 /// A trait for different collectors to emit any GC barriers they might require.
 pub trait GcCompiler {
     /// Emit a read barrier for when we are cloning a GC reference onto the Wasm
diff --git a/crates/cranelift/src/gc/disabled.rs b/crates/cranelift/src/gc/disabled.rs
index 308806299..7822439d7 100644
--- a/crates/cranelift/src/gc/disabled.rs
+++ b/crates/cranelift/src/gc/disabled.rs
@@ -4,7 +4,7 @@ use super::GcCompiler;
 use crate::func_environ::FuncEnvironment;
 use cranelift_codegen::ir;
 use cranelift_frontend::FunctionBuilder;
-use cranelift_wasm::{wasm_unsupported, WasmHeapType, WasmRefType, WasmResult, WasmValType};
+use cranelift_wasm::{wasm_unsupported, WasmHeapType, WasmRefType, WasmResult};
 
 /// Get the default GC compiler.
 pub fn gc_compiler(_: &FuncEnvironment<'_>) -> Box<dyn GcCompiler> {
@@ -60,28 +60,6 @@ pub fn gc_ref_table_fill_builtin(
     ))
 }
 
-pub fn gc_ref_global_get_builtin(
-    ty: WasmValType,
-    _func_env: &mut FuncEnvironment<'_>,
-    _func: &mut ir::Function,
-) -> WasmResult<ir::FuncRef> {
-    Err(wasm_unsupported!(
-        "support for `{ty}` disabled at compile time because the `gc` cargo \
-         feature was not enabled"
-    ))
-}
-
-pub fn gc_ref_global_set_builtin(
-    ty: WasmValType,
-    _func_env: &mut FuncEnvironment<'_>,
-    _func: &mut ir::Function,
-) -> WasmResult<ir::FuncRef> {
-    Err(wasm_unsupported!(
-        "support for `{ty}` disabled at compile time because the `gc` cargo \
-         feature was not enabled"
-    ))
-}
-
 struct DisabledGcCompiler;
 
 impl GcCompiler for DisabledGcCompiler {
diff --git a/crates/cranelift/src/gc/enabled.rs b/crates/cranelift/src/gc/enabled.rs
index f91a2492c..f9c8deae9 100644
--- a/crates/cranelift/src/gc/enabled.rs
+++ b/crates/cranelift/src/gc/enabled.rs
@@ -2,9 +2,7 @@ use super::GcCompiler;
 use crate::func_environ::FuncEnvironment;
 use cranelift_codegen::ir::{self, condcodes::IntCC, InstBuilder};
 use cranelift_frontend::FunctionBuilder;
-use cranelift_wasm::{
-    TargetEnvironment, WasmHeapTopType, WasmHeapType, WasmRefType, WasmResult, WasmValType,
-};
+use cranelift_wasm::{TargetEnvironment, WasmHeapTopType, WasmHeapType, WasmRefType, WasmResult};
 use wasmtime_environ::{PtrSize, I31_DISCRIMINANT, NON_NULL_NON_I31_MASK};
 
 /// Get the default GC compiler.
@@ -62,24 +60,6 @@ pub fn gc_ref_table_fill_builtin(
     Ok(func_env.builtin_functions.table_fill_gc_ref(func))
 }
 
-pub fn gc_ref_global_get_builtin(
-    ty: WasmValType,
-    func_env: &mut FuncEnvironment<'_>,
-    func: &mut ir::Function,
-) -> WasmResult<ir::FuncRef> {
-    debug_assert!(ty.is_vmgcref_type());
-    Ok(func_env.builtin_functions.gc_ref_global_get(func))
-}
-
-pub fn gc_ref_global_set_builtin(
-    ty: WasmValType,
-    func_env: &mut FuncEnvironment<'_>,
-    func: &mut ir::Function,
-) -> WasmResult<ir::FuncRef> {
-    debug_assert!(ty.is_vmgcref_type());
-    Ok(func_env.builtin_functions.gc_ref_global_set(func))
-}
-
 impl FuncEnvironment<'_> {
     /// Get the GC heap's base pointer and bound.
     fn get_gc_heap_base_bound(&mut self, builder: &mut FunctionBuilder) -> (ir::Value, ir::Value) {
diff --git a/crates/environ/src/builtin.rs b/crates/environ/src/builtin.rs
index f42b0b5dc..03a9e776c 100644
--- a/crates/environ/src/builtin.rs
+++ b/crates/environ/src/builtin.rs
@@ -79,16 +79,6 @@ macro_rules! foreach_builtin_function {
             #[cfg(feature = "gc")]
             gc(vmctx: vmctx, root: reference) -> reference;
 
-            // Implementation of Wasm's `global.get` instruction for globals
-            // containing GC references.
-            #[cfg(feature = "gc")]
-            gc_ref_global_get(vmctx: vmctx, global: i32) -> reference;
-
-            // Implementation of Wasm's `global.set` instruction for globals
-            // containing GC references.
-            #[cfg(feature = "gc")]
-            gc_ref_global_set(vmctx: vmctx, global: i32, val: reference);
-
             // Returns an index for Wasm's `table.grow` instruction for GC references.
             #[cfg(feature = "gc")]
             table_grow_gc_ref(vmctx: vmctx, table: i32, delta: i64, init: reference) -> pointer;
diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs
index bd8638459..ecc94f62f 100644
--- a/crates/wasmtime/src/runtime/vm/libcalls.rs
+++ b/crates/wasmtime/src/runtime/vm/libcalls.rs
@@ -437,43 +437,6 @@ unsafe fn gc(instance: &mut Instance, gc_ref: u32) -> Result<u32> {
     }
 }
 
-/// Perform a Wasm `global.get` for GC reference globals.
-#[cfg(feature = "gc")]
-unsafe fn gc_ref_global_get(instance: &mut Instance, index: u32) -> Result<u32> {
-    use core::num::NonZeroUsize;
-
-    let index = wasmtime_environ::GlobalIndex::from_u32(index);
-    let global = instance.defined_or_imported_global_ptr(index);
-    let gc_store = (*instance.store()).gc_store();
-
-    if gc_store
-        .gc_heap
-        .need_gc_before_entering_wasm(NonZeroUsize::new(1).unwrap())
-    {
-        (*instance.store()).gc(None)?;
-    }
-
-    match (*global).as_gc_ref() {
-        None => Ok(0),
-        Some(gc_ref) => {
-            let gc_ref = gc_store.clone_gc_ref(gc_ref);
-            let ret = gc_ref.as_raw_u32();
-            gc_store.expose_gc_ref_to_wasm(gc_ref);
-            Ok(ret)
-        }
-    }
-}
-
-/// Perform a Wasm `global.set` for GC reference globals.
-#[cfg(feature = "gc")]
-unsafe fn gc_ref_global_set(instance: &mut Instance, index: u32, gc_ref: u32) {
-    let index = wasmtime_environ::GlobalIndex::from_u32(index);
-    let global = instance.defined_or_imported_global_ptr(index);
-    let gc_ref = VMGcRef::from_raw_u32(gc_ref);
-    let gc_store = (*instance.store()).gc_store();
-    (*global).write_gc_ref(gc_store, gc_ref.as_ref());
-}
-
 // Implementation of `memory.atomic.notify` for locally defined memories.
 #[cfg(feature = "threads")]
 fn memory_atomic_notify(


Test patch:
diff --git a/cranelift/filetests/src/test_wasm/env.rs b/cranelift/filetests/src/test_wasm/env.rs
deleted file mode 100644
index dccc9c31f..000000000
--- a/cranelift/filetests/src/test_wasm/env.rs
+++ /dev/null
@@ -1,712 +0,0 @@
-//! `cranelift_wasm` environments for translating Wasm to CLIF.
-//!
-//! Mostly wrappers around the dummy environments, but also supports
-//! pre-configured heaps.
-
-use std::collections::{BTreeMap, HashSet};
-
-use super::config::TestConfig;
-use cranelift::prelude::EntityRef;
-use cranelift_codegen::{
-    ir,
-    isa::{TargetFrontendConfig, TargetIsa},
-};
-use cranelift_wasm::{
-    DummyEnvironment, FuncEnvironment, FuncIndex, ModuleEnvironment, TargetEnvironment,
-    TypeConvert, TypeIndex, WasmHeapType,
-};
-
-pub struct ModuleEnv {
-    pub inner: DummyEnvironment,
-    pub config: TestConfig,
-    pub heap_access_spectre_mitigation: bool,
-    pub proof_carrying_code: bool,
-}
-
-impl ModuleEnv {
-    pub fn new(target_isa: &dyn TargetIsa, config: TestConfig) -> Self {
-        let inner = DummyEnvironment::new(target_isa.frontend_config());
-        Self {
-            inner,
-            config,
-            heap_access_spectre_mitigation: target_isa
-                .flags()
-                .enable_heap_access_spectre_mitigation(),
-            proof_carrying_code: target_isa.flags().enable_pcc(),
-        }
-    }
-}
-
-impl<'data> ModuleEnvironment<'data> for ModuleEnv {
-    fn define_function_body(
-        &mut self,
-        mut validator: wasmparser::FuncValidator<wasmparser::ValidatorResources>,
-        body: wasmparser::FunctionBody<'data>,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .func_bytecode_sizes
-            .push(body.get_binary_reader().bytes_remaining());
-
-        let func = {
-            let mut func_environ = FuncEnv::new(
-                &self.inner.info,
-                self.inner.expected_reachability.clone(),
-                self.config.clone(),
-                self.heap_access_spectre_mitigation,
-                self.proof_carrying_code,
-            );
-            let func_index = FuncIndex::new(
-                self.inner.get_num_func_imports() + self.inner.info.function_bodies.len(),
-            );
-
-            let sig = func_environ
-                .inner
-                .vmctx_sig(self.inner.get_func_type(func_index));
-            let mut func = ir::Function::with_name_signature(
-                ir::UserFuncName::user(0, func_index.as_u32()),
-                sig,
-            );
-
-            self.inner
-                .trans
-                .translate_body(&mut validator, body, &mut func, &mut func_environ)?;
-            func
-        };
-
-        self.inner.info.function_bodies.push(func);
-
-        Ok(())
-    }
-
-    fn wasm_features(&self) -> wasmparser::WasmFeatures {
-        self.inner.wasm_features()
-            | wasmparser::WasmFeatures::MEMORY64
-            | wasmparser::WasmFeatures::MULTI_MEMORY
-            | wasmparser::WasmFeatures::RELAXED_SIMD
-    }
-
-    // ================================================================
-    // ====== Everything below here is delegated to `self.inner` ======
-    // ================================================================
-
-    fn declare_type_func(
-        &mut self,
-        wasm_func_type: cranelift_wasm::WasmFuncType,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_type_func(wasm_func_type)
-    }
-
-    fn declare_func_import(
-        &mut self,
-        index: cranelift_wasm::TypeIndex,
-        module: &'data str,
-        field: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_func_import(index, module, field)
-    }
-
-    fn declare_table_import(
-        &mut self,
-        table: cranelift_wasm::Table,
-        module: &'data str,
-        field: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_table_import(table, module, field)
-    }
-
-    fn declare_memory_import(
-        &mut self,
-        memory: cranelift_wasm::Memory,
-        module: &'data str,
-        field: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_memory_import(memory, module, field)
-    }
-
-    fn declare_global_import(
-        &mut self,
-        global: cranelift_wasm::Global,
-        module: &'data str,
-        field: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_global_import(global, module, field)
-    }
-
-    fn declare_func_type(
-        &mut self,
-        index: cranelift_wasm::TypeIndex,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_func_type(index)
-    }
-
-    fn declare_table(&mut self, table: cranelift_wasm::Table) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_table(table)
-    }
-
-    fn declare_memory(&mut self, memory: cranelift_wasm::Memory) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_memory(memory)
-    }
-
-    fn declare_global(
-        &mut self,
-        global: cranelift_wasm::Global,
-        init: cranelift_wasm::GlobalInit,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_global(global, init)
-    }
-
-    fn declare_func_export(
-        &mut self,
-        func_index: cranelift_wasm::FuncIndex,
-        name: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_func_export(func_index, name)
-    }
-
-    fn declare_table_export(
-        &mut self,
-        table_index: cranelift_wasm::TableIndex,
-        name: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_table_export(table_index, name)
-    }
-
-    fn declare_memory_export(
-        &mut self,
-        memory_index: cranelift_wasm::MemoryIndex,
-        name: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_memory_export(memory_index, name)
-    }
-
-    fn declare_global_export(
-        &mut self,
-        global_index: cranelift_wasm::GlobalIndex,
-        name: &'data str,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_global_export(global_index, name)
-    }
-
-    fn declare_start_func(
-        &mut self,
-        index: cranelift_wasm::FuncIndex,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_start_func(index)
-    }
-
-    fn declare_table_elements(
-        &mut self,
-        table_index: cranelift_wasm::TableIndex,
-        base: Option<cranelift_wasm::GlobalIndex>,
-        offset: u32,
-        elements: Box<[cranelift_wasm::FuncIndex]>,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .declare_table_elements(table_index, base, offset, elements)
-    }
-
-    fn declare_passive_element(
-        &mut self,
-        index: cranelift_wasm::ElemIndex,
-        elements: Box<[cranelift_wasm::FuncIndex]>,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_passive_element(index, elements)
-    }
-
-    fn declare_passive_data(
-        &mut self,
-        data_index: cranelift_wasm::DataIndex,
-        data: &'data [u8],
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.declare_passive_data(data_index, data)
-    }
-
-    fn declare_data_initialization(
-        &mut self,
-        memory_index: cranelift_wasm::MemoryIndex,
-        base: Option<cranelift_wasm::GlobalIndex>,
-        offset: u64,
-        data: &'data [u8],
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .declare_data_initialization(memory_index, base, offset, data)
-    }
-}
-
-impl TypeConvert for ModuleEnv {
-    fn lookup_heap_type(&self, _index: wasmparser::UnpackedIndex) -> WasmHeapType {
-        todo!()
-    }
-}
-
-pub struct FuncEnv<'a> {
-    pub inner: cranelift_wasm::DummyFuncEnvironment<'a>,
-    pub config: TestConfig,
-    pub name_to_ir_global: BTreeMap<String, ir::GlobalValue>,
-    pub next_heap: usize,
-    pub heap_access_spectre_mitigation: bool,
-    pub proof_carrying_code: bool,
-}
-
-impl<'a> FuncEnv<'a> {
-    pub fn new(
-        mod_info: &'a cranelift_wasm::DummyModuleInfo,
-        expected_reachability: Option<cranelift_wasm::ExpectedReachability>,
-        config: TestConfig,
-        heap_access_spectre_mitigation: bool,
-        proof_carrying_code: bool,
-    ) -> Self {
-        let inner = cranelift_wasm::DummyFuncEnvironment::new(mod_info, expected_reachability);
-        Self {
-            inner,
-            config,
-            name_to_ir_global: Default::default(),
-            next_heap: 0,
-            heap_access_spectre_mitigation,
-            proof_carrying_code,
-        }
-    }
-}
-
-impl TypeConvert for FuncEnv<'_> {
-    fn lookup_heap_type(&self, _index: wasmparser::UnpackedIndex) -> WasmHeapType {
-        todo!()
-    }
-}
-
-impl<'a> TargetEnvironment for FuncEnv<'a> {
-    fn target_config(&self) -> TargetFrontendConfig {
-        self.inner.target_config()
-    }
-
-    fn heap_access_spectre_mitigation(&self) -> bool {
-        self.heap_access_spectre_mitigation
-    }
-
-    fn proof_carrying_code(&self) -> bool {
-        self.proof_carrying_code
-    }
-}
-
-impl<'a> FuncEnvironment for FuncEnv<'a> {
-    fn make_heap(
-        &mut self,
-        func: &mut ir::Function,
-        index: cranelift_wasm::MemoryIndex,
-    ) -> cranelift_wasm::WasmResult<cranelift_wasm::Heap> {
-        if self.next_heap < self.config.heaps.len() {
-            let heap = &self.config.heaps[self.next_heap];
-            self.next_heap += 1;
-
-            // Create all of the globals our test heap depends on in topological
-            // order.
-            let mut worklist: Vec<&str> = heap
-                .dependencies()
-                .filter(|g| !self.name_to_ir_global.contains_key(*g))
-                .collect();
-            let mut in_worklist: HashSet<&str> = worklist.iter().copied().collect();
-            'worklist_fixpoint: while let Some(global_name) = worklist.pop() {
-                let was_in_set = in_worklist.remove(global_name);
-                debug_assert!(was_in_set);
-
-                let global = &self.config.globals[global_name];
-
-                // Check that all of this global's dependencies have already
-                // been created. If not, then enqueue them to be created
-                // first and re-enqueue this global.
-                for g in global.dependencies() {
-                    if !self.name_to_ir_global.contains_key(g) {
-                        if in_worklist.contains(&g) {
-                            return Err(cranelift_wasm::WasmError::User(format!(
-                                "dependency cycle between global '{global_name}' and global '{g}'"
-                            )));
-                        }
-
-                        worklist.push(global_name);
-                        let is_new_entry = in_worklist.insert(global_name);
-                        debug_assert!(is_new_entry);
-
-                        worklist.push(g);
-                        let is_new_entry = in_worklist.insert(g);
-                        debug_assert!(is_new_entry);
-
-                        continue 'worklist_fixpoint;
-                    }
-                }
-
-                // All of this globals dependencies have already been
-                // created, we can create it now!
-                let data = global.to_ir(&self.name_to_ir_global);
-                let g = func.create_global_value(data);
-                self.name_to_ir_global.insert(global_name.to_string(), g);
-            }
-
-            Ok(self.inner.heaps.push(heap.to_ir(&self.name_to_ir_global)))
-        } else {
-            self.inner.make_heap(func, index)
-        }
-    }
-
-    // ================================================================
-    // ====== Everything below here is delegated to `self.inner` ======
-    // ================================================================
-
-    fn make_global(
-        &mut self,
-        func: &mut ir::Function,
-        index: cranelift_wasm::GlobalIndex,
-    ) -> cranelift_wasm::WasmResult<cranelift_wasm::GlobalVariable> {
-        self.inner.make_global(func, index)
-    }
-
-    fn make_table(
-        &mut self,
-        func: &mut ir::Function,
-        index: cranelift_wasm::TableIndex,
-    ) -> cranelift_wasm::WasmResult<ir::Table> {
-        self.inner.make_table(func, index)
-    }
-
-    fn make_indirect_sig(
-        &mut self,
-        func: &mut ir::Function,
-        index: cranelift_wasm::TypeIndex,
-    ) -> cranelift_wasm::WasmResult<ir::SigRef> {
-        self.inner.make_indirect_sig(func, index)
-    }
-
-    fn make_direct_func(
-        &mut self,
-        func: &mut ir::Function,
-        index: FuncIndex,
-    ) -> cranelift_wasm::WasmResult<ir::FuncRef> {
-        self.inner.make_direct_func(func, index)
-    }
-
-    fn translate_call_indirect(
-        &mut self,
-        builder: &mut cranelift_frontend::FunctionBuilder,
-        table_index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-        sig_index: cranelift_wasm::TypeIndex,
-        sig_ref: ir::SigRef,
-        callee: ir::Value,
-        call_args: &[ir::Value],
-    ) -> cranelift_wasm::WasmResult<ir::Inst> {
-        self.inner.translate_call_indirect(
-            builder,
-            table_index,
-            table,
-            sig_index,
-            sig_ref,
-            callee,
-            call_args,
-        )
-    }
-
-    fn translate_return_call_indirect(
-        &mut self,
-        builder: &mut cranelift_frontend::FunctionBuilder,
-        table_index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-        sig_index: TypeIndex,
-        sig_ref: ir::SigRef,
-        callee: ir::Value,
-        call_args: &[ir::Value],
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.translate_return_call_indirect(
-            builder,
-            table_index,
-            table,
-            sig_index,
-            sig_ref,
-            callee,
-            call_args,
-        )
-    }
-
-    fn translate_return_call_ref(
-        &mut self,
-        builder: &mut cranelift_frontend::FunctionBuilder,
-        sig_ref: ir::SigRef,
-        callee: ir::Value,
-        call_args: &[ir::Value],
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_return_call_ref(builder, sig_ref, callee, call_args)
-    }
-
-    fn translate_memory_grow(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::MemoryIndex,
-        heap: cranelift_wasm::Heap,
-        val: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner.translate_memory_grow(pos, index, heap, val)
-    }
-
-    fn translate_memory_size(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::MemoryIndex,
-        heap: cranelift_wasm::Heap,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner.translate_memory_size(pos, index, heap)
-    }
-
-    fn translate_memory_copy(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        src_index: cranelift_wasm::MemoryIndex,
-        src_heap: cranelift_wasm::Heap,
-        dst_index: cranelift_wasm::MemoryIndex,
-        dst_heap: cranelift_wasm::Heap,
-        dst: ir::Value,
-        src: ir::Value,
-        len: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_memory_copy(pos, src_index, src_heap, dst_index, dst_heap, dst, src, len)
-    }
-
-    fn translate_memory_fill(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::MemoryIndex,
-        heap: cranelift_wasm::Heap,
-        dst: ir::Value,
-        val: ir::Value,
-        len: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_memory_fill(pos, index, heap, dst, val, len)
-    }
-
-    fn translate_memory_init(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::MemoryIndex,
-        heap: cranelift_wasm::Heap,
-        seg_index: u32,
-        dst: ir::Value,
-        src: ir::Value,
-        len: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_memory_init(pos, index, heap, seg_index, dst, src, len)
-    }
-
-    fn translate_data_drop(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        seg_index: u32,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.translate_data_drop(pos, seg_index)
-    }
-
-    fn translate_table_size(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner.translate_table_size(pos, index, table)
-    }
-
-    fn translate_table_grow(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        table_index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-        delta: ir::Value,
-        init_value: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner
-            .translate_table_grow(pos, table_index, table, delta, init_value)
-    }
-
-    fn translate_table_get(
-        &mut self,
-        builder: &mut cranelift_frontend::FunctionBuilder,
-        table_index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-        index: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner
-            .translate_table_get(builder, table_index, table, index)
-    }
-
-    fn translate_table_set(
-        &mut self,
-        builder: &mut cranelift_frontend::FunctionBuilder,
-        table_index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-        value: ir::Value,
-        index: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_table_set(builder, table_index, table, value, index)
-    }
-
-    fn translate_table_copy(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        dst_table_index: cranelift_wasm::TableIndex,
-        dst_table: ir::Table,
-        src_table_index: cranelift_wasm::TableIndex,
-        src_table: ir::Table,
-        dst: ir::Value,
-        src: ir::Value,
-        len: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.translate_table_copy(
-            pos,
-            dst_table_index,
-            dst_table,
-            src_table_index,
-            src_table,
-            dst,
-            src,
-            len,
-        )
-    }
-
-    fn translate_table_fill(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        table_index: cranelift_wasm::TableIndex,
-        dst: ir::Value,
-        val: ir::Value,
-        len: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_table_fill(pos, table_index, dst, val, len)
-    }
-
-    fn translate_table_init(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        seg_index: u32,
-        table_index: cranelift_wasm::TableIndex,
-        table: ir::Table,
-        dst: ir::Value,
-        src: ir::Value,
-        len: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_table_init(pos, seg_index, table_index, table, dst, src, len)
-    }
-
-    fn translate_elem_drop(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        seg_index: u32,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner.translate_elem_drop(pos, seg_index)
-    }
-
-    fn translate_ref_func(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        func_index: FuncIndex,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner.translate_ref_func(pos, func_index)
-    }
-
-    fn translate_custom_global_get(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        global_index: cranelift_wasm::GlobalIndex,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner.translate_custom_global_get(pos, global_index)
-    }
-
-    fn translate_custom_global_set(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        global_index: cranelift_wasm::GlobalIndex,
-        val: ir::Value,
-    ) -> cranelift_wasm::WasmResult<()> {
-        self.inner
-            .translate_custom_global_set(pos, global_index, val)
-    }
-
-    fn translate_atomic_wait(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::MemoryIndex,
-        heap: cranelift_wasm::Heap,
-        addr: ir::Value,
-        expected: ir::Value,
-        timeout: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner
-            .translate_atomic_wait(pos, index, heap, addr, expected, timeout)
-    }
-
-    fn translate_atomic_notify(
-        &mut self,
-        pos: cranelift_codegen::cursor::FuncCursor,
-        index: cranelift_wasm::MemoryIndex,
-        heap: cranelift_wasm::Heap,
-        addr: ir::Value,
-        count: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        self.inner
-            .translate_atomic_notify(pos, index, heap, addr, count)
-    }
-
-    fn heaps(
-        &self,
-    ) -> &cranelift_codegen::entity::PrimaryMap<cranelift_wasm::Heap, cranelift_wasm::HeapData>
-    {
-        self.inner.heaps()
-    }
-
-    fn relaxed_simd_deterministic(&self) -> bool {
-        self.config.relaxed_simd_deterministic
-    }
-
-    fn is_x86(&self) -> bool {
-        self.config.target.contains("x86_64")
-    }
-
-    fn use_x86_pmaddubsw_for_dot(&self) -> bool {
-        self.config.target.contains("x86_64")
-    }
-
-    fn translate_call_ref(
-        &mut self,
-        _builder: &mut cranelift_frontend::FunctionBuilder<'_>,
-        _ty: ir::SigRef,
-        _func: ir::Value,
-        _args: &[ir::Value],
-    ) -> cranelift_wasm::WasmResult<ir::Inst> {
-        unimplemented!()
-    }
-
-    fn translate_ref_i31(
-        &mut self,
-        _pos: cranelift_codegen::cursor::FuncCursor,
-        _val: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        unimplemented!()
-    }
-
-    fn translate_i31_get_s(
-        &mut self,
-        _pos: cranelift_codegen::cursor::FuncCursor,
-        _i31ref: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        unimplemented!()
-    }
-
-    fn translate_i31_get_u(
-        &mut self,
-        _pos: cranelift_codegen::cursor::FuncCursor,
-        _i31ref: ir::Value,
-    ) -> cranelift_wasm::WasmResult<ir::Value> {
-        unimplemented!()
-    }
-}
diff --git a/tests/disas/gc/externref-globals.wat b/tests/disas/gc/externref-globals.wat
new file mode 100644
index 000000000..95ce3ce51
--- /dev/null
+++ b/tests/disas/gc/externref-globals.wat
@@ -0,0 +1,186 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (global $x (mut externref) (ref.null extern))
+  (func (export "get") (result externref)
+    (global.get $x)
+  )
+  (func (export "set") (param externref)
+    (global.set $x (local.get 0))
+  )
+)
+
+;; function u0:0(i64 vmctx, i64) -> i32 tail {
+;;     ss0 = explicit_slot 4, align = 4
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     sig0 = (i64 vmctx, i32) -> i32 system_v
+;;     fn0 = colocated u1:26 sig0
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64):
+;;                                     v43 = iconst.i64 96
+;; @0034                               v4 = iadd v0, v43  ; v43 = 96
+;; @0034                               v5 = load.i32 notrap aligned v4
+;;                                     v44 = stack_addr.i64 ss0
+;;                                     store notrap v5, v44
+;;                                     v46 = iconst.i32 0
+;; @0034                               v6 = icmp eq v5, v46  ; v46 = 0
+;; @0034                               brif v6, block5, block2
+;;
+;;                                 block2:
+;; @0034                               v8 = load.i64 notrap aligned v0+56
+;; @0034                               v9 = load.i64 notrap aligned v8
+;; @0034                               v10 = load.i64 notrap aligned v8+8
+;; @0034                               v11 = icmp eq v9, v10
+;; @0034                               brif v11, block3, block4
+;;
+;;                                 block4:
+;; @0034                               v15 = uextend.i64 v5
+;; @0034                               v16 = iconst.i64 8
+;; @0034                               v17 = uadd_overflow_trap v15, v16, user65535  ; v16 = 8
+;; @0034                               v19 = uadd_overflow_trap v17, v16, user65535  ; v16 = 8
+;; @0034                               v14 = load.i64 notrap aligned readonly v0+48
+;; @0034                               v20 = icmp ult v19, v14
+;; @0034                               brif v20, block7, block6
+;;
+;;                                 block6 cold:
+;; @0034                               trap user65535
+;;
+;;                                 block7:
+;; @0034                               v13 = load.i64 notrap aligned readonly v0+40
+;; @0034                               v21 = iadd v13, v17
+;; @0034                               v22 = load.i64 notrap aligned v21
+;;                                     v40 = load.i32 notrap v44
+;; @0034                               v27 = uextend.i64 v40
+;;                                     v54 = iconst.i64 8
+;; @0034                               v29 = uadd_overflow_trap v27, v54, user65535  ; v54 = 8
+;; @0034                               v31 = uadd_overflow_trap v29, v54, user65535  ; v54 = 8
+;; @0034                               v32 = icmp ult v31, v14
+;; @0034                               brif v32, block9, block8
+;;
+;;                                 block8 cold:
+;; @0034                               trap user65535
+;;
+;;                                 block9:
+;;                                     v48 = iconst.i64 1
+;; @0034                               v23 = iadd.i64 v22, v48  ; v48 = 1
+;; @0034                               v33 = iadd.i64 v13, v29
+;; @0034                               store notrap aligned v23, v33
+;;                                     v39 = load.i32 notrap v44
+;; @0034                               store notrap aligned v39, v9
+;;                                     v51 = iconst.i64 4
+;; @0034                               v34 = iadd.i64 v9, v51  ; v51 = 4
+;; @0034                               store notrap aligned v34, v8
+;; @0034                               jump block5
+;;
+;;                                 block3 cold:
+;; @0034                               v36 = call fn0(v0, v5), stack_map=[i32 @ ss0+0]
+;; @0034                               jump block5
+;;
+;;                                 block5:
+;;                                     v37 = load.i32 notrap v44
+;; @0036                               jump block1
+;;
+;;                                 block1:
+;; @0036                               return v37
+;; }
+;;
+;; function u0:1(i64 vmctx, i64, i32) tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     sig0 = (i64 vmctx, i32 uext) system_v
+;;     fn0 = colocated u1:25 sig0
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;;                                     v54 = iconst.i64 96
+;; @003b                               v4 = iadd v0, v54  ; v54 = 96
+;; @003b                               v5 = load.i32 notrap aligned v4
+;;                                     v55 = iconst.i32 0
+;; @003b                               v6 = icmp eq v2, v55  ; v55 = 0
+;; @003b                               brif v6, block3, block2
+;;
+;;                                 block2:
+;; @003b                               v10 = uextend.i64 v2
+;; @003b                               v34 = iconst.i64 8
+;; @003b                               v12 = uadd_overflow_trap v10, v34, user65535  ; v34 = 8
+;; @003b                               v14 = uadd_overflow_trap v12, v34, user65535  ; v34 = 8
+;; @003b                               v32 = load.i64 notrap aligned readonly v0+48
+;; @003b                               v15 = icmp ult v14, v32
+;; @003b                               brif v15, block9, block8
+;;
+;;                                 block8 cold:
+;; @003b                               trap user65535
+;;
+;;                                 block9:
+;; @003b                               v31 = load.i64 notrap aligned readonly v0+40
+;; @003b                               v16 = iadd v31, v12
+;; @003b                               v17 = load.i64 notrap aligned v16
+;; @003b                               brif.i8 v15, block11, block10
+;;
+;;                                 block10 cold:
+;; @003b                               trap user65535
+;;
+;;                                 block11:
+;;                                     v56 = iconst.i64 1
+;; @003b                               v18 = iadd.i64 v17, v56  ; v56 = 1
+;; @003b                               store notrap aligned v18, v16
+;; @003b                               jump block3
+;;
+;;                                 block3:
+;;                                     v60 = iadd.i64 v0, v54  ; v54 = 96
+;; @003b                               store.i32 notrap aligned v2, v60
+;;                                     v61 = iconst.i32 0
+;;                                     v62 = icmp.i32 eq v5, v61  ; v61 = 0
+;; @003b                               brif v62, block7, block4
+;;
+;;                                 block4:
+;; @003b                               v33 = uextend.i64 v5
+;;                                     v63 = iconst.i64 8
+;; @003b                               v35 = uadd_overflow_trap v33, v63, user65535  ; v63 = 8
+;; @003b                               v37 = uadd_overflow_trap v35, v63, user65535  ; v63 = 8
+;;                                     v64 = load.i64 notrap aligned readonly v0+48
+;; @003b                               v38 = icmp ult v37, v64
+;; @003b                               brif v38, block13, block12
+;;
+;;                                 block12 cold:
+;; @003b                               trap user65535
+;;
+;;                                 block13:
+;;                                     v65 = load.i64 notrap aligned readonly v0+40
+;; @003b                               v39 = iadd v65, v35
+;; @003b                               v40 = load.i64 notrap aligned v39
+;;                                     v58 = iconst.i64 -1
+;; @003b                               v41 = iadd v40, v58  ; v58 = -1
+;;                                     v59 = iconst.i64 0
+;; @003b                               v42 = icmp eq v41, v59  ; v59 = 0
+;; @003b                               brif v42, block5, block6
+;;
+;;                                 block5 cold:
+;; @003b                               call fn0(v0, v5)
+;; @003b                               jump block7
+;;
+;;                                 block6:
+;; @003b                               brif.i8 v38, block15, block14
+;;
+;;                                 block14 cold:
+;; @003b                               trap user65535
+;;
+;;                                 block15:
+;;                                     v66 = iadd.i64 v40, v58  ; v58 = -1
+;; @003b                               store notrap aligned v66, v39
+;; @003b                               jump block7
+;;
+;;                                 block7:
+;; @003d                               jump block1
+;;
+;;                                 block1:
+;; @003d                               return
+;; }
diff --git a/tests/disas/gc/i31ref-globals.wat b/tests/disas/gc/i31ref-globals.wat
new file mode 100644
index 000000000..b172c6f35
--- /dev/null
+++ b/tests/disas/gc/i31ref-globals.wat
@@ -0,0 +1,47 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (global $x (mut i31ref) (ref.i31 (i32.const 42)))
+  (func (export "get") (result i31ref)
+    (global.get $x)
+  )
+  (func (export "set") (param i31ref)
+    (global.set $x (local.get 0))
+  )
+)
+
+;; function u0:0(i64 vmctx, i64) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64):
+;;                                     v6 = iconst.i64 96
+;; @0036                               v4 = iadd v0, v6  ; v6 = 96
+;; @0036                               v5 = load.i32 notrap aligned v4
+;; @0038                               jump block1
+;;
+;;                                 block1:
+;; @0038                               return v5
+;; }
+;;
+;; function u0:1(i64 vmctx, i64, i32) tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;;                                     v5 = iconst.i64 96
+;; @003d                               v4 = iadd v0, v5  ; v5 = 96
+;; @003d                               store notrap aligned v2, v4
+;; @003f                               jump block1
+;;
+;;                                 block1:
+;; @003f                               return
+;; }
diff --git a/tests/disas/ref-func-0.wat b/tests/disas/ref-func-0.wat
index d0dcc9d94..9b527227c 100644
--- a/tests/disas/ref-func-0.wat
+++ b/tests/disas/ref-func-0.wat
@@ -15,28 +15,137 @@
 
 ;; function u0:1(i64 vmctx, i64) -> i32, i32, i64, i64 tail {
 ;;     ss0 = explicit_slot 4, align = 4
+;;     ss1 = explicit_slot 4, align = 4
 ;;     gv0 = vmctx
 ;;     gv1 = load.i64 notrap aligned readonly gv0+8
 ;;     gv2 = load.i64 notrap aligned gv1
 ;;     gv3 = vmctx
-;;     sig0 = (i64 vmctx, i32 uext) -> i32 system_v
-;;     fn0 = colocated u1:27 sig0
+;;     sig0 = (i64 vmctx, i32) -> i32 system_v
+;;     fn0 = colocated u1:26 sig0
 ;;     stack_limit = gv2
 ;;
 ;;                                 block0(v0: i64, v1: i64):
 ;; @008f                               v6 = global_value.i64 gv3
-;; @008f                               v7 = iconst.i32 0
-;; @008f                               v8 = call fn0(v6, v7)  ; v7 = 0
+;; @008f                               v7 = iadd_imm v6, 112
+;; @008f                               v8 = load.i32 notrap aligned v7
 ;;                                     stack_store v8, ss0
-;; @0091                               v9 = global_value.i64 gv3
-;; @0091                               v10 = iconst.i32 1
-;; @0091                               v11 = call fn0(v9, v10), stack_map=[i32 @ ss0+0]  ; v10 = 1
-;; @0093                               v12 = global_value.i64 gv3
-;; @0093                               v13 = load.i64 notrap aligned table v12+144
-;; @0095                               v14 = global_value.i64 gv3
-;; @0095                               v15 = load.i64 notrap aligned table v14+160
-;;                                     v16 = stack_load.i32 ss0
-;; @0097                               jump block1(v16, v11, v13, v15)
+;;                                     v89 = stack_load.i32 ss0
+;; @008f                               v9 = icmp_imm eq v89, 0
+;; @008f                               brif v9, block5, block2
+;;
+;;                                 block2:
+;; @008f                               v10 = global_value.i64 gv3
+;; @008f                               v11 = load.i64 notrap aligned v10+56
+;; @008f                               v12 = load.i64 notrap aligned v11
+;; @008f                               v13 = load.i64 notrap aligned v11+8
+;; @008f                               v14 = icmp eq v12, v13
+;; @008f                               brif v14, block3, block4
+;;
+;;                                 block4:
+;; @008f                               v15 = global_value.i64 gv3
+;; @008f                               v16 = load.i64 notrap aligned readonly v15+40
+;; @008f                               v17 = load.i64 notrap aligned readonly v15+48
+;;                                     v88 = stack_load.i32 ss0
+;; @008f                               v18 = uextend.i64 v88
+;; @008f                               v19 = iconst.i64 8
+;; @008f                               v20 = uadd_overflow_trap v18, v19, user65535  ; v19 = 8
+;; @008f                               v21 = iconst.i64 8
+;; @008f                               v22 = uadd_overflow_trap v20, v21, user65535  ; v21 = 8
+;; @008f                               v23 = icmp ult v22, v17
+;; @008f                               trapz v23, user65535
+;; @008f                               v24 = iadd v16, v20
+;; @008f                               v25 = load.i64 notrap aligned v24
+;; @008f                               v26 = iadd_imm v25, 1
+;; @008f                               v27 = global_value.i64 gv3
+;; @008f                               v28 = load.i64 notrap aligned readonly v27+40
+;; @008f                               v29 = load.i64 notrap aligned readonly v27+48
+;;                                     v87 = stack_load.i32 ss0
+;; @008f                               v30 = uextend.i64 v87
+;; @008f                               v31 = iconst.i64 8
+;; @008f                               v32 = uadd_overflow_trap v30, v31, user65535  ; v31 = 8
+;; @008f                               v33 = iconst.i64 8
+;; @008f                               v34 = uadd_overflow_trap v32, v33, user65535  ; v33 = 8
+;; @008f                               v35 = icmp ult v34, v29
+;; @008f                               trapz v35, user65535
+;; @008f                               v36 = iadd v28, v32
+;; @008f                               store notrap aligned v26, v36
+;;                                     v86 = stack_load.i32 ss0
+;; @008f                               store notrap aligned v86, v12
+;; @008f                               v37 = iadd_imm.i64 v12, 4
+;; @008f                               store notrap aligned v37, v11
+;; @008f                               jump block5
+;;
+;;                                 block3 cold:
+;; @008f                               v38 = global_value.i64 gv3
+;;                                     v85 = stack_load.i32 ss0
+;; @008f                               v39 = call fn0(v38, v85), stack_map=[i32 @ ss0+0]
+;; @008f                               jump block5
+;;
+;;                                 block5:
+;; @0091                               v40 = global_value.i64 gv3
+;; @0091                               v41 = iadd_imm v40, 128
+;; @0091                               v42 = load.i32 notrap aligned v41
+;;                                     stack_store v42, ss1
+;;                                     v84 = stack_load.i32 ss1
+;; @0091                               v43 = icmp_imm eq v84, 0
+;; @0091                               brif v43, block9, block6
+;;
+;;                                 block6:
+;; @0091                               v44 = global_value.i64 gv3
+;; @0091                               v45 = load.i64 notrap aligned v44+56
+;; @0091                               v46 = load.i64 notrap aligned v45
+;; @0091                               v47 = load.i64 notrap aligned v45+8
+;; @0091                               v48 = icmp eq v46, v47
+;; @0091                               brif v48, block7, block8
+;;
+;;                                 block8:
+;; @0091                               v49 = global_value.i64 gv3
+;; @0091                               v50 = load.i64 notrap aligned readonly v49+40
+;; @0091                               v51 = load.i64 notrap aligned readonly v49+48
+;;                                     v83 = stack_load.i32 ss1
+;; @0091                               v52 = uextend.i64 v83
+;; @0091                               v53 = iconst.i64 8
+;; @0091                               v54 = uadd_overflow_trap v52, v53, user65535  ; v53 = 8
+;; @0091                               v55 = iconst.i64 8
+;; @0091                               v56 = uadd_overflow_trap v54, v55, user65535  ; v55 = 8
+;; @0091                               v57 = icmp ult v56, v51
+;; @0091                               trapz v57, user65535
+;; @0091                               v58 = iadd v50, v54
+;; @0091                               v59 = load.i64 notrap aligned v58
+;; @0091                               v60 = iadd_imm v59, 1
+;; @0091                               v61 = global_value.i64 gv3
+;; @0091                               v62 = load.i64 notrap aligned readonly v61+40
+;; @0091                               v63 = load.i64 notrap aligned readonly v61+48
+;;                                     v82 = stack_load.i32 ss1
+;; @0091                               v64 = uextend.i64 v82
+;; @0091                               v65 = iconst.i64 8
+;; @0091                               v66 = uadd_overflow_trap v64, v65, user65535  ; v65 = 8
+;; @0091                               v67 = iconst.i64 8
+;; @0091                               v68 = uadd_overflow_trap v66, v67, user65535  ; v67 = 8
+;; @0091                               v69 = icmp ult v68, v63
+;; @0091                               trapz v69, user65535
+;; @0091                               v70 = iadd v62, v66
+;; @0091                               store notrap aligned v60, v70
+;;                                     v81 = stack_load.i32 ss1
+;; @0091                               store notrap aligned v81, v46
+;; @0091                               v71 = iadd_imm.i64 v46, 4
+;; @0091                               store notrap aligned v71, v45
+;; @0091                               jump block9
+;;
+;;                                 block7 cold:
+;; @0091                               v72 = global_value.i64 gv3
+;;                                     v80 = stack_load.i32 ss1
+;; @0091                               v73 = call fn0(v72, v80), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
+;; @0091                               jump block9
+;;
+;;                                 block9:
+;; @0093                               v74 = global_value.i64 gv3
+;; @0093                               v75 = load.i64 notrap aligned table v74+144
+;; @0095                               v76 = global_value.i64 gv3
+;; @0095                               v77 = load.i64 notrap aligned table v76+160
+;;                                     v78 = stack_load.i32 ss0
+;;                                     v79 = stack_load.i32 ss1
+;; @0097                               jump block1(v78, v79, v75, v77)
 ;;
 ;;                                 block1(v2: i32, v3: i32, v4: i64, v5: i64):
 ;; @0097                               return v2, v3, v4, v5