Repository: bytecodealliance/wasmtime
Pull request #9355 (2024-10-01 20:59:40)

Update CI nightly to the latest nightly
Brings in a new warning which fires on generating a safe (either `&` or `&mut`) reference to a `static mut`. This then additionally refactors code to use `addr_of_mut!` appropriately and/or switch to other alternatives.

<!--
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
-->


github-actions[bot]: #### Subscribe to Label Action

cc @fitzgen

<details>
This issue or pull request has been labeled: "fuzzing", "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

* fitzgen: fuzzing

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/.github/actions/install-rust/action.yml b/.github/actions/install-rust/action.yml
index e0ebe61c4..5f6a287d3 100644
--- a/.github/actions/install-rust/action.yml
+++ b/.github/actions/install-rust/action.yml
@@ -36,7 +36,7 @@ runs:
         elif [ "${{ inputs.toolchain }}" = "msrv" ]; then
           echo "version=1.$msrv.0" >> "$GITHUB_OUTPUT"
         elif [ "${{ inputs.toolchain }}" = "wasmtime-ci-pinned-nightly" ]; then
-          echo "version=nightly-2024-09-05" >> "$GITHUB_OUTPUT"
+          echo "version=nightly-2024-10-01" >> "$GITHUB_OUTPUT"
         else
           echo "version=${{ inputs.toolchain }}" >> "$GITHUB_OUTPUT"
         fi
diff --git a/Cargo.lock b/Cargo.lock
index 25d585fe7..500058a8f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3762,12 +3762,12 @@ dependencies = [
  "semver",
  "serde",
  "serde_derive",
+ "smallvec",
  "target-lexicon",
  "wasm-encoder",
  "wasmparser",
  "wasmprinter",
  "wasmtime-component-util",
- "wasmtime-types",
  "wat",
 ]
 
@@ -3906,18 +3906,6 @@ dependencies = [
  "syn 2.0.60",
 ]
 
-[[package]]
-name = "wasmtime-types"
-version = "26.0.0"
-dependencies = [
- "anyhow",
- "cranelift-entity",
- "serde",
- "serde_derive",
- "smallvec",
- "wasmparser",
-]
-
 [[package]]
 name = "wasmtime-versioned-export-macros"
 version = "26.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index 3620a42f9..e4dbe38a3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -200,7 +200,6 @@ wasmtime-winch = { path = "crates/winch", version = "=26.0.0" }
 wasmtime-environ = { path = "crates/environ", version = "=26.0.0" }
 wasmtime-explorer = { path = "crates/explorer", version = "=26.0.0" }
 wasmtime-fiber = { path = "crates/fiber", version = "=26.0.0" }
-wasmtime-types = { path = "crates/types", version = "26.0.0" }
 wasmtime-jit-debug = { path = "crates/jit-debug", version = "=26.0.0" }
 wasmtime-wast = { path = "crates/wast", version = "=26.0.0" }
 wasmtime-wasi = { path = "crates/wasi", version = "26.0.0", default-features = false }
diff --git a/crates/component-util/src/lib.rs b/crates/component-util/src/lib.rs
index 642b8e349..0c987c44d 100644
--- a/crates/component-util/src/lib.rs
+++ b/crates/component-util/src/lib.rs
@@ -75,7 +75,7 @@ impl FlagsSize {
         } else if count <= 16 {
             FlagsSize::Size2
         } else {
-            let amt = ceiling_divide(count, 32);
+            let amt = count.div_ceil(32);
             if amt > (u8::MAX as usize) {
                 panic!("too many flags");
             }
@@ -84,11 +84,6 @@ impl FlagsSize {
     }
 }
 
-/// Divide `n` by `d`, rounding up in the case of a non-zero remainder.
-const fn ceiling_divide(n: usize, d: usize) -> usize {
-    (n + d - 1) / d
-}
-
 /// A simple bump allocator which can be used with modules
 pub const REALLOC_AND_FREE: &str = r#"
     (global $last (mut i32) (i32.const 8))
diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml
index ee7c0db73..b7d83c13c 100644
--- a/crates/environ/Cargo.toml
+++ b/crates/environ/Cargo.toml
@@ -21,9 +21,8 @@ all-features = true
 anyhow = { workspace = true }
 postcard = { workspace = true }
 cpp_demangle = { version = "0.4.3", optional = true }
-cranelift-entity = { workspace = true }
+cranelift-entity = { workspace = true, features = ['enable-serde'] }
 cranelift-bitset = { workspace = true, features = ['enable-serde'] }
-wasmtime-types = { workspace = true }
 wasmparser = { workspace = true, features = ['validate', 'serde', 'features'] }
 indexmap = { workspace = true, features = ["serde"] }
 serde = { workspace = true }
@@ -37,6 +36,7 @@ wasm-encoder = { workspace = true, optional = true }
 wasmprinter = { workspace = true, optional = true }
 wasmtime-component-util = { workspace = true, optional = true }
 semver = { workspace = true, optional = true, features = ['serde'] }
+smallvec = { workspace = true, features = ['serde'] }
 
 [dev-dependencies]
 clap = { workspace = true, features = ['default'] }
@@ -61,7 +61,6 @@ compile = [
 threads = ['std']
 wmemcheck = ['std']
 std = [
-  'wasmtime-types/std',
   'anyhow/std',
   'object/std',
   'wasmparser/std',
diff --git a/crates/environ/src/compile/module_artifacts.rs b/crates/environ/src/compile/module_artifacts.rs
index 6d071dc1b..d50c2696e 100644
--- a/crates/environ/src/compile/module_artifacts.rs
+++ b/crates/environ/src/compile/module_artifacts.rs
@@ -4,13 +4,13 @@
 use crate::prelude::*;
 use crate::{
     obj, CompiledFunctionInfo, CompiledModuleInfo, DebugInfoData, DefinedFuncIndex, FunctionLoc,
-    FunctionName, MemoryInitialization, Metadata, ModuleTranslation, PrimaryMap, Tunables,
+    FunctionName, MemoryInitialization, Metadata, ModuleInternedTypeIndex, ModuleTranslation,
+    PrimaryMap, Tunables,
 };
 use anyhow::{bail, Result};
 use object::write::{Object, SectionId, StandardSegment, WritableBuffer};
 use object::SectionKind;
 use std::ops::Range;
-use wasmtime_types::ModuleInternedTypeIndex;
 
 /// Helper structure to create an ELF file as a compilation artifact.
 ///
diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs
index a2648ecaa..9bfcc1ff8 100644
--- a/crates/environ/src/compile/module_environ.rs
+++ b/crates/environ/src/compile/module_environ.rs
@@ -4,10 +4,11 @@ use crate::module::{
 };
 use crate::prelude::*;
 use crate::{
-    DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, FuncIndex, GlobalIndex,
-    InitMemory, MemoryIndex, ModuleTypesBuilder, PrimaryMap, StaticMemoryInitializer, TableIndex,
-    TableInitialValue, Tunables, TypeConvert, TypeIndex, Unsigned, WasmError, WasmHeapType,
-    WasmResult, WasmValType, WasmparserTypeConverter,
+    ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex,
+    EntityIndex, EntityType, FuncIndex, GlobalIndex, IndexType, InitMemory, MemoryIndex,
+    ModuleInternedTypeIndex, ModuleTypesBuilder, PrimaryMap, SizeOverflow, StaticMemoryInitializer,
+    TableIndex, TableInitialValue, Tunables, TypeConvert, TypeIndex, Unsigned, WasmError,
+    WasmHeapTopType, WasmHeapType, WasmResult, WasmValType, WasmparserTypeConverter,
 };
 use anyhow::{bail, Result};
 use cranelift_entity::packed_option::ReservedValue;
@@ -21,9 +22,6 @@ use wasmparser::{
     FuncToValidate, FunctionBody, KnownCustom, NameSectionReader, Naming, Parser, Payload, TypeRef,
     Validator, ValidatorResources,
 };
-use wasmtime_types::{
-    ConstExpr, ConstOp, IndexType, ModuleInternedTypeIndex, SizeOverflow, WasmHeapTopType,
-};
 
 /// Object containing the standalone environment information.
 pub struct ModuleEnvironment<'a, 'data> {
@@ -307,9 +305,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
                             let interned_index = self.result.module.types[index];
                             self.result.module.num_imported_funcs += 1;
                             self.result.debuginfo.wasm_file.imported_func_count += 1;
-                            EntityType::Function(wasmtime_types::EngineOrModuleTypeIndex::Module(
-                                interned_index,
-                            ))
+                            EntityType::Function(EngineOrModuleTypeIndex::Module(interned_index))
                         }
                         TypeRef::Memory(ty) => {
                             self.result.module.num_imported_memories += 1;
@@ -872,10 +868,7 @@ impl TypeConvert for ModuleEnvironment<'_, '_> {
         WasmparserTypeConverter::new(&self.types, &self.result.module).lookup_heap_type(index)
     }
 
-    fn lookup_type_index(
-        &self,
-        index: wasmparser::UnpackedIndex,
-    ) -> wasmtime_types::EngineOrModuleTypeIndex {
+    fn lookup_type_index(&self, index: wasmparser::UnpackedIndex) -> EngineOrModuleTypeIndex {
         WasmparserTypeConverter::new(&self.types, &self.result.module).lookup_type_index(index)
     }
 }
diff --git a/crates/environ/src/compile/module_types.rs b/crates/environ/src/compile/module_types.rs
index e9048c232..97b9c0dd2 100644
--- a/crates/environ/src/compile/module_types.rs
+++ b/crates/environ/src/compile/module_types.rs
@@ -1,10 +1,10 @@
-use crate::{EntityRef, Module, ModuleTypes, TypeConvert};
+use crate::{
+    EngineOrModuleTypeIndex, EntityRef, Module, ModuleInternedRecGroupIndex,
+    ModuleInternedTypeIndex, ModuleTypes, TypeConvert, TypeIndex, WasmCompositeType, WasmFuncType,
+    WasmHeapType, WasmResult, WasmSubType,
+};
 use std::{borrow::Cow, collections::HashMap, ops::Index};
 use wasmparser::{UnpackedIndex, Validator, ValidatorId};
-use wasmtime_types::{
-    EngineOrModuleTypeIndex, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, TypeIndex,
-    WasmCompositeType, WasmFuncType, WasmHeapType, WasmResult, WasmSubType,
-};
 
 /// A type marking the start of a recursion group's definition.
 ///
diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs
index 4766ebdfe..1bf88819f 100644
--- a/crates/environ/src/component/dfg.rs
+++ b/crates/environ/src/component/dfg.rs
@@ -29,14 +29,13 @@
 
 use crate::component::*;
 use crate::prelude::*;
-use crate::{EntityIndex, EntityRef, PrimaryMap, WasmValType};
+use crate::{EntityIndex, EntityRef, ModuleInternedTypeIndex, PrimaryMap, WasmValType};
 use anyhow::Result;
 use indexmap::IndexMap;
 use std::collections::HashMap;
 use std::hash::Hash;
 use std::ops::Index;
 use wasmparser::types::ComponentCoreModuleTypeId;
-use wasmtime_types::ModuleInternedTypeIndex;
 
 #[derive(Default)]
 #[allow(missing_docs)]
diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs
index 401d4bafd..6fb367cca 100644
--- a/crates/environ/src/component/info.rs
+++ b/crates/environ/src/component/info.rs
@@ -48,9 +48,8 @@
 
 use crate::component::*;
 use crate::prelude::*;
-use crate::{EntityIndex, PrimaryMap, WasmValType};
+use crate::{EntityIndex, ModuleInternedTypeIndex, PrimaryMap, WasmValType};
 use serde_derive::{Deserialize, Serialize};
-use wasmtime_types::ModuleInternedTypeIndex;
 
 /// Metadata as a result of compiling a component.
 pub struct ComponentTranslation {
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index 81d345d91..ee7b9b89e 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -3,8 +3,9 @@ use crate::prelude::*;
 use crate::Module;
 use crate::ScopeVec;
 use crate::{
-    EntityIndex, ModuleEnvironment, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, Tunables,
-    TypeConvert, WasmHeapType, WasmValType,
+    EngineOrModuleTypeIndex, EntityIndex, ModuleEnvironment, ModuleInternedTypeIndex,
+    ModuleTranslation, ModuleTypesBuilder, PrimaryMap, Tunables, TypeConvert, WasmHeapType,
+    WasmResult, WasmValType,
 };
 use anyhow::anyhow;
 use anyhow::{bail, Result};
@@ -16,8 +17,6 @@ use wasmparser::types::{
     ComponentInstanceTypeId, Types,
 };
 use wasmparser::{Chunk, ComponentImportName, Encoding, Parser, Payload, Validator};
-use wasmtime_types::ModuleInternedTypeIndex;
-use wasmtime_types::WasmResult;
 
 mod adapt;
 pub use self::adapt::*;
@@ -987,10 +986,7 @@ mod pre_inlining {
             self.types.lookup_heap_type(index)
         }
 
-        fn lookup_type_index(
-            &self,
-            index: wasmparser::UnpackedIndex,
-        ) -> wasmtime_types::EngineOrModuleTypeIndex {
+        fn lookup_type_index(&self, index: wasmparser::UnpackedIndex) -> EngineOrModuleTypeIndex {
             self.types.lookup_type_index(index)
         }
     }
diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs
index afb6f6295..6953199bb 100644
--- a/crates/environ/src/component/translate/inline.rs
+++ b/crates/environ/src/component/translate/inline.rs
@@ -46,9 +46,9 @@
 //! final `Component`.
 
 use crate::component::translate::*;
+use crate::{EntityType, IndexType};
 use std::borrow::Cow;
 use wasmparser::types::{ComponentAnyTypeId, ComponentCoreModuleTypeId};
-use wasmtime_types::IndexType;
 
 pub(super) fn run(
     types: &mut ComponentTypesBuilder,
@@ -976,7 +976,7 @@ impl<'a> Inliner<'a> {
                 },
                 InstanceModule::Import(ty) => match &memory.item {
                     ExportItem::Name(name) => match types[*ty].exports[name] {
-                        wasmtime_types::EntityType::Memory(m) => match m.idx_type {
+                        EntityType::Memory(m) => match m.idx_type {
                             IndexType::I32 => false,
                             IndexType::I64 => true,
                         },
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index 5e70bd6be..8d53d9c09 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -1,14 +1,13 @@
 use crate::component::{MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
 use crate::prelude::*;
-use crate::{EntityType, ModuleTypes, PrimaryMap};
+use crate::{EntityType, ModuleInternedTypeIndex, ModuleTypes, PrimaryMap};
 use core::hash::{Hash, Hasher};
 use core::ops::Index;
 use serde_derive::{Deserialize, Serialize};
 use wasmparser::types;
 use wasmtime_component_util::{DiscriminantSize, FlagsSize};
-use wasmtime_types::ModuleInternedTypeIndex;
 
-pub use wasmtime_types::StaticModuleIndex;
+pub use crate::StaticModuleIndex;
 
 macro_rules! indices {
     ($(
diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs
index 85319865b..a26c8ffc5 100644
--- a/crates/environ/src/component/types_builder.rs
+++ b/crates/environ/src/component/types_builder.rs
@@ -1,8 +1,8 @@
 use crate::component::*;
 use crate::prelude::*;
 use crate::{
-    EntityType, Module, ModuleTypes, ModuleTypesBuilder, PrimaryMap, TypeConvert, WasmHeapType,
-    WasmValType,
+    EngineOrModuleTypeIndex, EntityType, Module, ModuleInternedTypeIndex, ModuleTypes,
+    ModuleTypesBuilder, PrimaryMap, TypeConvert, WasmHeapType, WasmValType,
 };
 use anyhow::{bail, Result};
 use cranelift_entity::EntityRef;
@@ -12,7 +12,6 @@ use std::ops::Index;
 use wasmparser::names::KebabString;
 use wasmparser::{types, Validator};
 use wasmtime_component_util::FlagsSize;
-use wasmtime_types::ModuleInternedTypeIndex;
 
 mod resources;
 pub use resources::ResourcesBuilder;
@@ -676,10 +675,7 @@ impl TypeConvert for ComponentTypesBuilder {
         panic!("heap types are not supported yet")
     }
 
-    fn lookup_type_index(
-        &self,
-        _index: wasmparser::UnpackedIndex,
-    ) -> wasmtime_types::EngineOrModuleTypeIndex {
+    fn lookup_type_index(&self, _index: wasmparser::UnpackedIndex) -> EngineOrModuleTypeIndex {
         panic!("typed references are not supported yet")
     }
 }
diff --git a/crates/types/src/error.rs b/crates/environ/src/error.rs
similarity index 98%
rename from crates/types/src/error.rs
rename to crates/environ/src/error.rs
index 27c25bbcc..f82bad97f 100644
--- a/crates/types/src/error.rs
+++ b/crates/environ/src/error.rs
@@ -42,6 +42,8 @@ pub enum WasmError {
 macro_rules! wasm_unsupported {
     ($($arg:tt)*) => { $crate::WasmError::Unsupported($crate::__format!($($arg)*)) }
 }
+#[doc(hidden)]
+pub use alloc::format as __format;
 
 impl From<wasmparser::BinaryReaderError> for WasmError {
     /// Convert from a `BinaryReaderError` to a `WasmError`.
diff --git a/crates/environ/src/gc.rs b/crates/environ/src/gc.rs
index a5813180c..a89c0d33b 100644
--- a/crates/environ/src/gc.rs
+++ b/crates/environ/src/gc.rs
@@ -13,10 +13,8 @@
 pub mod drc;
 
 use crate::prelude::*;
+use crate::{WasmArrayType, WasmCompositeType, WasmStorageType, WasmStructType, WasmValType};
 use core::alloc::Layout;
-use wasmtime_types::{
-    WasmArrayType, WasmCompositeType, WasmStorageType, WasmStructType, WasmValType,
-};
 
 /// Discriminant to check whether GC reference is an `i31ref` or not.
 pub const I31_DISCRIMINANT: u64 = 1;
diff --git a/crates/environ/src/lib.rs b/crates/environ/src/lib.rs
index 7768330b7..2b001ac1e 100644
--- a/crates/environ/src/lib.rs
+++ b/crates/environ/src/lib.rs
@@ -12,11 +12,12 @@
 extern crate std;
 extern crate alloc;
 
-pub use wasmtime_types::prelude;
+pub mod prelude;
 
 mod address_map;
 mod builtin;
 mod demangling;
+mod error;
 mod gc;
 mod module;
 mod module_artifacts;
@@ -27,11 +28,13 @@ mod scopevec;
 mod stack_map;
 mod trap_encoding;
 mod tunables;
+mod types;
 mod vmoffsets;
 
 pub use crate::address_map::*;
 pub use crate::builtin::*;
 pub use crate::demangling::*;
+pub use crate::error::*;
 pub use crate::gc::*;
 pub use crate::module::*;
 pub use crate::module_artifacts::*;
@@ -41,6 +44,7 @@ pub use crate::scopevec::ScopeVec;
 pub use crate::stack_map::StackMap;
 pub use crate::trap_encoding::*;
 pub use crate::tunables::*;
+pub use crate::types::*;
 pub use crate::vmoffsets::*;
 pub use object;
 
@@ -58,7 +62,6 @@ pub mod fact;
 // much easier to refer to everything through one crate rather than importing
 // one of three and making sure you're using the right one.
 pub use cranelift_entity::*;
-pub use wasmtime_types::*;
 
 /// Version number of this crate.
 pub const VERSION: &str = env!("CARGO_PKG_VERSION");
diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs
index 16507387a..8406eac02 100644
--- a/crates/environ/src/module.rs
+++ b/crates/environ/src/module.rs
@@ -1,12 +1,11 @@
 //! Data structures for representing decoded wasm modules.
 
 use crate::prelude::*;
-use crate::{PrimaryMap, Tunables};
+use crate::*;
 use alloc::collections::BTreeMap;
 use core::ops::Range;
 use cranelift_entity::{packed_option::ReservedValue, EntityRef};
 use serde_derive::{Deserialize, Serialize};
-use wasmtime_types::*;
 
 /// Implementation styles for WebAssembly linear memory.
 #[derive(Debug, Clone, Hash, Serialize, Deserialize)]
diff --git a/crates/environ/src/module_artifacts.rs b/crates/environ/src/module_artifacts.rs
index b479521cc..898720f30 100644
--- a/crates/environ/src/module_artifacts.rs
+++ b/crates/environ/src/module_artifacts.rs
@@ -2,12 +2,13 @@
 //! with `bincode` as part of a module's compilation process.
 
 use crate::prelude::*;
-use crate::{DefinedFuncIndex, FilePos, FuncIndex, Module, PrimaryMap, StackMap};
+use crate::{
+    DefinedFuncIndex, FilePos, FuncIndex, Module, ModuleInternedTypeIndex, PrimaryMap, StackMap,
+};
 use core::fmt;
 use core::ops::Range;
 use core::str;
 use serde_derive::{Deserialize, Serialize};
-use wasmtime_types::ModuleInternedTypeIndex;
 
 /// Secondary in-memory results of function compilation.
 #[derive(Serialize, Deserialize)]
diff --git a/crates/environ/src/module_types.rs b/crates/environ/src/module_types.rs
index a04e36624..4a96a85e7 100644
--- a/crates/environ/src/module_types.rs
+++ b/crates/environ/src/module_types.rs
@@ -1,8 +1,7 @@
-use crate::PrimaryMap;
+use crate::{ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, PrimaryMap, WasmSubType};
 use core::ops::{Index, Range};
 use cranelift_entity::{packed_option::PackedOption, SecondaryMap};
 use serde_derive::{Deserialize, Serialize};
-use wasmtime_types::{ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, WasmSubType};
 
 /// All types used in a core wasm module.
 ///
diff --git a/crates/types/src/prelude.rs b/crates/environ/src/prelude.rs
similarity index 95%
rename from crates/types/src/prelude.rs
rename to crates/environ/src/prelude.rs
index 70a430820..43168d1f1 100644
--- a/crates/types/src/prelude.rs
+++ b/crates/environ/src/prelude.rs
@@ -10,11 +10,11 @@
 //! use crate::*;
 //! ```
 //!
-//! Externally for crates that depend on `wasmtime-types` they should have this
+//! Externally for crates that depend on `wasmtime-environ` they should have this
 //! in the root of the crate:
 //!
 //! ```rust,ignore
-//! use wasmtime_types::prelude;
+//! use wasmtime_environ::prelude;
 //! ```
 //!
 //! and then `use crate::*` works as usual.
diff --git a/crates/types/src/lib.rs b/crates/environ/src/types.rs
similarity index 99%
rename from crates/types/src/lib.rs
rename to crates/environ/src/types.rs
index cb2b7189a..5d0aca7c8 100644
--- a/crates/types/src/lib.rs
+++ b/crates/environ/src/types.rs
@@ -1,19 +1,4 @@
-//! Internal dependency of Wasmtime and Cranelift that defines types for
-//! WebAssembly.
-
-#![no_std]
-
-extern crate alloc;
-#[cfg(feature = "std")]
-extern crate std;
-
-pub use wasmparser;
-
-#[doc(hidden)]
-pub use alloc::format as __format;
-
-pub mod prelude;
-
+use crate::{wasm_unsupported, WasmResult};
 use alloc::borrow::Cow;
 use alloc::boxed::Box;
 use core::{fmt, ops::Range};
@@ -21,9 +6,6 @@ use cranelift_entity::entity_impl;
 use serde_derive::{Deserialize, Serialize};
 use smallvec::SmallVec;
 
-mod error;
-pub use error::*;
-
 /// A trait for things that can trace all type-to-type edges, aka all type
 /// indices within this thing.
 pub trait TypeTrace {
@@ -244,7 +226,9 @@ impl WasmValType {
 /// WebAssembly reference type -- equivalent of `wasmparser`'s RefType
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct WasmRefType {
+    /// Whether or not this reference is nullable.
     pub nullable: bool,
+    /// The heap type that this reference contains.
     pub heap_type: WasmHeapType,
 }
 
@@ -265,10 +249,12 @@ impl TypeTrace for WasmRefType {
 }
 
 impl WasmRefType {
+    /// Shorthand for `externref`
     pub const EXTERNREF: WasmRefType = WasmRefType {
         nullable: true,
         heap_type: WasmHeapType::Extern,
     };
+    /// Shorthand for `funcref`
     pub const FUNCREF: WasmRefType = WasmRefType {
         nullable: true,
         heap_type: WasmHeapType::Func,
@@ -419,6 +405,7 @@ impl EngineOrModuleTypeIndex {
 
 /// WebAssembly heap type -- equivalent of `wasmparser`'s HeapType
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[allow(missing_docs)]
 pub enum WasmHeapType {
     // External types.
     Extern,
@@ -613,6 +600,7 @@ impl TypeTrace for WasmFuncType {
 }
 
 impl WasmFuncType {
+    /// Creates a new function type from the provided `params` and `returns`.
     #[inline]
     pub fn new(params: Box<[WasmValType]>, returns: Box<[WasmValType]>) -> Self {
         let non_i31_gc_ref_params_count = params
@@ -806,6 +794,7 @@ impl TypeTrace for WasmArrayType {
 /// A concrete struct type.
 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct WasmStructType {
+    /// The fields that make up this struct type.
     pub fields: Box<[WasmFieldType]>,
 }
 
@@ -843,6 +832,7 @@ impl TypeTrace for WasmStructType {
 
 /// A function, array, or struct type.
 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+#[allow(missing_docs)]
 pub enum WasmCompositeType {
     Array(WasmArrayType),
     Func(WasmFuncType),
@@ -859,6 +849,7 @@ impl fmt::Display for WasmCompositeType {
     }
 }
 
+#[allow(missing_docs)]
 impl WasmCompositeType {
     #[inline]
     pub fn is_array(&self) -> bool {
@@ -970,6 +961,7 @@ impl fmt::Display for WasmSubType {
     }
 }
 
+#[allow(missing_docs)]
 impl WasmSubType {
     #[inline]
     pub fn is_func(&self) -> bool {
@@ -1558,6 +1550,7 @@ impl ConstOp {
 
 /// The type that can be used to index into [Memory] and [Table].
 #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
+#[allow(missing_docs)]
 pub enum IndexType {
     I32,
     I64,
@@ -1565,6 +1558,7 @@ pub enum IndexType {
 
 /// The size range of resizeable storage associated with [Memory] types and [Table] types.
 #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
+#[allow(missing_docs)]
 pub struct Limits {
     pub min: u64,
     pub max: Option<u64>,
@@ -1710,6 +1704,7 @@ impl Memory {
 }
 
 #[derive(Copy, Clone, Debug)]
+#[allow(missing_docs)]
 pub struct SizeOverflow;
 
 impl fmt::Display for SizeOverflow {
@@ -1763,6 +1758,7 @@ impl From<wasmparser::TagType> for Tag {
 }
 
 /// Helpers used to convert a `wasmparser` type to a type in this crate.
+#[allow(missing_docs)]
 pub trait TypeConvert {
     /// Converts a wasmparser table type into a wasmtime type
     fn convert_global_type(&self, ty: &wasmparser::GlobalType) -> Global {
diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs
index c6c354d2e..7bf02b595 100644
--- a/crates/environ/src/vmoffsets.rs
+++ b/crates/environ/src/vmoffsets.rs
@@ -28,10 +28,9 @@
 
 use crate::{
     DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, FuncRefIndex,
-    GlobalIndex, MemoryIndex, Module, TableIndex,
+    GlobalIndex, MemoryIndex, Module, OwnedMemoryIndex, TableIndex,
 };
 use cranelift_entity::packed_option::ReservedValue;
-use wasmtime_types::OwnedMemoryIndex;
 
 #[cfg(target_pointer_width = "32")]
 fn cast_to_u32(sz: usize) -> u32 {
diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml
deleted file mode 100644
index 1fbbb8fde..000000000
--- a/crates/types/Cargo.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-[package]
-name = "wasmtime-types"
-version.workspace = true
-authors.workspace = true
-description = "WebAssembly type definitions for Cranelift"
-license = "Apache-2.0 WITH LLVM-exception"
-repository = "https://github.com/bytecodealliance/wasmtime"
-documentation = "https://docs.rs/wasmtime-types"
-edition.workspace = true
-rust-version.workspace = true
-
-[dependencies]
-anyhow = { workspace = true }
-cranelift-entity = { workspace = true, features = ['enable-serde'] }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-smallvec = { workspace = true, features = ["serde"] }
-wasmparser = { workspace = true, features = ["validate"] }
-
-[lints]
-workspace = true
-
-[features]
-std = ['wasmparser/std']
diff --git a/crates/types/LICENSE b/crates/types/LICENSE
deleted file mode 100644
index f9d81955f..000000000
--- a/crates/types/LICENSE
+++ /dev/null
@@ -1,220 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
---- LLVM Exceptions to the Apache 2.0 License ----
-
-As an exception, if, as a result of your compiling your source code, portions
-of this Software are embedded into an Object form of such source code, you
-may redistribute such embedded portions in such Object form without complying
-with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
-
-In addition, if you combine or link compiled forms of this Software with
-software that is licensed under the GPLv2 ("Combined Software") and if a
-court of competent jurisdiction determines that the patent provision (Section
-3), the indemnity provision (Section 9) or other Section of the License
-conflicts with the conditions of the GPLv2, you may retroactively and
-prospectively choose to deem waived or otherwise exclude such Section(s) of
-the License, but only in their entirety and only with respect to the Combined
-Software.
-
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index b7464ff1f..1806df36f 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -448,7 +448,7 @@ impl BumpAlloc {
             unreachable!("invalid alignment");
         }
         let align_offset = self.base.align_offset(align);
-        if align_offset >= self.len {
+        if align_offset > self.len {
             unreachable!("failed to allocate")
         }
         self.len -= align_offset;
diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs
index e85c277e1..21add3328 100644
--- a/crates/wasmtime/src/runtime/component/component.rs
+++ b/crates/wasmtime/src/runtime/component/component.rs
@@ -618,6 +618,30 @@ impl Component {
         self.inner.code.code_memory().mmap().image_range()
     }
 
+    /// Force initialization of copy-on-write images to happen here-and-now
+    /// instead of when they're requested during first instantiation.
+    ///
+    /// When [copy-on-write memory
+    /// initialization](crate::Config::memory_init_cow) is enabled then Wasmtime
+    /// will lazily create the initialization image for a component. This method
+    /// can be used to explicitly dictate when this initialization happens.
+    ///
+    /// Note that this largely only matters on Linux when memfd is used.
+    /// Otherwise the copy-on-write image typically comes from disk and in that
+    /// situation the creation of the image is trivial as the image is always
+    /// sourced from disk. On Linux, though, when memfd is used a memfd is
+    /// created and the initialization image is written to it.
+    ///
+    /// Also note that this method is not required to be called, it's available
+    /// as a performance optimization if required but is otherwise handled
+    /// automatically.
+    pub fn initialize_copy_on_write_image(&self) -> Result<()> {
+        for (_, module) in self.inner.static_modules.iter() {
+            module.initialize_copy_on_write_image()?;
+        }
+        Ok(())
+    }
+
     /// Looks up a specific export of this component by `name` optionally nested
     /// within the `instance` provided.
     ///
diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs
index 06ba5ed7c..9eb1468ce 100644
--- a/crates/wasmtime/src/runtime/types.rs
+++ b/crates/wasmtime/src/runtime/types.rs
@@ -81,7 +81,7 @@ impl Finality {
 #[derive(Clone, Hash)]
 pub enum ValType {
     // NB: the ordering of variants here is intended to match the ordering in
-    // `wasmtime_types::WasmType` to help improve codegen when converting.
+    // `wasmtime_environ::WasmType` to help improve codegen when converting.
     //
     /// Signed 32 bit integer.
     I32,
diff --git a/crates/wasmtime/src/runtime/vm/sys/unix/machports.rs b/crates/wasmtime/src/runtime/vm/sys/unix/machports.rs
index 2fa035355..ae85e53b2 100644
--- a/crates/wasmtime/src/runtime/vm/sys/unix/machports.rs
+++ b/crates/wasmtime/src/runtime/vm/sys/unix/machports.rs
@@ -55,7 +55,7 @@ use mach2::thread_act::*;
 use mach2::thread_status::*;
 use mach2::traps::*;
 use std::io;
-use std::mem::{self, MaybeUninit};
+use std::mem;
 use std::ptr::addr_of_mut;
 use std::thread;
 use wasmtime_environ::Trap;
@@ -69,7 +69,7 @@ pub struct TrapHandler {
     thread: Option<thread::JoinHandle<()>>,
 }
 
-static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
+static mut PREV_SIGBUS: libc::sigaction = unsafe { mem::zeroed() };
 
 impl TrapHandler {
     pub unsafe fn new() -> TrapHandler {
@@ -103,7 +103,7 @@ impl TrapHandler {
             handler.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
             handler.sa_sigaction = sigbus_handler as usize;
             libc::sigemptyset(&mut handler.sa_mask);
-            if libc::sigaction(libc::SIGBUS, &handler, PREV_SIGBUS.as_mut_ptr()) != 0 {
+            if libc::sigaction(libc::SIGBUS, &handler, addr_of_mut!(PREV_SIGBUS)) != 0 {
                 panic!(
                     "unable to install signal handler: {}",
                     io::Error::last_os_error(),
@@ -149,7 +149,7 @@ unsafe extern "C" fn sigbus_handler(
     });
 
     super::signals::delegate_signal_to_previous_handler(
-        PREV_SIGBUS.as_ptr(),
+        addr_of_mut!(PREV_SIGBUS),
         signum,
         siginfo,
         context,
diff --git a/crates/wasmtime/src/runtime/vm/sys/unix/signals.rs b/crates/wasmtime/src/runtime/vm/sys/unix/signals.rs
index 5198d6b05..d8a3d9293 100644
--- a/crates/wasmtime/src/runtime/vm/sys/unix/signals.rs
+++ b/crates/wasmtime/src/runtime/vm/sys/unix/signals.rs
@@ -4,8 +4,8 @@ use crate::runtime::vm::traphandlers::{tls, TrapRegisters, TrapTest};
 use crate::runtime::vm::VMContext;
 use std::cell::RefCell;
 use std::io;
-use std::mem::{self, MaybeUninit};
-use std::ptr::{self, null_mut};
+use std::mem;
+use std::ptr::{self, addr_of, addr_of_mut, null_mut};
 
 #[link(name = "wasmtime-helpers")]
 extern "C" {
@@ -26,10 +26,11 @@ extern "C" {
 pub type SignalHandler<'a> =
     dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
 
-static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
-static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
-static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
-static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
+const UNINIT_SIGACTION: libc::sigaction = unsafe { mem::zeroed() };
+static mut PREV_SIGSEGV: libc::sigaction = UNINIT_SIGACTION;
+static mut PREV_SIGBUS: libc::sigaction = UNINIT_SIGACTION;
+static mut PREV_SIGILL: libc::sigaction = UNINIT_SIGACTION;
+static mut PREV_SIGFPE: libc::sigaction = UNINIT_SIGACTION;
 
 pub struct TrapHandler;
 
@@ -82,20 +83,20 @@ impl TrapHandler {
 
 unsafe fn foreach_handler(mut f: impl FnMut(*mut libc::sigaction, i32)) {
     // Allow handling OOB with signals on all architectures
-    f(PREV_SIGSEGV.as_mut_ptr(), libc::SIGSEGV);
+    f(addr_of_mut!(PREV_SIGSEGV), libc::SIGSEGV);
 
     // Handle `unreachable` instructions which execute `ud2` right now
-    f(PREV_SIGILL.as_mut_ptr(), libc::SIGILL);
+    f(addr_of_mut!(PREV_SIGILL), libc::SIGILL);
 
     // x86 and s390x use SIGFPE to report division by zero
     if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
-        f(PREV_SIGFPE.as_mut_ptr(), libc::SIGFPE);
+        f(addr_of_mut!(PREV_SIGFPE), libc::SIGFPE);
     }
 
     // Sometimes we need to handle SIGBUS too:
     // - On Darwin, guard page accesses are raised as SIGBUS.
     if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
-        f(PREV_SIGBUS.as_mut_ptr(), libc::SIGBUS);
+        f(addr_of_mut!(PREV_SIGBUS), libc::SIGBUS);
     }
 
     // TODO(#1980): x86-32, if we support it, will also need a SIGFPE handler.
@@ -145,10 +146,10 @@ unsafe extern "C" fn trap_handler(
     context: *mut libc::c_void,
 ) {
     let previous = match signum {
-        libc::SIGSEGV => PREV_SIGSEGV.as_ptr(),
-        libc::SIGBUS => PREV_SIGBUS.as_ptr(),
-        libc::SIGFPE => PREV_SIGFPE.as_ptr(),
-        libc::SIGILL => PREV_SIGILL.as_ptr(),
+        libc::SIGSEGV => addr_of!(PREV_SIGSEGV),
+        libc::SIGBUS => addr_of!(PREV_SIGBUS),
+        libc::SIGFPE => addr_of!(PREV_SIGFPE),
+        libc::SIGILL => addr_of!(PREV_SIGILL),
         _ => panic!("unknown signal: {signum}"),
     };
     let handled = tls::with(|info| {
diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs
index 6f2fef5f3..802c8edeb 100644
--- a/fuzz/fuzz_targets/differential.rs
+++ b/fuzz/fuzz_targets/differential.rs
@@ -4,7 +4,7 @@ use libfuzzer_sys::arbitrary::{Result, Unstructured};
 use libfuzzer_sys::fuzz_target;
 use std::sync::atomic::AtomicUsize;
 use std::sync::atomic::Ordering::SeqCst;
-use std::sync::Once;
+use std::sync::{Mutex, Once};
 use wasmtime_fuzzing::generators::{Config, DiffValue, DiffValueType, SingleInstModule};
 use wasmtime_fuzzing::oracles::diff_wasmtime::WasmtimeInstance;
 use wasmtime_fuzzing::oracles::engine::{build_allowed_env_list, parse_env_list};
@@ -22,8 +22,8 @@ static SETUP: Once = Once::new();
 // - ALLOWED_ENGINES=wasmi,spec cargo +nightly fuzz run ...
 // - ALLOWED_ENGINES=-v8 cargo +nightly fuzz run ...
 // - ALLOWED_MODULES=single-inst cargo +nightly fuzz run ...
-static mut ALLOWED_ENGINES: Vec<&str> = vec![];
-static mut ALLOWED_MODULES: Vec<&str> = vec![];
+static ALLOWED_ENGINES: Mutex<Vec<&str>> = Mutex::new(vec![]);
+static ALLOWED_MODULES: Mutex<Vec<&str>> = Mutex::new(vec![]);
 
 // Statistics about what's actually getting executed during fuzzing
 static STATS: RuntimeStats = RuntimeStats::new();
@@ -45,10 +45,8 @@ fuzz_target!(|data: &[u8]| {
             &["wasm-smith", "single-inst"],
         );
 
-        unsafe {
-            ALLOWED_ENGINES = allowed_engines;
-            ALLOWED_MODULES = allowed_modules;
-        }
+        *ALLOWED_ENGINES.lock().unwrap() = allowed_engines;
+        *ALLOWED_MODULES.lock().unwrap() = allowed_modules;
     });
 
     // Errors in `run` have to do with not enough input in `data`, which we
@@ -69,10 +67,13 @@ fn execute_one(data: &[u8]) -> Result<()> {
     let mut config: Config = u.arbitrary()?;
     config.set_differential_config();
 
+    let allowed_engines = ALLOWED_ENGINES.lock().unwrap();
+    let allowed_modules = ALLOWED_MODULES.lock().unwrap();
+
     // Choose an engine that Wasmtime will be differentially executed against.
     // The chosen engine is then created, which might update `config`, and
     // returned as a trait object.
-    let lhs = u.choose(unsafe { &*std::ptr::addr_of!(ALLOWED_ENGINES) })?;
+    let lhs = u.choose(&allowed_engines)?;
     let mut lhs = match engine::build(&mut u, lhs, &mut config)? {
         Some(engine) => engine,
         // The chosen engine does not have support compiled into the fuzzer,
@@ -95,10 +96,10 @@ fn execute_one(data: &[u8]) -> Result<()> {
         let module = SingleInstModule::new(u, &config.module_config)?;
         Ok(module.to_bytes())
     };
-    if unsafe { ALLOWED_MODULES.is_empty() } {
+    if allowed_modules.is_empty() {
         panic!("unable to generate a module to fuzz against; check `ALLOWED_MODULES`")
     }
-    let wasm = match *u.choose(unsafe { ALLOWED_MODULES.as_slice() })? {
+    let wasm = match *u.choose(&allowed_modules)? {
         "wasm-smith" => build_wasm_smith_module(&mut u, &config)?,
         "single-inst" => build_single_inst_module(&mut u, &config)?,
         _ => unreachable!(),
diff --git a/scripts/publish.rs b/scripts/publish.rs
index 37d45ad5b..df0bb1248 100644
--- a/scripts/publish.rs
+++ b/scripts/publish.rs
@@ -23,7 +23,6 @@ const CRATES_TO_PUBLISH: &[&str] = &[
     // cranelift
     "cranelift-isle",
     "cranelift-entity",
-    "wasmtime-types",
     "cranelift-bforest",
     "cranelift-codegen-shared",
     "cranelift-codegen-meta",
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index 8f6c52a4f..b122a00ee 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -145,9 +145,6 @@ audit-as-crates-io = true
 [policy.wasmtime-slab]
 audit-as-crates-io = true
 
-[policy.wasmtime-types]
-audit-as-crates-io = true
-
 [policy.wasmtime-versioned-export-macros]
 audit-as-crates-io = false
 
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 6a94c6121..eb3344e53 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1856,18 +1856,6 @@ when = "2024-08-20"
 user-id = 73222
 user-login = "wasmtime-publish"
 
-[[publisher.wasmtime-types]]
-version = "23.0.1"
-when = "2024-07-22"
-user-id = 73222
-user-login = "wasmtime-publish"
-
-[[publisher.wasmtime-types]]
-version = "24.0.0"
-when = "2024-08-20"
-user-id = 73222
-user-login = "wasmtime-publish"
-
 [[publisher.wasmtime-wasi]]
 version = "23.0.1"
 when = "2024-07-22"
diff --git a/winch/codegen/src/codegen/bounds.rs b/winch/codegen/src/codegen/bounds.rs
index 613dd2f1b..331ff0fb2 100644
--- a/winch/codegen/src/codegen/bounds.rs
+++ b/winch/codegen/src/codegen/bounds.rs
@@ -5,7 +5,7 @@ use super::env::{HeapData, HeapStyle};
 use crate::{
     abi::{scratch, vmctx},
     codegen::CodeGenContext,
-    isa::reg::Reg,
+    isa::reg::{writable, Reg},
     masm::{IntCmpKind, MacroAssembler, OperandSize, RegImm, TrapCode},
     stack::TypedReg,
 };
@@ -93,19 +93,19 @@ where
     match (heap.max_size, &heap.style) {
         // Constant size, no need to perform a load.
         (Some(max_size), HeapStyle::Dynamic) if heap.min_size == max_size => {
-            masm.mov(RegImm::i64(max_size as i64), dst, ptr_size)
+            masm.mov(writable!(dst), RegImm::i64(max_size as i64), ptr_size)
         }
         (_, HeapStyle::Dynamic) => {
             let scratch = scratch!(M);
             let base = if let Some(offset) = heap.import_from {
                 let addr = masm.address_at_vmctx(offset);
-                masm.load_ptr(addr, scratch);
+                masm.load_ptr(addr, writable!(scratch));
                 scratch
             } else {
                 vmctx!(M)
             };
             let addr = masm.address_at_reg(base, heap.current_length_offset);
-            masm.load_ptr(addr, dst);
+            masm.load_ptr(addr, writable!(dst));
         }
         (_, HeapStyle::Static { .. }) => unreachable!("Loading dynamic bounds of a static heap"),
     }
@@ -134,7 +134,7 @@ pub(crate) fn ensure_index_and_offset<M: MacroAssembler>(
         // overflow check, and return 0 as the offset.
         Err(_) => {
             masm.checked_uadd(
-                index.as_typed_reg().into(),
+                writable!(index.as_typed_reg().into()),
                 index.as_typed_reg().into(),
                 RegImm::i64(offset as i64),
                 heap_ty_size,
@@ -175,9 +175,9 @@ where
         // Conditionally assign 0 to the register holding the base address if
         // the comparison kind is met.
         let tmp = context.any_gpr(masm);
-        masm.mov(RegImm::i64(0), tmp, ptr_size);
+        masm.mov(writable!(tmp), RegImm::i64(0), ptr_size);
         let cmp_kind = emit_check_condition(masm, bounds, index);
-        masm.cmov(tmp, addr, cmp_kind, ptr_size);
+        masm.cmov(writable!(addr), tmp, cmp_kind, ptr_size);
         context.free_reg(tmp);
         addr
     }
@@ -200,7 +200,7 @@ pub(crate) fn load_heap_addr_unchecked<M>(
         // If the WebAssembly memory is imported, load the address into
         // the scratch register.
         let scratch = scratch!(M);
-        masm.load_ptr(masm.address_at_vmctx(offset), scratch);
+        masm.load_ptr(masm.address_at_vmctx(offset), writable!(scratch));
         scratch
     } else {
         // Else if the WebAssembly memory is defined in the current module,
@@ -209,12 +209,17 @@ pub(crate) fn load_heap_addr_unchecked<M>(
     };
 
     // Load the base of the memory into the `addr` register.
-    masm.load_ptr(masm.address_at_reg(base, heap.offset), dst);
+    masm.load_ptr(masm.address_at_reg(base, heap.offset), writable!(dst));
     // Start by adding the index to the heap base addr.
     let index_reg = index.as_typed_reg().reg;
-    masm.add(dst, dst, index_reg.into(), ptr_size);
+    masm.add(writable!(dst), dst, index_reg.into(), ptr_size);
 
     if offset.as_u32() > 0 {
-        masm.add(dst, dst, RegImm::i64(offset.as_u32() as i64), ptr_size);
+        masm.add(
+            writable!(dst),
+            dst,
+            RegImm::i64(offset.as_u32() as i64),
+            ptr_size,
+        );
     }
 }
diff --git a/winch/codegen/src/codegen/call.rs b/winch/codegen/src/codegen/call.rs
index 50d501db9..ee3b4415c 100644
--- a/winch/codegen/src/codegen/call.rs
+++ b/winch/codegen/src/codegen/call.rs
@@ -63,6 +63,7 @@ use crate::{
         CalleeKind, ContextArgs, MacroAssembler, MemMoveDirection, OperandSize, SPOffset,
         VMContextLoc,
     },
+    reg::writable,
     reg::Reg,
     stack::Val,
     FuncEnv,
@@ -186,11 +187,11 @@ impl FnCall {
             });
         let callee_vmctx_offset = vmoffsets.vmctx_vmfunction_import_vmctx(index);
         let callee_vmctx_addr = masm.address_at_vmctx(callee_vmctx_offset);
-        masm.load_ptr(callee_vmctx_addr, callee_vmctx);
+        masm.load_ptr(callee_vmctx_addr, writable!(callee_vmctx));
 
         let callee_body_offset = vmoffsets.vmctx_vmfunction_import_wasm_call(index);
         let callee_addr = masm.address_at_vmctx(callee_body_offset);
-        masm.load_ptr(callee_addr, callee);
+        masm.load_ptr(callee_addr, writable!(callee));
 
         (
             CalleeKind::indirect(callee),
@@ -224,13 +225,13 @@ impl FnCall {
         // the function call.
         masm.load_ptr(
             masm.address_at_reg(funcref_ptr, ptr.vm_func_ref_vmctx().into()),
-            callee_vmctx,
+            writable!(callee_vmctx),
         );
 
         // Load the function pointer to be called.
         masm.load_ptr(
             masm.address_at_reg(funcref_ptr, ptr.vm_func_ref_wasm_call().into()),
-            funcref,
+            writable!(funcref),
         );
         context.free_reg(funcref_ptr);
 
@@ -250,7 +251,7 @@ impl FnCall {
         {
             match (context_arg, operand) {
                 (VMContextLoc::Pinned, ABIOperand::Reg { ty, reg, .. }) => {
-                    masm.mov(vmctx!(M).into(), *reg, (*ty).into());
+                    masm.mov(writable!(*reg), vmctx!(M).into(), (*ty).into());
                 }
                 (VMContextLoc::Pinned, ABIOperand::Stack { ty, offset, .. }) => {
                     let addr = masm.address_at_sp(SPOffset::from_u32(*offset));
@@ -258,7 +259,7 @@ impl FnCall {
                 }
 
                 (VMContextLoc::Reg(src), ABIOperand::Reg { ty, reg, .. }) => {
-                    masm.mov((*src).into(), *reg, (*ty).into());
+                    masm.mov(writable!(*reg), (*src).into(), (*ty).into());
                 }
 
                 (VMContextLoc::Reg(src), ABIOperand::Stack { ty, offset, .. }) => {
@@ -313,14 +314,14 @@ impl FnCall {
 
             match operand {
                 &ABIOperand::Reg { ty, reg, .. } => {
-                    masm.load_addr(addr, reg, ty.into());
+                    masm.load_addr(addr, writable!(reg), ty.into());
                 }
                 &ABIOperand::Stack { ty, offset, .. } => {
                     let slot = masm.address_at_sp(SPOffset::from_u32(offset));
                     // Don't rely on `ABI::scratch_for` as we always use
                     // an int register as the return pointer.
                     let scratch = scratch!(M);
-                    masm.load_addr(addr, scratch, ty.into());
+                    masm.load_addr(addr, writable!(scratch), ty.into());
                     masm.store(scratch.into(), slot, ty.into());
                 }
             }
diff --git a/winch/codegen/src/codegen/context.rs b/winch/codegen/src/codegen/context.rs
index f58513337..300b12c77 100644
--- a/winch/codegen/src/codegen/context.rs
+++ b/winch/codegen/src/codegen/context.rs
@@ -6,7 +6,7 @@ use crate::{
     frame::Frame,
     isa::reg::RegClass,
     masm::{MacroAssembler, OperandSize, RegImm, SPOffset, ShiftKind, StackSlot},
-    reg::Reg,
+    reg::{writable, Reg},
     regalloc::RegAlloc,
     stack::{Stack, TypedReg, Val},
 };
@@ -161,7 +161,7 @@ impl<'a> CodeGenContext<'a> {
         if val.is_mem() {
             let mem = val.unwrap_mem();
             debug_assert_eq!(mem.slot.offset.as_u32(), masm.sp_offset().as_u32());
-            masm.pop(reg, val.ty().into());
+            masm.pop(writable!(reg), val.ty().into());
         } else {
             self.move_val_to_reg(&val, reg, masm);
             // Free the source value if it is a register.
@@ -192,12 +192,12 @@ impl<'a> CodeGenContext<'a> {
                 let slot = self.frame.get_wasm_local(local.index);
                 let scratch = scratch!(M);
                 let local_addr = masm.local_address(&slot);
-                masm.load(local_addr, scratch, size);
+                masm.load(local_addr, writable!(scratch), size);
                 masm.store(scratch.into(), addr, size);
             }
             Val::Memory(_) => {
                 let scratch = scratch!(M, &ty);
-                masm.pop(scratch, size);
+                masm.pop(writable!(scratch), size);
                 masm.store(scratch.into(), addr, size);
             }
         }
@@ -207,20 +207,20 @@ impl<'a> CodeGenContext<'a> {
     pub fn move_val_to_reg<M: MacroAssembler>(&self, src: &Val, dst: Reg, masm: &mut M) {
         let size: OperandSize = src.ty().into();
         match src {
-            Val::Reg(tr) => masm.mov(RegImm::reg(tr.reg), dst, size),
-            Val::I32(imm) => masm.mov(RegImm::i32(*imm), dst, size),
-            Val::I64(imm) => masm.mov(RegImm::i64(*imm), dst, size),
-            Val::F32(imm) => masm.mov(RegImm::f32(imm.bits()), dst, size),
-            Val::F64(imm) => masm.mov(RegImm::f64(imm.bits()), dst, size),
-            Val::V128(imm) => masm.mov(RegImm::v128(*imm), dst, size),
+            Val::Reg(tr) => masm.mov(writable!(dst), RegImm::reg(tr.reg), size),
+            Val::I32(imm) => masm.mov(writable!(dst), RegImm::i32(*imm), size),
+            Val::I64(imm) => masm.mov(writable!(dst), RegImm::i64(*imm), size),
+            Val::F32(imm) => masm.mov(writable!(dst), RegImm::f32(imm.bits()), size),
+            Val::F64(imm) => masm.mov(writable!(dst), RegImm::f64(imm.bits()), size),
+            Val::V128(imm) => masm.mov(writable!(dst), RegImm::v128(*imm), size),
             Val::Local(local) => {
                 let slot = self.frame.get_wasm_local(local.index);
                 let addr = masm.local_address(&slot);
-                masm.load(addr, dst, size);
+                masm.load(addr, writable!(dst), size);
             }
             Val::Memory(mem) => {
                 let addr = masm.address_from_sp(mem.slot.offset);
-                masm.load(addr, dst, size);
+                masm.load(addr, writable!(dst), size);
             }
         }
     }
@@ -339,7 +339,7 @@ impl<'a> CodeGenContext<'a> {
                 .expect("i32 const value at stack top");
             let typed_reg = self.pop_to_reg(masm, None);
             masm.shift_ir(
-                typed_reg.reg,
+                writable!(typed_reg.reg),
                 val as u64,
                 typed_reg.reg,
                 kind,
@@ -364,7 +364,7 @@ impl<'a> CodeGenContext<'a> {
                 .expect("i64 const value at stack top");
             let typed_reg = self.pop_to_reg(masm, None);
             masm.shift_ir(
-                typed_reg.reg,
+                writable!(typed_reg.reg),
                 val as u64,
                 typed_reg.reg,
                 kind,
@@ -559,7 +559,7 @@ impl<'a> CodeGenContext<'a> {
         M: MacroAssembler,
     {
         let addr = masm.local_address(&self.frame.vmctx_slot);
-        masm.load_ptr(addr, vmctx!(M));
+        masm.load_ptr(addr, writable!(vmctx!(M)));
     }
 
     /// Spill locals and registers to memory.
@@ -583,7 +583,7 @@ impl<'a> CodeGenContext<'a> {
                 let slot = frame.get_wasm_local(local.index);
                 let addr = masm.local_address(&slot);
                 let scratch = scratch!(M, &slot.ty);
-                masm.load(addr, scratch, slot.ty.into());
+                masm.load(addr, writable!(scratch), slot.ty.into());
                 let stack_slot = masm.push(scratch, slot.ty.into());
                 *v = Val::mem(slot.ty, stack_slot);
             }
diff --git a/winch/codegen/src/codegen/control.rs b/winch/codegen/src/codegen/control.rs
index 1d6d10ed9..d9fc7fc7e 100644
--- a/winch/codegen/src/codegen/control.rs
+++ b/winch/codegen/src/codegen/control.rs
@@ -10,6 +10,7 @@ use super::{CodeGenContext, OperandSize, Reg, TypedReg};
 use crate::{
     abi::{ABIOperand, ABIResults, ABISig, RetArea, ABI},
     masm::{IntCmpKind, MacroAssembler, MemMoveDirection, RegImm, SPOffset},
+    reg::writable,
     stack::Val,
     CallingConvention,
 };
@@ -918,7 +919,7 @@ impl ControlStackFrame {
                     let base = context
                         .without::<_, M, _>(results.regs(), masm, |cx, masm| cx.any_gpr(masm));
                     let local_addr = masm.local_address(&slot);
-                    masm.load_ptr(local_addr, base);
+                    masm.load_ptr(local_addr, writable!(base));
                     Some(base)
                 }
                 _ => None,
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index f8cd10204..362da834c 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -1,7 +1,7 @@
 use crate::{
     abi::{scratch, vmctx, ABIOperand, ABISig, RetArea},
     codegen::BlockSig,
-    isa::reg::Reg,
+    isa::reg::{writable, Reg},
     masm::{
         ExtendKind, IntCmpKind, MacroAssembler, OperandSize, RegImm, SPOffset, ShiftKind, TrapCode,
     },
@@ -135,8 +135,11 @@ where
         // We need to use the vmctx parameter before pinning it for stack checking.
         self.masm.prologue(vmctx);
         // Pin the `VMContext` pointer.
-        self.masm
-            .mov(vmctx.into(), vmctx!(M), self.env.ptr_type().into());
+        self.masm.mov(
+            writable!(vmctx!(M)),
+            vmctx.into(),
+            self.env.ptr_type().into(),
+        );
 
         self.masm.reserve_stack(self.context.frame.locals_size);
 
@@ -345,7 +348,7 @@ where
         // Load the signatures address into the scratch register.
         self.masm.load(
             self.masm.address_at_vmctx(signatures_base_offset.into()),
-            scratch,
+            writable!(scratch),
             ptr_size,
         );
 
@@ -353,7 +356,7 @@ where
         let caller_id = self.context.any_gpr(self.masm);
         self.masm.load(
             self.masm.address_at_reg(scratch, sig_offset),
-            caller_id,
+            writable!(caller_id),
             sig_size,
         );
 
@@ -361,7 +364,7 @@ where
         self.masm.load(
             self.masm
                 .address_at_reg(funcref_ptr, funcref_sig_offset.into()),
-            callee_id,
+            writable!(callee_id),
             sig_size,
         );
 
@@ -455,7 +458,7 @@ where
         let addr = if data.imported {
             let global_base = self.masm.address_at_reg(vmctx!(M), data.offset);
             let scratch = scratch!(M);
-            self.masm.load_ptr(global_base, scratch);
+            self.masm.load_ptr(global_base, writable!(scratch));
             self.masm.address_at_reg(scratch, 0)
         } else {
             self.masm.address_at_reg(vmctx!(M), data.offset)
@@ -489,7 +492,7 @@ where
         let base = self.context.any_gpr(self.masm);
 
         let elem_addr = self.emit_compute_table_elem_addr(index.into(), base, &table_data);
-        self.masm.load_ptr(elem_addr, elem_value);
+        self.masm.load_ptr(elem_addr, writable!(elem_value));
         // Free the register used as base, once we have loaded the element
         // address into the element value register.
         self.context.free_reg(base);
@@ -532,7 +535,7 @@ where
         self.masm.bind(defined);
         let imm = RegImm::i64(FUNCREF_MASK as i64);
         let dst = top.into();
-        self.masm.and(dst, dst, imm, top.ty.into());
+        self.masm.and(writable!(dst), dst, imm, top.ty.into());
 
         self.masm.bind(cont);
     }
@@ -605,8 +608,8 @@ where
                 // * The memory64 proposal specifies that the index is bound to
                 // the heap type instead of hardcoding it to 32-bits (i32).
                 self.masm.mov(
+                    writable!(index_offset_and_access_size),
                     index_reg.into(),
-                    index_offset_and_access_size,
                     heap.ty.into(),
                 );
                 // Perform
@@ -620,7 +623,7 @@ where
                 // result could be clamped, resulting in an erroneus overflow
                 // check.
                 self.masm.checked_uadd(
-                    index_offset_and_access_size,
+                    writable!(index_offset_and_access_size),
                     index_offset_and_access_size,
                     RegImm::i64(offset_with_access_size as i64),
                     ptr_size,
@@ -763,7 +766,7 @@ where
             };
 
             let src = self.masm.address_at_reg(addr, 0);
-            self.masm.wasm_load(src, dst, size, sextend);
+            self.masm.wasm_load(src, writable!(dst), size, sextend);
             self.context.stack.push(TypedReg::new(ty, dst).into());
             self.context.free_reg(addr);
         }
@@ -798,11 +801,12 @@ where
             // If the table data declares a particular offset base,
             // load the address into a register to further use it as
             // the table address.
-            self.masm.load_ptr(self.masm.address_at_vmctx(offset), base);
+            self.masm
+                .load_ptr(self.masm.address_at_vmctx(offset), writable!(base));
         } else {
             // Else, simply move the vmctx register into the addr register as
             // the base to calculate the table address.
-            self.masm.mov(vmctx!(M).into(), base, ptr_size);
+            self.masm.mov(writable!(base), vmctx!(M).into(), ptr_size);
         };
 
         // OOB check.
@@ -810,7 +814,8 @@ where
             .masm
             .address_at_reg(base, table_data.current_elems_offset);
         let bound_size = table_data.current_elements_size;
-        self.masm.load(bound_addr, bound, bound_size.into());
+        self.masm
+            .load(bound_addr, writable!(bound), bound_size.into());
         self.masm.cmp(index, bound.into(), bound_size);
         self.masm.trapif(IntCmpKind::GeU, TRAP_TABLE_OUT_OF_BOUNDS);
 
@@ -818,25 +823,29 @@ where
         // element address.
         // Moving the value of the index register to the scratch register
         // also avoids overwriting the context of the index register.
-        self.masm.mov(index.into(), scratch, bound_size);
+        self.masm.mov(writable!(scratch), index.into(), bound_size);
         self.masm.mul(
-            scratch,
+            writable!(scratch),
             scratch,
             RegImm::i32(table_data.element_size.bytes() as i32),
             table_data.element_size,
         );
-        self.masm
-            .load_ptr(self.masm.address_at_reg(base, table_data.offset), base);
+        self.masm.load_ptr(
+            self.masm.address_at_reg(base, table_data.offset),
+            writable!(base),
+        );
         // Copy the value of the table base into a temporary register
         // so that we can use it later in case of a misspeculation.
-        self.masm.mov(base.into(), tmp, ptr_size);
+        self.masm.mov(writable!(tmp), base.into(), ptr_size);
         // Calculate the address of the table element.
-        self.masm.add(base, base, scratch.into(), ptr_size);
+        self.masm
+            .add(writable!(base), base, scratch.into(), ptr_size);
         if self.env.table_access_spectre_mitigation() {
             // Perform a bounds check and override the value of the
             // table element address in case the index is out of bounds.
             self.masm.cmp(index, bound.into(), OperandSize::S32);
-            self.masm.cmov(tmp, base, IntCmpKind::GeU, ptr_size);
+            self.masm
+                .cmov(writable!(base), tmp, IntCmpKind::GeU, ptr_size);
         }
         self.context.free_reg(bound);
         self.context.free_reg(tmp);
@@ -851,16 +860,20 @@ where
 
         if let Some(offset) = table_data.import_from {
             self.masm
-                .load_ptr(self.masm.address_at_vmctx(offset), scratch);
+                .load_ptr(self.masm.address_at_vmctx(offset), writable!(scratch));
         } else {
-            self.masm.mov(vmctx!(M).into(), scratch, ptr_size);
+            self.masm
+                .mov(writable!(scratch), vmctx!(M).into(), ptr_size);
         };
 
         let size_addr = self
             .masm
             .address_at_reg(scratch, table_data.current_elems_offset);
-        self.masm
-            .load(size_addr, size, table_data.current_elements_size.into());
+        self.masm.load(
+            size_addr,
+            writable!(size),
+            table_data.current_elements_size.into(),
+        );
 
         self.context.stack.push(TypedReg::i32(size).into());
     }
@@ -872,7 +885,7 @@ where
 
         let base = if let Some(offset) = heap_data.import_from {
             self.masm
-                .load_ptr(self.masm.address_at_vmctx(offset), scratch);
+                .load_ptr(self.masm.address_at_vmctx(offset), writable!(scratch));
             scratch
         } else {
             vmctx!(M)
@@ -881,12 +894,12 @@ where
         let size_addr = self
             .masm
             .address_at_reg(base, heap_data.current_length_offset);
-        self.masm.load_ptr(size_addr, size_reg);
+        self.masm.load_ptr(size_addr, writable!(size_reg));
         // Emit a shift to get the size in pages rather than in bytes.
         let dst = TypedReg::new(heap_data.ty, size_reg);
         let pow = heap_data.page_size_log2;
         self.masm.shift_ir(
-            dst.reg,
+            writable!(dst.reg),
             pow as u64,
             dst.into(),
             ShiftKind::ShrU,
diff --git a/winch/codegen/src/isa/aarch64/asm.rs b/winch/codegen/src/isa/aarch64/asm.rs
index d77f68b0e..2e63478e7 100644
--- a/winch/codegen/src/isa/aarch64/asm.rs
+++ b/winch/codegen/src/isa/aarch64/asm.rs
@@ -2,7 +2,10 @@
 
 use super::{address::Address, regs};
 use crate::masm::{ExtendKind, FloatCmpKind, IntCmpKind, RoundingMode, ShiftKind};
-use crate::{masm::OperandSize, reg::Reg};
+use crate::{
+    masm::OperandSize,
+    reg::{writable, Reg, WritableReg},
+};
 use cranelift_codegen::isa::aarch64::inst::{
     BitOp, BranchTarget, Cond, CondBrKind, FPULeftShiftImm, FPUOp1, FPUOp2,
     FPUOpRI::{self, UShr32, UShr64},
@@ -122,8 +125,8 @@ impl Assembler {
     }
 
     /// Load a constant into a register.
-    pub fn load_constant(&mut self, imm: u64, rd: Reg) {
-        let writable = Writable::from_reg(rd.into());
+    pub fn load_constant(&mut self, imm: u64, rd: WritableReg) {
+        let writable = rd.map(Into::into);
         Inst::load_constant(writable, imm, &mut |_| writable)
             .into_iter()
             .for_each(|i| self.emit(i));
@@ -188,23 +191,23 @@ impl Assembler {
     }
 
     /// Load a signed register.
-    pub fn sload(&mut self, addr: Address, rd: Reg, size: OperandSize) {
+    pub fn sload(&mut self, addr: Address, rd: WritableReg, size: OperandSize) {
         self.ldr(addr, rd, size, true);
     }
 
     /// Load an unsigned register.
-    pub fn uload(&mut self, addr: Address, rd: Reg, size: OperandSize) {
+    pub fn uload(&mut self, addr: Address, rd: WritableReg, size: OperandSize) {
         self.ldr(addr, rd, size, false);
     }
 
     /// Load a register.
-    fn ldr(&mut self, addr: Address, rd: Reg, size: OperandSize, signed: bool) {
+    fn ldr(&mut self, addr: Address, rd: WritableReg, size: OperandSize, signed: bool) {
         use OperandSize::*;
-        let writable_reg = Writable::from_reg(rd.into());
+        let writable_reg = rd.map(Into::into);
         let mem: AMode = addr.try_into().unwrap();
         let flags = MemFlags::trusted();
 
-        let inst = match (rd.is_int(), signed, size) {
+        let inst = match (rd.to_reg().is_int(), signed, size) {
             (_, false, S8) => Inst::ULoad8 {
                 rd: writable_reg,
                 mem,
@@ -275,8 +278,8 @@ impl Assembler {
     }
 
     /// Register to register move.
-    pub fn mov_rr(&mut self, rm: Reg, rd: Reg, size: OperandSize) {
-        let writable_rd = Writable::from_reg(rd.into());
+    pub fn mov_rr(&mut self, rm: Reg, rd: WritableReg, size: OperandSize) {
+        let writable_rd = rd.map(Into::into);
         self.emit(Inst::Mov {
             size: size.into(),
             rd: writable_rd,
@@ -285,8 +288,8 @@ impl Assembler {
     }
 
     /// Floating point register to register move.
-    pub fn fmov_rr(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
-        let writable = Writable::from_reg(rd.into());
+    pub fn fmov_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
+        let writable = rd.map(Into::into);
         let inst = match size {
             OperandSize::S32 => Inst::FpuMove32 {
                 rd: writable,
@@ -302,8 +305,8 @@ impl Assembler {
         self.emit(inst);
     }
 
-    pub fn mov_to_fpu(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
-        let writable_rd = Writable::from_reg(rd.into());
+    pub fn mov_to_fpu(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
+        let writable_rd = rd.map(Into::into);
         self.emit(Inst::MovToFpu {
             size: size.into(),
             rd: writable_rd,
@@ -321,18 +324,18 @@ impl Assembler {
     }
 
     /// Add with three registers.
-    pub fn add_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn add_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_alu_rrr_extend(ALUOp::Add, rm, rn, rd, size);
     }
 
     /// Add immediate and register.
-    pub fn add_ir(&mut self, imm: u64, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn add_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
         let alu_op = ALUOp::Add;
         if let Some(imm) = Imm12::maybe_from_u64(imm) {
             self.emit_alu_rri(alu_op, imm, rn, rd, size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
+            self.load_constant(imm, writable!(scratch));
             self.emit_alu_rrr_extend(alu_op, scratch, rn, rd, size);
         }
     }
@@ -348,176 +351,190 @@ impl Assembler {
     }
 
     /// Subtract with three registers.
-    pub fn sub_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn sub_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_alu_rrr_extend(ALUOp::Sub, rm, rn, rd, size);
     }
 
     /// Subtract immediate and register.
-    pub fn sub_ir(&mut self, imm: u64, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn sub_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
         let alu_op = ALUOp::Sub;
         if let Some(imm) = Imm12::maybe_from_u64(imm) {
             self.emit_alu_rri(alu_op, imm, rn, rd, size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
+            self.load_constant(imm, writable!(scratch));
             self.emit_alu_rrr_extend(alu_op, scratch, rn, rd, size);
         }
     }
 
     /// Subtract with three registers, setting flags.
     pub fn subs_rrr(&mut self, rm: Reg, rn: Reg, size: OperandSize) {
-        self.emit_alu_rrr_extend(ALUOp::SubS, rm, rn, regs::zero(), size);
+        self.emit_alu_rrr_extend(ALUOp::SubS, rm, rn, writable!(regs::zero()), size);
     }
 
     /// Subtract immediate and register, setting flags.
     pub fn subs_ir(&mut self, imm: u64, rn: Reg, size: OperandSize) {
         let alu_op = ALUOp::SubS;
         if let Some(imm) = Imm12::maybe_from_u64(imm) {
-            self.emit_alu_rri(alu_op, imm, rn, regs::zero(), size);
+            self.emit_alu_rri(alu_op, imm, rn, writable!(regs::zero()), size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
-            self.emit_alu_rrr_extend(alu_op, scratch, rn, regs::zero(), size);
+            self.load_constant(imm, writable!(scratch));
+            self.emit_alu_rrr_extend(alu_op, scratch, rn, writable!(regs::zero()), size);
         }
     }
 
     /// Multiply with three registers.
-    pub fn mul_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn mul_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_alu_rrrr(ALUOp3::MAdd, rm, rn, rd, regs::zero(), size);
     }
 
     /// Multiply immediate and register.
-    pub fn mul_ir(&mut self, imm: u64, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn mul_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
         let scratch = regs::scratch();
-        self.load_constant(imm, scratch);
+        self.load_constant(imm, writable!(scratch));
         self.emit_alu_rrrr(ALUOp3::MAdd, scratch, rn, rd, regs::zero(), size);
     }
 
     /// And with three registers.
-    pub fn and_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn and_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_alu_rrr(ALUOp::And, rm, rn, rd, size);
     }
 
     /// And immediate and register.
-    pub fn and_ir(&mut self, imm: u64, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn and_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
         let alu_op = ALUOp::And;
         let cl_size: inst::OperandSize = size.into();
         if let Some(imm) = ImmLogic::maybe_from_u64(imm, cl_size.to_ty()) {
             self.emit_alu_rri_logic(alu_op, imm, rn, rd, size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
+            self.load_constant(imm, writable!(scratch));
             self.emit_alu_rrr(alu_op, scratch, rn, rd, size);
         }
     }
 
     /// Or with three registers.
-    pub fn or_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn or_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_alu_rrr(ALUOp::Orr, rm, rn, rd, size);
     }
 
     /// Or immediate and register.
-    pub fn or_ir(&mut self, imm: u64, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn or_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
         let alu_op = ALUOp::Orr;
         let cl_size: inst::OperandSize = size.into();
         if let Some(imm) = ImmLogic::maybe_from_u64(imm, cl_size.to_ty()) {
             self.emit_alu_rri_logic(alu_op, imm, rn, rd, size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
+            self.load_constant(imm, writable!(scratch));
             self.emit_alu_rrr(alu_op, scratch, rn, rd, size);
         }
     }
 
     /// Xor with three registers.
-    pub fn xor_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn xor_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_alu_rrr(ALUOp::Eor, rm, rn, rd, size);
     }
 
     /// Xor immediate and register.
-    pub fn xor_ir(&mut self, imm: u64, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn xor_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
         let alu_op = ALUOp::Eor;
         let cl_size: inst::OperandSize = size.into();
         if let Some(imm) = ImmLogic::maybe_from_u64(imm, cl_size.to_ty()) {
             self.emit_alu_rri_logic(alu_op, imm, rn, rd, size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
+            self.load_constant(imm, writable!(scratch));
             self.emit_alu_rrr(alu_op, scratch, rn, rd, size);
         }
     }
 
     /// Shift with three registers.
-    pub fn shift_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, kind: ShiftKind, size: OperandSize) {
+    pub fn shift_rrr(
+        &mut self,
+        rm: Reg,
+        rn: Reg,
+        rd: WritableReg,
+        kind: ShiftKind,
+        size: OperandSize,
+    ) {
         let shift_op = self.shift_kind_to_alu_op(kind, rm, size);
         self.emit_alu_rrr(shift_op, rm, rn, rd, size);
     }
 
     /// Shift immediate and register.
-    pub fn shift_ir(&mut self, imm: u64, rn: Reg, rd: Reg, kind: ShiftKind, size: OperandSize) {
+    pub fn shift_ir(
+        &mut self,
+        imm: u64,
+        rn: Reg,
+        rd: WritableReg,
+        kind: ShiftKind,
+        size: OperandSize,
+    ) {
         let shift_op = self.shift_kind_to_alu_op(kind, rn, size);
 
         if let Some(imm) = ImmShift::maybe_from_u64(imm) {
             self.emit_alu_rri_shift(shift_op, imm, rn, rd, size);
         } else {
             let scratch = regs::scratch();
-            self.load_constant(imm, scratch);
+            self.load_constant(imm, writable!(scratch));
             self.emit_alu_rrr(shift_op, scratch, rn, rd, size);
         }
     }
 
     /// Count Leading Zeros.
-    pub fn clz(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn clz(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_bit_rr(BitOp::Clz, rn, rd, size);
     }
 
     /// Reverse Bits reverses the bit order in a register.
-    pub fn rbit(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn rbit(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_bit_rr(BitOp::RBit, rn, rd, size);
     }
 
     /// Float add with three registers.
-    pub fn fadd_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fadd_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rrr(FPUOp2::Add, rm, rn, rd, size);
     }
 
     /// Float sub with three registers.
-    pub fn fsub_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fsub_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rrr(FPUOp2::Sub, rm, rn, rd, size);
     }
 
     /// Float multiply with three registers.
-    pub fn fmul_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fmul_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rrr(FPUOp2::Mul, rm, rn, rd, size);
     }
 
     /// Float division with three registers.
-    pub fn fdiv_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fdiv_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rrr(FPUOp2::Div, rm, rn, rd, size);
     }
 
     /// Float max with three registers.
-    pub fn fmax_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fmax_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rrr(FPUOp2::Max, rm, rn, rd, size);
     }
 
     /// Float min with three registers.
-    pub fn fmin_rrr(&mut self, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fmin_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rrr(FPUOp2::Min, rm, rn, rd, size);
     }
 
     /// Float neg with two registers.
-    pub fn fneg_rr(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fneg_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rr(FPUOp1::Neg, rn, rd, size);
     }
 
     /// Float abs with two registers.
-    pub fn fabs_rr(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fabs_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rr(FPUOp1::Abs, rn, rd, size);
     }
 
     /// Float sqrt with two registers.
-    pub fn fsqrt_rr(&mut self, rn: Reg, rd: Reg, size: OperandSize) {
+    pub fn fsqrt_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit_fpu_rr(FPUOp1::Sqrt, rn, rd, size);
     }
 
@@ -538,9 +555,9 @@ impl Assembler {
     }
 
     /// Float unsigned shift right with two registers and an immediate.
-    pub fn fushr_rri(&mut self, rn: Reg, rd: Reg, amount: u8, size: OperandSize) {
+    pub fn fushr_rri(&mut self, rn: Reg, rd: WritableReg, amount: u8, size: OperandSize) {
         let imm = FPURightShiftImm {
-            amount: amount,
+            amount,
             lane_size_in_bits: size.num_bits(),
         };
         let ushr = match size {
@@ -553,9 +570,16 @@ impl Assembler {
 
     /// Float unsigned shift left and insert with three registers
     /// and an immediate.
-    pub fn fsli_rri_mod(&mut self, ri: Reg, rn: Reg, rd: Reg, amount: u8, size: OperandSize) {
+    pub fn fsli_rri_mod(
+        &mut self,
+        ri: Reg,
+        rn: Reg,
+        rd: WritableReg,
+        amount: u8,
+        size: OperandSize,
+    ) {
         let imm = FPULeftShiftImm {
-            amount: amount,
+            amount,
             lane_size_in_bits: size.num_bits(),
         };
         let sli = match size {
@@ -579,7 +603,7 @@ impl Assembler {
     pub fn cvt_float_to_float(
         &mut self,
         rn: Reg,
-        rd: Reg,
+        rd: WritableReg,
         src_size: OperandSize,
         dst_size: OperandSize,
     ) {
@@ -591,7 +615,7 @@ impl Assembler {
         self.emit(Inst::FpuRR {
             fpu_op,
             size,
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
         });
     }
@@ -643,9 +667,9 @@ impl Assembler {
 
     /// Conditional Set sets the destination register to 1 if the condition
     /// is true, and otherwise sets it to 0.
-    pub fn cset(&mut self, rd: Reg, cond: Cond) {
+    pub fn cset(&mut self, rd: WritableReg, cond: Cond) {
         self.emit(Inst::CSet {
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             cond,
         });
     }
@@ -660,9 +684,9 @@ impl Assembler {
         });
     }
 
-    pub fn extend(&mut self, rn: Reg, rd: Reg, kind: ExtendKind) {
+    pub fn extend(&mut self, rn: Reg, rd: WritableReg, kind: ExtendKind) {
         self.emit(Inst::Extend {
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             signed: kind.signed(),
             from_bits: kind.from_bits(),
@@ -672,16 +696,16 @@ impl Assembler {
 
     /// Bitwise AND (shifted register), setting flags.
     pub fn ands_rr(&mut self, rn: Reg, rm: Reg, size: OperandSize) {
-        self.emit_alu_rrr(ALUOp::AndS, rm, rn, regs::zero(), size);
+        self.emit_alu_rrr(ALUOp::AndS, rm, rn, writable!(regs::zero()), size);
     }
 
     // Helpers for ALU operations.
 
-    fn emit_alu_rri(&mut self, op: ALUOp, imm: Imm12, rn: Reg, rd: Reg, size: OperandSize) {
+    fn emit_alu_rri(&mut self, op: ALUOp, imm: Imm12, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRRImm12 {
             alu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             imm12: imm,
         });
@@ -692,13 +716,13 @@ impl Assembler {
         op: ALUOp,
         imm: ImmLogic,
         rn: Reg,
-        rd: Reg,
+        rd: WritableReg,
         size: OperandSize,
     ) {
         self.emit(Inst::AluRRImmLogic {
             alu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             imml: imm,
         });
@@ -709,82 +733,97 @@ impl Assembler {
         op: ALUOp,
         imm: ImmShift,
         rn: Reg,
-        rd: Reg,
+        rd: WritableReg,
         size: OperandSize,
     ) {
         self.emit(Inst::AluRRImmShift {
             alu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             immshift: imm,
         });
     }
 
-    fn emit_alu_rrr(&mut self, op: ALUOp, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    fn emit_alu_rrr(&mut self, op: ALUOp, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRRR {
             alu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             rm: rm.into(),
         });
     }
 
-    fn emit_alu_rrr_extend(&mut self, op: ALUOp, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    fn emit_alu_rrr_extend(
+        &mut self,
+        op: ALUOp,
+        rm: Reg,
+        rn: Reg,
+        rd: WritableReg,
+        size: OperandSize,
+    ) {
         self.emit(Inst::AluRRRExtend {
             alu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             rm: rm.into(),
             extendop: ExtendOp::UXTX,
         });
     }
 
-    fn emit_alu_rrrr(&mut self, op: ALUOp3, rm: Reg, rn: Reg, rd: Reg, ra: Reg, size: OperandSize) {
+    fn emit_alu_rrrr(
+        &mut self,
+        op: ALUOp3,
+        rm: Reg,
+        rn: Reg,
+        rd: WritableReg,
+        ra: Reg,
+        size: OperandSize,
+    ) {
         self.emit(Inst::AluRRRR {
             alu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             rm: rm.into(),
             ra: ra.into(),
         });
     }
 
-    fn emit_fpu_rrr(&mut self, op: FPUOp2, rm: Reg, rn: Reg, rd: Reg, size: OperandSize) {
+    fn emit_fpu_rrr(&mut self, op: FPUOp2, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit(Inst::FpuRRR {
             fpu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
             rm: rm.into(),
         });
     }
 
-    fn emit_fpu_rri(&mut self, op: FPUOpRI, rn: Reg, rd: Reg) {
+    fn emit_fpu_rri(&mut self, op: FPUOpRI, rn: Reg, rd: WritableReg) {
         self.emit(Inst::FpuRRI {
             fpu_op: op,
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
         });
     }
 
-    fn emit_fpu_rri_mod(&mut self, op: FPUOpRIMod, ri: Reg, rn: Reg, rd: Reg) {
+    fn emit_fpu_rri_mod(&mut self, op: FPUOpRIMod, ri: Reg, rn: Reg, rd: WritableReg) {
         self.emit(Inst::FpuRRIMod {
             fpu_op: op,
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             ri: ri.into(),
             rn: rn.into(),
         });
     }
 
-    fn emit_fpu_rr(&mut self, op: FPUOp1, rn: Reg, rd: Reg, size: OperandSize) {
+    fn emit_fpu_rr(&mut self, op: FPUOp1, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit(Inst::FpuRR {
             fpu_op: op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
         });
     }
@@ -797,11 +836,11 @@ impl Assembler {
         });
     }
 
-    fn emit_bit_rr(&mut self, op: BitOp, rn: Reg, rd: Reg, size: OperandSize) {
+    fn emit_bit_rr(&mut self, op: BitOp, rn: Reg, rd: WritableReg, size: OperandSize) {
         self.emit(Inst::BitRR {
             op,
             size: size.into(),
-            rd: Writable::from_reg(rd.into()),
+            rd: rd.map(Into::into),
             rn: rn.into(),
         });
     }
@@ -816,7 +855,7 @@ impl Assembler {
             ShiftKind::Rotr => ALUOp::RotR,
             ShiftKind::Rotl => {
                 // neg(r) is sub(zero, r).
-                self.emit_alu_rrr(ALUOp::Sub, regs::zero(), r, r, size);
+                self.emit_alu_rrr(ALUOp::Sub, regs::zero(), r, writable!(r), size);
                 ALUOp::RotR
             }
         }
diff --git a/winch/codegen/src/isa/aarch64/masm.rs b/winch/codegen/src/isa/aarch64/masm.rs
index 8c9c7df04..eb12ceb75 100644
--- a/winch/codegen/src/isa/aarch64/masm.rs
+++ b/winch/codegen/src/isa/aarch64/masm.rs
@@ -2,7 +2,7 @@ use super::{abi::Aarch64ABI, address::Address, asm::Assembler, regs};
 use crate::{
     abi::local::LocalSlot,
     codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv},
-    isa::reg::Reg,
+    isa::reg::{writable, Reg, WritableReg},
     masm::{
         CalleeKind, DivKind, ExtendKind, FloatCmpKind, Imm as I, IntCmpKind,
         MacroAssembler as Masm, OperandSize, RegImm, RemKind, RoundingMode, SPOffset, ShiftKind,
@@ -51,7 +51,7 @@ impl Masm for MacroAssembler {
         let addr = Address::pre_indexed_from_sp(-16);
 
         self.asm.stp(fp, lr, addr);
-        self.asm.mov_rr(sp, fp, OperandSize::S64);
+        self.asm.mov_rr(sp, writable!(fp), OperandSize::S64);
         self.move_sp_to_shadow_sp();
     }
 
@@ -76,7 +76,8 @@ impl Masm for MacroAssembler {
         }
 
         let sp = regs::sp();
-        self.asm.sub_ir(bytes as u64, sp, sp, OperandSize::S64);
+        self.asm
+            .sub_ir(bytes as u64, sp, writable!(sp), OperandSize::S64);
         self.move_sp_to_shadow_sp();
 
         self.increment_sp(bytes);
@@ -88,7 +89,8 @@ impl Masm for MacroAssembler {
         }
 
         let sp = regs::sp();
-        self.asm.add_ir(bytes as u64, sp, sp, OperandSize::S64);
+        self.asm
+            .add_ir(bytes as u64, sp, writable!(sp), OperandSize::S64);
         self.move_sp_to_shadow_sp();
 
         self.decrement_sp(bytes);
@@ -138,10 +140,11 @@ impl Masm for MacroAssembler {
                     I::V128(_) => unreachable!(),
                 };
                 let scratch = regs::scratch();
-                self.asm.load_constant(imm, scratch);
+                self.asm.load_constant(imm, writable!(scratch));
                 if v.is_float() {
                     let float_scratch = regs::float_scratch();
-                    self.asm.mov_to_fpu(scratch, float_scratch, v.size());
+                    self.asm
+                        .mov_to_fpu(scratch, writable!(float_scratch), v.size());
                     float_scratch
                 } else {
                     scratch
@@ -165,18 +168,18 @@ impl Masm for MacroAssembler {
         todo!()
     }
 
-    fn load(&mut self, src: Address, dst: Reg, size: OperandSize) {
+    fn load(&mut self, src: Address, dst: WritableReg, size: OperandSize) {
         self.asm.uload(src, dst, size);
     }
 
-    fn load_ptr(&mut self, src: Self::Address, dst: Reg) {
+    fn load_ptr(&mut self, src: Self::Address, dst: WritableReg) {
         self.load(src, dst, self.ptr_size);
     }
 
     fn wasm_load(
         &mut self,
         src: Self::Address,
-        dst: Reg,
+        dst: WritableReg,
         size: OperandSize,
         kind: Option<ExtendKind>,
     ) {
@@ -189,11 +192,11 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn load_addr(&mut self, _src: Self::Address, _dst: Reg, _size: OperandSize) {
+    fn load_addr(&mut self, _src: Self::Address, _dst: WritableReg, _size: OperandSize) {
         todo!()
     }
 
-    fn pop(&mut self, dst: Reg, size: OperandSize) {
+    fn pop(&mut self, dst: WritableReg, size: OperandSize) {
         let addr = self.address_from_sp(SPOffset::from_u32(self.sp_offset));
         self.asm.uload(addr, dst, size);
         self.free_stack(size.bytes());
@@ -207,7 +210,7 @@ impl Masm for MacroAssembler {
         self.asm.finalize(base)
     }
 
-    fn mov(&mut self, src: RegImm, dst: Reg, size: OperandSize) {
+    fn mov(&mut self, dst: WritableReg, src: RegImm, size: OperandSize) {
         match (src, dst) {
             (RegImm::Imm(v), rd) => {
                 let imm = match v {
@@ -219,14 +222,14 @@ impl Masm for MacroAssembler {
                 };
 
                 let scratch = regs::scratch();
-                self.asm.load_constant(imm, scratch);
-                match rd.class() {
+                self.asm.load_constant(imm, writable!(scratch));
+                match rd.to_reg().class() {
                     RegClass::Int => self.asm.mov_rr(scratch, rd, size),
                     RegClass::Float => self.asm.mov_to_fpu(scratch, rd, size),
                     _ => todo!(),
                 }
             }
-            (RegImm::Reg(rs), rd) => match (rs.class(), rd.class()) {
+            (RegImm::Reg(rs), rd) => match (rs.class(), rd.to_reg().class()) {
                 (RegClass::Int, RegClass::Int) => self.asm.mov_rr(rs, rd, size),
                 (RegClass::Float, RegClass::Float) => self.asm.fmov_rr(rs, rd, size),
                 (RegClass::Int, RegClass::Float) => self.asm.mov_to_fpu(rs, rd, size),
@@ -235,11 +238,11 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn cmov(&mut self, _src: Reg, _dst: Reg, _cc: IntCmpKind, _size: OperandSize) {
+    fn cmov(&mut self, _dst: WritableReg, _src: Reg, _cc: IntCmpKind, _size: OperandSize) {
         todo!()
     }
 
-    fn add(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+    fn add(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         match (rhs, lhs, dst) {
             (RegImm::Imm(v), rn, rd) => {
                 let imm = match v {
@@ -259,7 +262,7 @@ impl Masm for MacroAssembler {
 
     fn checked_uadd(
         &mut self,
-        _dst: Reg,
+        _dst: WritableReg,
         _lhs: Reg,
         _rhs: RegImm,
         _size: OperandSize,
@@ -268,7 +271,7 @@ impl Masm for MacroAssembler {
         todo!()
     }
 
-    fn sub(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+    fn sub(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         match (rhs, lhs, dst) {
             (RegImm::Imm(v), rn, rd) => {
                 let imm = match v {
@@ -286,7 +289,7 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn mul(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+    fn mul(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         match (rhs, lhs, dst) {
             (RegImm::Imm(v), rn, rd) => {
                 let imm = match v {
@@ -304,46 +307,46 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn float_add(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_add(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         self.asm.fadd_rrr(rhs, lhs, dst, size);
     }
 
-    fn float_sub(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_sub(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         self.asm.fsub_rrr(rhs, lhs, dst, size);
     }
 
-    fn float_mul(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_mul(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         self.asm.fmul_rrr(rhs, lhs, dst, size);
     }
 
-    fn float_div(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_div(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         self.asm.fdiv_rrr(rhs, lhs, dst, size);
     }
 
-    fn float_min(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_min(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         self.asm.fmin_rrr(rhs, lhs, dst, size);
     }
 
-    fn float_max(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_max(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         self.asm.fmax_rrr(rhs, lhs, dst, size);
     }
 
-    fn float_copysign(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
+    fn float_copysign(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
         let max_shift = match size {
             OperandSize::S32 => 0x1f,
             OperandSize::S64 => 0x3f,
             _ => unreachable!(),
         };
-        self.asm.fushr_rri(rhs, rhs, max_shift, size);
+        self.asm.fushr_rri(rhs, writable!(rhs), max_shift, size);
         self.asm.fsli_rri_mod(lhs, rhs, dst, max_shift, size);
     }
 
-    fn float_neg(&mut self, dst: Reg, size: OperandSize) {
-        self.asm.fneg_rr(dst, dst, size);
+    fn float_neg(&mut self, dst: WritableReg, size: OperandSize) {
+        self.asm.fneg_rr(dst.to_reg(), dst, size);
     }
 
-    fn float_abs(&mut self, dst: Reg, size: OperandSize) {
-        self.asm.fabs_rr(dst, dst, size);
+    fn float_abs(&mut self, dst: WritableReg, size: OperandSize) {
+        self.asm.fabs_rr(dst.to_reg(), dst, size);
     }
 
     fn float_round<F: FnMut(&mut FuncEnv<Self::Ptr>, &mut CodeGenContext, &mut Self)>(
@@ -359,11 +362,11 @@ impl Masm for MacroAssembler {
         context.stack.push(src.into());
     }
 
-    fn float_sqrt(&mut self, dst: Reg, src: Reg, size: OperandSize) {
+    fn float_sqrt(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         self.asm.fsqrt_rr(src, dst, size);
     }
 
-    fn and(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+    fn and(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         match (rhs, lhs, dst) {
             (RegImm::Imm(v), rn, rd) => {
                 let imm = match v {
@@ -381,7 +384,7 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn or(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+    fn or(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         match (rhs, lhs, dst) {
             (RegImm::Imm(v), rn, rd) => {
                 let imm = match v {
@@ -399,7 +402,7 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn xor(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+    fn xor(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
         match (rhs, lhs, dst) {
             (RegImm::Imm(v), rn, rd) => {
                 let imm = match v {
@@ -417,7 +420,14 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn shift_ir(&mut self, dst: Reg, imm: u64, lhs: Reg, kind: ShiftKind, size: OperandSize) {
+    fn shift_ir(
+        &mut self,
+        dst: WritableReg,
+        imm: u64,
+        lhs: Reg,
+        kind: ShiftKind,
+        size: OperandSize,
+    ) {
         self.asm.shift_ir(imm, lhs, dst, kind, size)
     }
 
@@ -426,7 +436,7 @@ impl Masm for MacroAssembler {
         let dst = context.pop_to_reg(self, None);
 
         self.asm
-            .shift_rrr(src.into(), dst.into(), dst.into(), kind, size);
+            .shift_rrr(src.into(), dst.into(), writable!(dst.into()), kind, size);
 
         context.free_reg(src);
         context.stack.push(dst.into());
@@ -440,14 +450,14 @@ impl Masm for MacroAssembler {
         todo!()
     }
 
-    fn zero(&mut self, reg: Reg) {
+    fn zero(&mut self, reg: WritableReg) {
         self.asm.load_constant(0, reg);
     }
 
     fn popcnt(&mut self, context: &mut CodeGenContext, size: OperandSize) {
         let src = context.pop_to_reg(self, None);
         let tmp = regs::float_scratch();
-        self.asm.mov_to_fpu(src.into(), tmp, size);
+        self.asm.mov_to_fpu(src.into(), writable!(tmp), size);
         self.asm.cnt(tmp);
         self.asm.addv(tmp, tmp, VectorSize::Size8x8);
         self.asm.mov_from_vec(tmp, src.into(), 0, OperandSize::S8);
@@ -456,8 +466,8 @@ impl Masm for MacroAssembler {
 
     fn signed_truncate(
         &mut self,
+        _dst: WritableReg,
         _src: Reg,
-        _dst: Reg,
         _src_size: OperandSize,
         _dst_size: OperandSize,
         _kind: TruncKind,
@@ -467,8 +477,8 @@ impl Masm for MacroAssembler {
 
     fn unsigned_truncate(
         &mut self,
+        _dst: WritableReg,
         _src: Reg,
-        _dst: Reg,
         _tmp_fpr: Reg,
         _src_size: OperandSize,
         _dst_size: OperandSize,
@@ -479,8 +489,8 @@ impl Masm for MacroAssembler {
 
     fn signed_convert(
         &mut self,
+        _dst: WritableReg,
         _src: Reg,
-        _dst: Reg,
         _src_size: OperandSize,
         _dst_size: OperandSize,
     ) {
@@ -489,8 +499,8 @@ impl Masm for MacroAssembler {
 
     fn unsigned_convert(
         &mut self,
+        _dst: WritableReg,
         _src: Reg,
-        _dst: Reg,
         _tmp_gpr: Reg,
         _src_size: OperandSize,
         _dst_size: OperandSize,
@@ -498,22 +508,22 @@ impl Masm for MacroAssembler {
         todo!()
     }
 
-    fn reinterpret_float_as_int(&mut self, _src: Reg, _dst: Reg, _size: OperandSize) {
+    fn reinterpret_float_as_int(&mut self, _dst: WritableReg, _src: Reg, _size: OperandSize) {
         todo!()
     }
 
-    fn reinterpret_int_as_float(&mut self, _src: Reg, _dst: Reg, _size: OperandSize) {
+    fn reinterpret_int_as_float(&mut self, _dst: WritableReg, _src: Reg, _size: OperandSize) {
         todo!()
     }
 
-    fn demote(&mut self, src: Reg, dst: Reg) {
+    fn demote(&mut self, dst: WritableReg, src: Reg) {
         self.asm
-            .cvt_float_to_float(src.into(), dst.into(), OperandSize::S64, OperandSize::S32);
+            .cvt_float_to_float(src.into(), dst, OperandSize::S64, OperandSize::S32);
     }
 
-    fn promote(&mut self, src: Reg, dst: Reg) {
+    fn promote(&mut self, dst: WritableReg, src: Reg) {
         self.asm
-            .cvt_float_to_float(src.into(), dst.into(), OperandSize::S32, OperandSize::S64);
+            .cvt_float_to_float(src.into(), dst, OperandSize::S32, OperandSize::S64);
     }
 
     fn push(&mut self, reg: Reg, size: OperandSize) -> StackSlot {
@@ -531,8 +541,8 @@ impl Masm for MacroAssembler {
         Address::offset(reg, offset as i64)
     }
 
-    fn cmp_with_set(&mut self, src: RegImm, dst: Reg, kind: IntCmpKind, size: OperandSize) {
-        self.cmp(dst, src, size);
+    fn cmp_with_set(&mut self, dst: WritableReg, src: RegImm, kind: IntCmpKind, size: OperandSize) {
+        self.cmp(dst.to_reg(), src, size);
         self.asm.cset(dst, kind.into());
     }
 
@@ -554,9 +564,9 @@ impl Masm for MacroAssembler {
 
     fn float_cmp_with_set(
         &mut self,
+        dst: WritableReg,
         src1: Reg,
         src2: Reg,
-        dst: Reg,
         kind: FloatCmpKind,
         size: OperandSize,
     ) {
@@ -564,21 +574,21 @@ impl Masm for MacroAssembler {
         self.asm.cset(dst, kind.into());
     }
 
-    fn clz(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    fn clz(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         self.asm.clz(src, dst, size);
     }
 
-    fn ctz(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    fn ctz(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         let scratch = regs::scratch();
-        self.asm.rbit(src, scratch, size);
+        self.asm.rbit(src, writable!(scratch), size);
         self.asm.clz(scratch, dst, size);
     }
 
-    fn wrap(&mut self, src: Reg, dst: Reg) {
+    fn wrap(&mut self, dst: WritableReg, src: Reg) {
         self.asm.mov_rr(src, dst, OperandSize::S32);
     }
 
-    fn extend(&mut self, src: Reg, dst: Reg, kind: ExtendKind) {
+    fn extend(&mut self, dst: WritableReg, src: Reg, kind: ExtendKind) {
         self.asm.extend(src, dst, kind);
     }
 
@@ -682,6 +692,6 @@ impl MacroAssembler {
     fn move_sp_to_shadow_sp(&mut self) {
         let sp = regs::sp();
         let shadow_sp = regs::shadow_sp();
-        self.asm.mov_rr(sp, shadow_sp, OperandSize::S64);
+        self.asm.mov_rr(sp, writable!(shadow_sp), OperandSize::S64);
     }
 }
diff --git a/winch/codegen/src/isa/reg.rs b/winch/codegen/src/isa/reg.rs
index 0a96df329..216029f47 100644
--- a/winch/codegen/src/isa/reg.rs
+++ b/winch/codegen/src/isa/reg.rs
@@ -11,6 +11,18 @@ pub use regalloc2::RegClass;
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Reg(PReg);
 
+pub(crate) type WritableReg = cranelift_codegen::Writable<Reg>;
+
+/// Mark a given register as writable. This macro constructs
+/// a [`cranelift_codegen::Writable`].
+macro_rules! writable {
+    ($e:expr) => {
+        cranelift_codegen::Writable::from_reg($e)
+    };
+}
+
+pub(crate) use writable;
+
 impl Reg {
     /// Create a register from its encoding and class.
     pub fn from(class: RegClass, enc: usize) -> Self {
diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs
index c7ea7835e..ebec91d01 100644
--- a/winch/codegen/src/isa/x64/asm.rs
+++ b/winch/codegen/src/isa/x64/asm.rs
@@ -27,6 +27,8 @@ use cranelift_codegen::{
     MachLabel, PatchRegion, RelocDistance, VCodeConstantData, VCodeConstants, Writable,
 };
 
+use crate::reg::WritableReg;
+
 use super::address::Address;
 use smallvec::SmallVec;
 
@@ -267,8 +269,8 @@ impl Assembler {
     }
 
     /// Pop to register.
-    pub fn pop_r(&mut self, dst: Reg) {
-        let writable = Writable::from_reg(dst.into());
+    pub fn pop_r(&mut self, dst: WritableReg) {
+        let writable = dst.map(Into::into);
         let dst = WritableGpr::from_writable_reg(writable).expect("valid writable gpr");
         self.emit(Inst::Pop64 { dst });
     }
@@ -281,10 +283,10 @@ impl Assembler {
     }
 
     /// Register-to-register move.
-    pub fn mov_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn mov_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::MovRR {
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             size: size.into(),
         });
     }
@@ -324,19 +326,22 @@ impl Assembler {
     }
 
     /// Immediate-to-register move.
-    pub fn mov_ir(&mut self, imm: u64, dst: Reg, size: OperandSize) {
-        let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
-            .expect("valid writable gpr");
-
+    pub fn mov_ir(&mut self, imm: u64, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::Imm {
             dst_size: size.into(),
             simm64: imm,
-            dst,
+            dst: dst.map(Into::into),
         });
     }
 
     /// Zero-extend memory-to-register load.
-    pub fn movzx_mr(&mut self, addr: &Address, dst: Reg, ext: Option<ExtMode>, memflags: MemFlags) {
+    pub fn movzx_mr(
+        &mut self,
+        addr: &Address,
+        dst: WritableReg,
+        ext: Option<ExtMode>,
+        memflags: MemFlags,
+    ) {
         let src = Self::to_synthetic_amode(
             addr,
             &mut self.pool,
@@ -350,12 +355,12 @@ impl Assembler {
             self.emit(Inst::MovzxRmR {
                 ext_mode: ext,
                 src: GprMem::unwrap_new(reg_mem),
-                dst: dst.into(),
+                dst: dst.map(Into::into),
             });
         } else {
             self.emit(Inst::Mov64MR {
                 src,
-                dst: dst.into(),
+                dst: dst.map(Into::into),
             });
         }
     }
@@ -364,7 +369,7 @@ impl Assembler {
     pub fn movsx_mr(
         &mut self,
         addr: &Address,
-        dst: Reg,
+        dst: WritableReg,
         ext: impl Into<ExtMode>,
         memflags: MemFlags,
     ) {
@@ -380,42 +385,43 @@ impl Assembler {
         self.emit(Inst::MovsxRmR {
             ext_mode: ext.into(),
             src: GprMem::unwrap_new(reg_mem),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         })
     }
 
     /// Register-to-register move with zero extension.
-    pub fn movzx_rr(&mut self, src: Reg, dst: Reg, kind: ExtendKind) {
+    pub fn movzx_rr(&mut self, src: Reg, dst: WritableReg, kind: ExtendKind) {
         self.emit(Inst::MovzxRmR {
             ext_mode: kind.into(),
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         })
     }
 
     /// Register-to-register move with sign extension.
-    pub fn movsx_rr(&mut self, src: Reg, dst: Reg, kind: ExtendKind) {
+    pub fn movsx_rr(&mut self, src: Reg, dst: WritableReg, kind: ExtendKind) {
         self.emit(Inst::MovsxRmR {
             ext_mode: kind.into(),
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Integer register conditional move.
-    pub fn cmov(&mut self, src: Reg, dst: Reg, cc: IntCmpKind, size: OperandSize) {
+    pub fn cmov(&mut self, src: Reg, dst: WritableReg, cc: IntCmpKind, size: OperandSize) {
+        let dst: WritableGpr = dst.map(Into::into);
         self.emit(Inst::Cmove {
             size: size.into(),
             cc: cc.into(),
             consequent: src.into(),
-            alternative: dst.into(),
-            dst: dst.into(),
+            alternative: dst.to_reg().into(),
+            dst,
         })
     }
 
     /// Single and double precision floating point
     /// register-to-register move.
-    pub fn xmm_mov_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_mov_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         use OperandSize::*;
 
         let op = match size {
@@ -428,15 +434,21 @@ impl Assembler {
         self.emit(Inst::XmmUnaryRmRUnaligned {
             op,
             src: XmmMem::unwrap_new(src.into()),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Single and double precision floating point load.
-    pub fn xmm_mov_mr(&mut self, src: &Address, dst: Reg, size: OperandSize, flags: MemFlags) {
+    pub fn xmm_mov_mr(
+        &mut self,
+        src: &Address,
+        dst: WritableReg,
+        size: OperandSize,
+        flags: MemFlags,
+    ) {
         use OperandSize::*;
 
-        assert!(dst.is_float());
+        assert!(dst.to_reg().is_float());
         let op = match size {
             S32 => SseOpcode::Movss,
             S64 => SseOpcode::Movsd,
@@ -454,7 +466,7 @@ impl Assembler {
         self.emit(Inst::XmmUnaryRmRUnaligned {
             op,
             src: XmmMem::unwrap_new(RegMem::mem(src)),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
@@ -486,7 +498,8 @@ impl Assembler {
     }
 
     /// Floating point register conditional move.
-    pub fn xmm_cmov(&mut self, src: Reg, dst: Reg, cc: IntCmpKind, size: OperandSize) {
+    pub fn xmm_cmov(&mut self, src: Reg, dst: WritableReg, cc: IntCmpKind, size: OperandSize) {
+        let dst: WritableXmm = dst.map(Into::into);
         let ty = match size {
             OperandSize::S32 => types::F32,
             OperandSize::S64 => types::F64,
@@ -499,59 +512,59 @@ impl Assembler {
             ty,
             cc: cc.into(),
             consequent: Xmm::unwrap_new(src.into()),
-            alternative: dst.into(),
-            dst: dst.into(),
+            alternative: dst.to_reg().into(),
+            dst,
         })
     }
 
     /// Subtract register and register
-    pub fn sub_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn sub_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Sub,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Subtact immediate register.
-    pub fn sub_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
+    pub fn sub_ir(&mut self, imm: i32, dst: WritableReg, size: OperandSize) {
         let imm = RegMemImm::imm(imm as u32);
 
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Sub,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: GprMemImm::unwrap_new(imm),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// "and" two registers.
-    pub fn and_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn and_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::And,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
-    pub fn and_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
+    pub fn and_ir(&mut self, imm: i32, dst: WritableReg, size: OperandSize) {
         let imm = RegMemImm::imm(imm as u32);
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::And,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: GprMemImm::unwrap_new(imm),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// "and" two float registers.
-    pub fn xmm_and_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_and_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Andps,
             OperandSize::S64 => SseOpcode::Andpd,
@@ -560,14 +573,14 @@ impl Assembler {
 
         self.emit(Inst::XmmRmR {
             op,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: XmmMemAligned::from(Xmm::from(src)),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// "and not" two float registers.
-    pub fn xmm_andn_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_andn_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Andnps,
             OperandSize::S64 => SseOpcode::Andnpd,
@@ -576,13 +589,13 @@ impl Assembler {
 
         self.emit(Inst::XmmRmR {
             op,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: Xmm::from(src).into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
-    pub fn gpr_to_xmm(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn gpr_to_xmm(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Movd,
             OperandSize::S64 => SseOpcode::Movq,
@@ -592,12 +605,12 @@ impl Assembler {
         self.emit(Inst::GprToXmm {
             op,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             src_size: size.into(),
         })
     }
 
-    pub fn xmm_to_gpr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_to_gpr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Movd,
             OperandSize::S64 => SseOpcode::Movq,
@@ -607,7 +620,7 @@ impl Assembler {
         self.emit(Inst::XmmToGpr {
             op,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             dst_size: size.into(),
         });
     }
@@ -616,7 +629,7 @@ impl Assembler {
     pub fn cvt_float_to_sint_seq(
         &mut self,
         src: Reg,
-        dst: Reg,
+        dst: WritableReg,
         tmp_gpr: Reg,
         tmp_xmm: Reg,
         src_size: OperandSize,
@@ -628,7 +641,7 @@ impl Assembler {
             src_size: src_size.into(),
             is_saturating: saturating,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             tmp_gpr: tmp_gpr.into(),
             tmp_xmm: tmp_xmm.into(),
         });
@@ -638,7 +651,7 @@ impl Assembler {
     pub fn cvt_float_to_uint_seq(
         &mut self,
         src: Reg,
-        dst: Reg,
+        dst: WritableReg,
         tmp_gpr: Reg,
         tmp_xmm: Reg,
         tmp_xmm2: Reg,
@@ -651,7 +664,7 @@ impl Assembler {
             src_size: src_size.into(),
             is_saturating: saturating,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             tmp_gpr: tmp_gpr.into(),
             tmp_xmm: tmp_xmm.into(),
             tmp_xmm2: tmp_xmm2.into(),
@@ -662,7 +675,7 @@ impl Assembler {
     pub fn cvt_sint_to_float(
         &mut self,
         src: Reg,
-        dst: Reg,
+        dst: WritableReg,
         src_size: OperandSize,
         dst_size: OperandSize,
     ) {
@@ -673,9 +686,9 @@ impl Assembler {
         };
         self.emit(Inst::CvtIntToFloat {
             op,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             src2_size: src_size.into(),
         });
     }
@@ -684,7 +697,7 @@ impl Assembler {
     pub fn cvt_uint64_to_float_seq(
         &mut self,
         src: Reg,
-        dst: Reg,
+        dst: WritableReg,
         tmp_gpr1: Reg,
         tmp_gpr2: Reg,
         dst_size: OperandSize,
@@ -692,7 +705,7 @@ impl Assembler {
         self.emit(Inst::CvtUint64ToFloatSeq {
             dst_size: dst_size.into(),
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             tmp_gpr1: tmp_gpr1.into(),
             tmp_gpr2: tmp_gpr2.into(),
         });
@@ -702,7 +715,7 @@ impl Assembler {
     pub fn cvt_float_to_float(
         &mut self,
         src: Reg,
-        dst: Reg,
+        dst: WritableReg,
         src_size: OperandSize,
         dst_size: OperandSize,
     ) {
@@ -715,34 +728,34 @@ impl Assembler {
         self.emit(Inst::XmmRmRUnaligned {
             op,
             src2: Xmm::unwrap_new(src.into()).into(),
-            src1: dst.into(),
-            dst: dst.into(),
+            src1: dst.to_reg().into(),
+            dst: dst.map(Into::into),
         });
     }
 
-    pub fn or_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn or_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Or,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
-    pub fn or_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
+    pub fn or_ir(&mut self, imm: i32, dst: WritableReg, size: OperandSize) {
         let imm = RegMemImm::imm(imm as u32);
 
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Or,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: GprMemImm::unwrap_new(imm),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
-    pub fn xmm_or_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_or_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Orps,
             OperandSize::S64 => SseOpcode::Orpd,
@@ -751,37 +764,37 @@ impl Assembler {
 
         self.emit(Inst::XmmRmR {
             op,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: XmmMemAligned::from(Xmm::from(src)),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Logical exclusive or with registers.
-    pub fn xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xor_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Xor,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
-    pub fn xor_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
+    pub fn xor_ir(&mut self, imm: i32, dst: WritableReg, size: OperandSize) {
         let imm = RegMemImm::imm(imm as u32);
 
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Xor,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: GprMemImm::unwrap_new(imm),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Logical exclusive or with float registers.
-    pub fn xmm_xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_xor_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Xorps,
             OperandSize::S64 => SseOpcode::Xorpd,
@@ -790,33 +803,33 @@ impl Assembler {
 
         self.emit(Inst::XmmRmR {
             op,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: XmmMemAligned::from(Xmm::from(src)),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Shift with register and register.
-    pub fn shift_rr(&mut self, src: Reg, dst: Reg, kind: ShiftKind, size: OperandSize) {
+    pub fn shift_rr(&mut self, src: Reg, dst: WritableReg, kind: ShiftKind, size: OperandSize) {
         self.emit(Inst::ShiftR {
             size: size.into(),
             kind: kind.into(),
-            src: dst.into(),
+            src: dst.to_reg().into(),
             num_bits: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Shift with immediate and register.
-    pub fn shift_ir(&mut self, imm: u8, dst: Reg, kind: ShiftKind, size: OperandSize) {
+    pub fn shift_ir(&mut self, imm: u8, dst: WritableReg, kind: ShiftKind, size: OperandSize) {
         let imm = imm.into();
 
         self.emit(Inst::ShiftR {
             size: size.into(),
             kind: kind.into(),
-            src: dst.into(),
+            src: dst.to_reg().into(),
             num_bits: Imm8Gpr::unwrap_new(imm),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
@@ -933,46 +946,46 @@ impl Assembler {
     }
 
     /// Multiply immediate and register.
-    pub fn mul_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
+    pub fn mul_ir(&mut self, imm: i32, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::IMulImm {
             size: size.into(),
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: imm,
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Multiply register and register.
-    pub fn mul_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn mul_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::IMul {
             size: size.into(),
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Add immediate and register.
-    pub fn add_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
+    pub fn add_ir(&mut self, imm: i32, dst: WritableReg, size: OperandSize) {
         let imm = RegMemImm::imm(imm as u32);
 
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Add,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: GprMemImm::unwrap_new(imm),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Add register and register.
-    pub fn add_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn add_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::AluRmiR {
             size: size.into(),
             op: AluRmiROpcode::Add,
-            src1: dst.into(),
+            src1: dst.to_reg().into(),
             src2: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
@@ -1037,94 +1050,94 @@ impl Assembler {
 
     /// Set value in dst to `0` or `1` based on flags in status register and
     /// [`CmpKind`].
-    pub fn setcc(&mut self, kind: IntCmpKind, dst: Reg) {
+    pub fn setcc(&mut self, kind: IntCmpKind, dst: WritableReg) {
         self.setcc_impl(kind.into(), dst);
     }
 
     /// Set value in dst to `1` if parity flag in status register is set, `0`
     /// otherwise.
-    pub fn setp(&mut self, dst: Reg) {
+    pub fn setp(&mut self, dst: WritableReg) {
         self.setcc_impl(CC::P, dst);
     }
 
     /// Set value in dst to `1` if parity flag in status register is not set,
     /// `0` otherwise.
-    pub fn setnp(&mut self, dst: Reg) {
+    pub fn setnp(&mut self, dst: WritableReg) {
         self.setcc_impl(CC::NP, dst);
     }
 
-    fn setcc_impl(&mut self, cc: CC, dst: Reg) {
+    fn setcc_impl(&mut self, cc: CC, dst: WritableReg) {
         // Clear the dst register or bits 1 to 31 may be incorrectly set.
         // Don't use xor since it updates the status register.
         self.emit(Inst::Imm {
             dst_size: args::OperandSize::Size32, // Always going to be an i32 result.
             simm64: 0,
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
         // Copy correct bit from status register into dst register.
         self.emit(Inst::Setcc {
             cc,
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Store the count of leading zeroes in src in dst.
     /// Requires `has_lzcnt` flag.
-    pub fn lzcnt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn lzcnt(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         assert!(self.isa_flags.has_lzcnt(), "Requires has_lzcnt flag");
         self.emit(Inst::UnaryRmR {
             size: size.into(),
             op: args::UnaryRmROpcode::Lzcnt,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Store the count of trailing zeroes in src in dst.
     /// Requires `has_bmi1` flag.
-    pub fn tzcnt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn tzcnt(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         assert!(self.isa_flags.has_bmi1(), "Requires has_bmi1 flag");
         self.emit(Inst::UnaryRmR {
             size: size.into(),
             op: args::UnaryRmROpcode::Tzcnt,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Stores position of the most significant bit set in src in dst.
     /// Zero flag is set if src is equal to 0.
-    pub fn bsr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn bsr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::UnaryRmR {
             size: size.into(),
             op: args::UnaryRmROpcode::Bsr,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Performs integer negation on src and places result in dst.
-    pub fn neg(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn neg(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::Neg {
             size: size.into(),
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Stores position of the least significant bit set in src in dst.
     /// Zero flag is set if src is equal to 0.
-    pub fn bsf(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn bsf(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::UnaryRmR {
             size: size.into(),
             op: args::UnaryRmROpcode::Bsf,
             src: src.into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Performs float addition on src and dst and places result in dst.
-    pub fn xmm_add_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_add_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Addss,
             OperandSize::S64 => SseOpcode::Addsd,
@@ -1133,14 +1146,14 @@ impl Assembler {
 
         self.emit(Inst::XmmRmRUnaligned {
             op,
-            src1: Xmm::from(dst).into(),
+            src1: Xmm::from(dst.to_reg()).into(),
             src2: Xmm::from(src).into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Performs float subtraction on src and dst and places result in dst.
-    pub fn xmm_sub_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_sub_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Subss,
             OperandSize::S64 => SseOpcode::Subsd,
@@ -1149,14 +1162,14 @@ impl Assembler {
 
         self.emit(Inst::XmmRmRUnaligned {
             op,
-            src1: Xmm::from(dst).into(),
+            src1: Xmm::from(dst.to_reg()).into(),
             src2: Xmm::from(src).into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Performs float multiplication on src and dst and places result in dst.
-    pub fn xmm_mul_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_mul_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Mulss,
             OperandSize::S64 => SseOpcode::Mulsd,
@@ -1165,14 +1178,14 @@ impl Assembler {
 
         self.emit(Inst::XmmRmRUnaligned {
             op,
-            src1: Xmm::from(dst).into(),
+            src1: Xmm::from(dst.to_reg()).into(),
             src2: Xmm::from(src).into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Performs float division on src and dst and places result in dst.
-    pub fn xmm_div_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_div_rr(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Divss,
             OperandSize::S64 => SseOpcode::Divsd,
@@ -1181,31 +1194,31 @@ impl Assembler {
 
         self.emit(Inst::XmmRmRUnaligned {
             op,
-            src1: Xmm::from(dst).into(),
+            src1: Xmm::from(dst.to_reg()).into(),
             src2: Xmm::from(src).into(),
-            dst: dst.into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Minimum for src and dst XMM registers with results put in dst.
-    pub fn xmm_min_seq(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_min_seq(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::XmmMinMaxSeq {
             size: size.into(),
             is_min: true,
             lhs: src.into(),
-            rhs: dst.into(),
-            dst: dst.into(),
+            rhs: dst.to_reg().into(),
+            dst: dst.map(Into::into),
         });
     }
 
     /// Maximum for src and dst XMM registers with results put in dst.
-    pub fn xmm_max_seq(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn xmm_max_seq(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         self.emit(Inst::XmmMinMaxSeq {
             size: size.into(),
             is_min: false,
             lhs: src.into(),
-            rhs: dst.into(),
-            dst: dst.into(),
+            rhs: dst.to_reg().into(),
+            dst: dst.map(Into::into),
         });
     }
 
@@ -1233,7 +1246,7 @@ impl Assembler {
         })
     }
 
-    pub fn sqrt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    pub fn sqrt(&mut self, src: Reg, dst: WritableReg, size: OperandSize) {
         let op = match size {
             OperandSize::S32 => SseOpcode::Sqrtss,
             OperandSize::S64 => SseOpcode::Sqrtsd,
@@ -1243,8 +1256,8 @@ impl Assembler {
         self.emit(Inst::XmmRmR {
             op,
             src2: Xmm::from(src).into(),
-            src1: dst.into(),
-            dst: dst.into(),
+            src1: dst.to_reg().into(),
+            dst: dst.map(Into::into),
         })
     }
 
@@ -1330,7 +1343,7 @@ impl Assembler {
     }
 
     /// Load effective address.
-    pub fn lea(&mut self, addr: &Address, dst: Reg, size: OperandSize) {
+    pub fn lea(&mut self, addr: &Address, dst: WritableReg, size: OperandSize) {
         let addr = Self::to_synthetic_amode(
             addr,
             &mut self.pool,
@@ -1340,7 +1353,7 @@ impl Assembler {
         );
         self.emit(Inst::LoadEffectiveAddress {
             addr,
-            dst: dst.into(),
+            dst: dst.map(Into::into),
             size: size.into(),
         });
     }
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index 1166b568e..e42f39e62 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -19,7 +19,7 @@ use crate::{
     masm::{SPOffset, StackSlot},
 };
 use crate::{
-    isa::reg::{Reg, RegClass},
+    isa::reg::{writable, Reg, RegClass, WritableReg},
     masm::CalleeKind,
 };
 use cranelift_codegen::{
@@ -74,7 +74,7 @@ impl Masm for MacroAssembler {
         }
 
         self.asm
-            .mov_rr(stack_pointer, frame_pointer, OperandSize::S64);
+            .mov_rr(stack_pointer, writable!(frame_pointer), OperandSize::S64);
     }
 
     fn check_stack(&mut self, vmctx: Reg) {
@@ -83,12 +83,12 @@ impl Masm for MacroAssembler {
 
         self.load_ptr(
             self.address_at_reg(vmctx, ptr_size.vmcontext_runtime_limits().into()),
-            scratch,
+            writable!(scratch),
         );
 
         self.load_ptr(
             Address::offset(scratch, ptr_size.vmruntime_limits_stack_limit().into()),
-            scratch,
+            writable!(scratch),
         );
 
         self.add_stack_max(scratch);
@@ -146,7 +146,8 @@ impl Masm for MacroAssembler {
             return;
         }
 
-        self.asm.sub_ir(bytes as i32, rsp(), OperandSize::S64);
+        self.asm
+            .sub_ir(bytes as i32, writable!(rsp()), OperandSize::S64);
         self.increment_sp(bytes);
     }
 
@@ -154,7 +155,8 @@ impl Masm for MacroAssembler {
         if bytes == 0 {
             return;
         }
-        self.asm.add_ir(bytes as i32, rsp(), OperandSize::S64);
+        self.asm
+            .add_ir(bytes as i32, writable!(rsp()), OperandSize::S64);
         self.decrement_sp(bytes);
     }
 
@@ -203,9 +205,9 @@ impl Masm for MacroAssembler {
         self.store_impl(src.into(), dst, size, UNTRUSTED_FLAGS);
     }
 
-    fn pop(&mut self, dst: Reg, size: OperandSize) {
+    fn pop(&mut self, dst: WritableReg, size: OperandSize) {
         let current_sp = SPOffset::from_u32(self.sp_offset);
-        match (dst.class(), size) {
+        match (dst.to_reg().class(), size) {
             (RegClass::Int, OperandSize::S32) => {
                 let addr = self.address_from_sp(current_sp);
                 self.asm.movzx_mr(&addr, dst, size.into(), TRUSTED_FLAGS);
@@ -244,22 +246,22 @@ impl Masm for MacroAssembler {
         total_stack
     }
 
-    fn load_ptr(&mut self, src: Self::Address, dst: Reg) {
+    fn load_ptr(&mut self, src: Self::Address, dst: WritableReg) {
         self.load(src, dst, self.ptr_size);
     }
 
-    fn load_addr(&mut self, src: Self::Address, dst: Reg, size: OperandSize) {
+    fn load_addr(&mut self, src: Self::Address, dst: WritableReg, size: OperandSize) {
         self.asm.lea(&src, dst, size);
     }
 
-    fn load(&mut self, src: Address, dst: Reg, size: OperandSize) {
+    fn load(&mut self, src: Address, dst: WritableReg, size: OperandSize) {
         self.load_impl::<Self>(src, dst, size, TRUSTED_FLAGS);
     }
 
     fn wasm_load(
         &mut self,
         src: Self::Address,
-        dst: Reg,
+        dst: WritableReg,
         size: OperandSize,
         kind: Option<ExtendKind>,
     ) {
@@ -274,19 +276,22 @@ impl Masm for MacroAssembler {
         SPOffset::from_u32(self.sp_offset)
     }
 
-    fn zero(&mut self, reg: Reg) {
-        self.asm
-            .xor_rr(reg, reg, OperandSize::from_bytes(<Self::ABI>::word_bytes()));
+    fn zero(&mut self, reg: WritableReg) {
+        self.asm.xor_rr(
+            reg.to_reg(),
+            reg,
+            OperandSize::from_bytes(<Self::ABI>::word_bytes()),
+        );
     }
 
-    fn mov(&mut self, src: RegImm, dst: Reg, size: OperandSize) {
-        match (src, dst) {
-            rr @ (RegImm::Reg(src), dst) => match (src.class(), dst.class()) {
+    fn mov(&mut self, dst: WritableReg, src: RegImm, size: OperandSize) {
+        match (src, dst.to_reg()) {
+            rr @ (RegImm::Reg(src), dst_reg) => match (src.class(), dst_reg.class()) {
                 (RegClass::Int, RegClass::Int) => self.asm.mov_rr(src, dst, size),
                 (RegClass::Float, RegClass::Float) => self.asm.xmm_mov_rr(src, dst, size),
                 _ => Self::handle_invalid_operand_combination(rr.0, rr.1),
             },
-            (RegImm::Imm(imm), dst) => match imm {
+            (RegImm::Imm(imm), _) => match imm {
                 I::I32(v) => self.asm.mov_ir(v as u64, dst, size),
                 I::I64(v) => self.asm.mov_ir(v, dst, size),
                 I::F32(v) => {
@@ -305,24 +310,24 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn cmov(&mut self, src: Reg, dst: Reg, cc: IntCmpKind, size: OperandSize) {
-        match (src.class(), dst.class()) {
+    fn cmov(&mut self, dst: WritableReg, src: Reg, cc: IntCmpKind, size: OperandSize) {
+        match (src.class(), dst.to_reg().class()) {
             (RegClass::Int, RegClass::Int) => self.asm.cmov(src, dst, cc, size),
             (RegClass::Float, RegClass::Float) => self.asm.xmm_cmov(src, dst, cc, size),
-            _ => Self::handle_invalid_operand_combination(src, dst),
+            _ => Self::handle_invalid_operand_combination(src, dst.to_reg()),
         }
     }
 
-    fn add(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn add(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         match (rhs, dst) {
-            (RegImm::Imm(imm), reg) => {
+            (RegImm::Imm(imm), _) => {
                 if let Some(v) = imm.to_i32() {
-                    self.asm.add_ir(v, reg, size);
+                    self.asm.add_ir(v, dst, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
-                    self.asm.add_rr(scratch, reg, size);
+                    self.load_constant(&imm, writable!(scratch), size);
+                    self.asm.add_rr(scratch, dst, size);
                 }
             }
 
@@ -332,20 +337,27 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn checked_uadd(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize, trap: TrapCode) {
+    fn checked_uadd(
+        &mut self,
+        dst: WritableReg,
+        lhs: Reg,
+        rhs: RegImm,
+        size: OperandSize,
+        trap: TrapCode,
+    ) {
         self.add(dst, lhs, rhs, size);
         self.asm.trapif(CC::B, trap);
     }
 
-    fn sub(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn sub(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         match (rhs, dst) {
             (RegImm::Imm(imm), reg) => {
                 if let Some(v) = imm.to_i32() {
                     self.asm.sub_ir(v, reg, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
+                    self.load_constant(&imm, writable!(scratch), size);
                     self.asm.sub_rr(scratch, reg, size);
                 }
             }
@@ -356,16 +368,16 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn mul(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn mul(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         match (rhs, dst) {
-            (RegImm::Imm(imm), reg) => {
+            (RegImm::Imm(imm), _) => {
                 if let Some(v) = imm.to_i32() {
-                    self.asm.mul_ir(v, reg, size);
+                    self.asm.mul_ir(v, dst, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
-                    self.asm.mul_rr(scratch, reg, size);
+                    self.load_constant(&imm, writable!(scratch), size);
+                    self.asm.mul_rr(scratch, dst, size);
                 }
             }
 
@@ -375,38 +387,38 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn float_add(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_add(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.xmm_add_rr(rhs, dst, size);
     }
 
-    fn float_sub(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_sub(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.xmm_sub_rr(rhs, dst, size);
     }
 
-    fn float_mul(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_mul(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.xmm_mul_rr(rhs, dst, size);
     }
 
-    fn float_div(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_div(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.xmm_div_rr(rhs, dst, size);
     }
 
-    fn float_min(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_min(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.xmm_min_seq(rhs, dst, size);
     }
 
-    fn float_max(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_max(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.xmm_max_seq(rhs, dst, size);
     }
 
-    fn float_copysign(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn float_copysign(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         let scratch_gpr = regs::scratch();
         let scratch_xmm = regs::scratch_xmm();
         let sign_mask = match size {
@@ -414,46 +426,50 @@ impl Masm for MacroAssembler {
             OperandSize::S64 => I::I64(0x8000000000000000),
             OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
         };
-        self.load_constant(&sign_mask, scratch_gpr, size);
-        self.asm.gpr_to_xmm(scratch_gpr, scratch_xmm, size);
+        self.load_constant(&sign_mask, writable!(scratch_gpr), size);
+        self.asm
+            .gpr_to_xmm(scratch_gpr, writable!(scratch_xmm), size);
 
         // Clear everything except sign bit in src.
-        self.asm.xmm_and_rr(scratch_xmm, rhs, size);
+        self.asm.xmm_and_rr(scratch_xmm, writable!(rhs), size);
 
         // Clear sign bit in dst using scratch to store result. Then copy the
         // result back to dst.
-        self.asm.xmm_andn_rr(dst, scratch_xmm, size);
+        self.asm
+            .xmm_andn_rr(dst.to_reg(), writable!(scratch_xmm), size);
         self.asm.xmm_mov_rr(scratch_xmm, dst, size);
 
         // Copy sign bit from src to dst.
         self.asm.xmm_or_rr(rhs, dst, size);
     }
 
-    fn float_neg(&mut self, dst: Reg, size: OperandSize) {
-        assert_eq!(dst.class(), RegClass::Float);
+    fn float_neg(&mut self, dst: WritableReg, size: OperandSize) {
+        assert_eq!(dst.to_reg().class(), RegClass::Float);
         let mask = match size {
             OperandSize::S32 => I::I32(0x80000000),
             OperandSize::S64 => I::I64(0x8000000000000000),
             OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
         };
         let scratch_gpr = regs::scratch();
-        self.load_constant(&mask, scratch_gpr, size);
+        self.load_constant(&mask, writable!(scratch_gpr), size);
         let scratch_xmm = regs::scratch_xmm();
-        self.asm.gpr_to_xmm(scratch_gpr, scratch_xmm, size);
+        self.asm
+            .gpr_to_xmm(scratch_gpr, writable!(scratch_xmm), size);
         self.asm.xmm_xor_rr(scratch_xmm, dst, size);
     }
 
-    fn float_abs(&mut self, dst: Reg, size: OperandSize) {
-        assert_eq!(dst.class(), RegClass::Float);
+    fn float_abs(&mut self, dst: WritableReg, size: OperandSize) {
+        assert_eq!(dst.to_reg().class(), RegClass::Float);
         let mask = match size {
             OperandSize::S32 => I::I32(0x7fffffff),
             OperandSize::S64 => I::I64(0x7fffffffffffffff),
             OperandSize::S128 | OperandSize::S16 | OperandSize::S8 => unreachable!(),
         };
         let scratch_gpr = regs::scratch();
-        self.load_constant(&mask, scratch_gpr, size);
+        self.load_constant(&mask, writable!(scratch_gpr), size);
         let scratch_xmm = regs::scratch_xmm();
-        self.asm.gpr_to_xmm(scratch_gpr, scratch_xmm, size);
+        self.asm
+            .gpr_to_xmm(scratch_gpr, writable!(scratch_xmm), size);
         self.asm.xmm_and_rr(scratch_xmm, dst, size);
     }
 
@@ -474,20 +490,20 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn float_sqrt(&mut self, dst: Reg, src: Reg, size: OperandSize) {
+    fn float_sqrt(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         self.asm.sqrt(src, dst, size);
     }
 
-    fn and(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn and(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         match (rhs, dst) {
-            (RegImm::Imm(imm), reg) => {
+            (RegImm::Imm(imm), _) => {
                 if let Some(v) = imm.to_i32() {
-                    self.asm.and_ir(v, reg, size);
+                    self.asm.and_ir(v, dst, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
-                    self.asm.and_rr(scratch, reg, size);
+                    self.load_constant(&imm, writable!(scratch), size);
+                    self.asm.and_rr(scratch, dst, size);
                 }
             }
 
@@ -497,16 +513,16 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn or(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn or(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         match (rhs, dst) {
-            (RegImm::Imm(imm), reg) => {
+            (RegImm::Imm(imm), _) => {
                 if let Some(v) = imm.to_i32() {
-                    self.asm.or_ir(v, reg, size);
+                    self.asm.or_ir(v, dst, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
-                    self.asm.or_rr(scratch, reg, size);
+                    self.load_constant(&imm, writable!(scratch), size);
+                    self.asm.or_rr(scratch, dst, size);
                 }
             }
 
@@ -516,27 +532,34 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn xor(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn xor(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         match (rhs, dst) {
-            (RegImm::Imm(imm), reg) => {
+            (RegImm::Imm(imm), _) => {
                 if let Some(v) = imm.to_i32() {
-                    self.asm.xor_ir(v, reg, size);
+                    self.asm.xor_ir(v, dst, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
-                    self.asm.xor_rr(scratch, reg, size);
+                    self.load_constant(&imm, writable!(scratch), size);
+                    self.asm.xor_rr(scratch, dst, size);
                 }
             }
 
-            (RegImm::Reg(src), dst) => {
+            (RegImm::Reg(src), _) => {
                 self.asm.xor_rr(src, dst, size);
             }
         }
     }
 
-    fn shift_ir(&mut self, dst: Reg, imm: u64, lhs: Reg, kind: ShiftKind, size: OperandSize) {
-        Self::ensure_two_argument_form(&dst, &lhs);
+    fn shift_ir(
+        &mut self,
+        dst: WritableReg,
+        imm: u64,
+        lhs: Reg,
+        kind: ShiftKind,
+        size: OperandSize,
+    ) {
+        Self::ensure_two_argument_form(&dst.to_reg(), &lhs);
         self.asm.shift_ir(imm as u8, dst, kind, size)
     }
 
@@ -545,7 +568,8 @@ impl Masm for MacroAssembler {
         let src = context.pop_to_reg(self, Some(regs::rcx()));
         let dst = context.pop_to_reg(self, None);
 
-        self.asm.shift_rr(src.into(), dst.into(), kind, size);
+        self.asm
+            .shift_rr(src.into(), writable!(dst.into()), kind, size);
 
         context.free_reg(src);
         context.stack.push(dst.into());
@@ -597,7 +621,7 @@ impl Masm for MacroAssembler {
 
     fn frame_restore(&mut self) {
         assert_eq!(self.sp_offset, 0);
-        self.asm.pop_r(rbp());
+        self.asm.pop_r(writable!(rbp()));
         self.asm.ret();
     }
 
@@ -620,7 +644,7 @@ impl Masm for MacroAssembler {
                     self.asm.cmp_ir(src1, v, size);
                 } else {
                     let scratch = regs::scratch();
-                    self.load_constant(&imm, scratch, size);
+                    self.load_constant(&imm, writable!(scratch), size);
                     self.asm.cmp_rr(src1, scratch, size);
                 }
             }
@@ -630,16 +654,16 @@ impl Masm for MacroAssembler {
         }
     }
 
-    fn cmp_with_set(&mut self, src: RegImm, dst: Reg, kind: IntCmpKind, size: OperandSize) {
-        self.cmp(dst, src, size);
+    fn cmp_with_set(&mut self, dst: WritableReg, src: RegImm, kind: IntCmpKind, size: OperandSize) {
+        self.cmp(dst.to_reg(), src, size);
         self.asm.setcc(kind, dst);
     }
 
     fn float_cmp_with_set(
         &mut self,
+        dst: WritableReg,
         src1: Reg,
         src2: Reg,
-        dst: Reg,
         kind: FloatCmpKind,
         size: OperandSize,
     ) {
@@ -670,21 +694,21 @@ impl Masm for MacroAssembler {
                 // Return false if either operand is NaN by ensuring PF is
                 // unset.
                 let scratch = regs::scratch();
-                self.asm.setnp(scratch);
+                self.asm.setnp(writable!(scratch));
                 self.asm.and_rr(scratch, dst, size);
             }
             FloatCmpKind::Ne => {
                 // Return true if either operand is NaN by checking if PF is
                 // set.
                 let scratch = regs::scratch();
-                self.asm.setp(scratch);
+                self.asm.setp(writable!(scratch));
                 self.asm.or_rr(scratch, dst, size);
             }
             FloatCmpKind::Lt | FloatCmpKind::Le => (),
         }
     }
 
-    fn clz(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    fn clz(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         if self.flags.has_lzcnt() {
             self.asm.lzcnt(src, dst, size);
         } else {
@@ -693,15 +717,15 @@ impl Masm for MacroAssembler {
             // Use the following approach:
             // dst = size.num_bits() - bsr(src) - is_not_zero
             //     = size.num.bits() + -bsr(src) - is_not_zero.
-            self.asm.bsr(src.into(), dst.into(), size);
-            self.asm.setcc(IntCmpKind::Ne, scratch.into());
-            self.asm.neg(dst, dst, size);
+            self.asm.bsr(src.into(), dst, size);
+            self.asm.setcc(IntCmpKind::Ne, writable!(scratch.into()));
+            self.asm.neg(dst.to_reg(), dst, size);
             self.asm.add_ir(size.num_bits() as i32, dst, size);
             self.asm.sub_rr(scratch, dst, size);
         }
     }
 
-    fn ctz(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    fn ctz(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         if self.flags.has_bmi1() {
             self.asm.tzcnt(src, dst, size);
         } else {
@@ -714,9 +738,9 @@ impl Masm for MacroAssembler {
             // When the value is 0, BSF outputs 0, correct output for ctz is
             // the number of bits.
             self.asm.bsf(src.into(), dst.into(), size);
-            self.asm.setcc(IntCmpKind::Eq, scratch.into());
+            self.asm.setcc(IntCmpKind::Eq, writable!(scratch.into()));
             self.asm
-                .shift_ir(size.log2(), scratch, ShiftKind::Shl, size);
+                .shift_ir(size.log2(), writable!(scratch), ShiftKind::Shl, size);
             self.asm.add_rr(scratch, dst, size);
         }
     }
@@ -770,8 +794,8 @@ impl Masm for MacroAssembler {
             // The fallback functionality here is based on `MacroAssembler::popcnt64` in:
             // https://searchfox.org/mozilla-central/source/js/src/jit/x64/MacroAssembler-x64-inl.h#495
 
-            let tmp = context.any_gpr(self);
-            let dst = src;
+            let tmp = writable!(context.any_gpr(self));
+            let dst = writable!(src.into());
             let (masks, shift_amt) = match size {
                 OperandSize::S64 => (
                     [
@@ -793,45 +817,45 @@ impl Masm for MacroAssembler {
             self.asm.mov_rr(src.into(), tmp, size);
 
             // x -= (x >> 1) & m1;
-            self.asm.shift_ir(1u8, dst.into(), ShiftKind::ShrU, size);
-            let lhs = dst.reg;
-            self.and(lhs, lhs, RegImm::i64(masks[0]), size);
-            self.asm.sub_rr(dst.into(), tmp, size);
+            self.asm.shift_ir(1u8, dst, ShiftKind::ShrU, size);
+            let lhs = dst.to_reg();
+            self.and(writable!(lhs), lhs, RegImm::i64(masks[0]), size);
+            self.asm.sub_rr(dst.to_reg(), tmp, size);
 
             // x = (x & m2) + ((x >> 2) & m2);
-            self.asm.mov_rr(tmp, dst.into(), size);
+            self.asm.mov_rr(tmp.to_reg(), dst, size);
             // Load `0x3333...` into the scratch reg once, allowing us to use
             // `and_rr` and avoid inadvertently loading it twice as with `and`
             let scratch = regs::scratch();
-            self.load_constant(&I::i64(masks[1]), scratch, size);
-            self.asm.and_rr(scratch, dst.into(), size);
+            self.load_constant(&I::i64(masks[1]), writable!(scratch), size);
+            self.asm.and_rr(scratch, dst, size);
             self.asm.shift_ir(2u8, tmp, ShiftKind::ShrU, size);
             self.asm.and_rr(scratch, tmp, size);
-            self.asm.add_rr(dst.into(), tmp, size);
+            self.asm.add_rr(dst.to_reg(), tmp, size);
 
             // x = (x + (x >> 4)) & m4;
-            self.asm.mov_rr(tmp.into(), dst.into(), size);
+            self.asm.mov_rr(tmp.to_reg(), dst.into(), size);
             self.asm.shift_ir(4u8, dst.into(), ShiftKind::ShrU, size);
-            self.asm.add_rr(tmp, dst.into(), size);
-            let lhs = dst.reg.into();
-            self.and(lhs, lhs, RegImm::i64(masks[2]), size);
+            self.asm.add_rr(tmp.to_reg(), dst, size);
+            let lhs = dst.to_reg();
+            self.and(writable!(lhs), lhs, RegImm::i64(masks[2]), size);
 
             // (x * h01) >> shift_amt
-            let lhs = dst.reg.into();
-            self.mul(lhs, lhs, RegImm::i64(masks[3]), size);
+            let lhs = dst.to_reg();
+            self.mul(writable!(lhs), lhs, RegImm::i64(masks[3]), size);
             self.asm
                 .shift_ir(shift_amt, dst.into(), ShiftKind::ShrU, size);
 
-            context.stack.push(dst.into());
-            context.free_reg(tmp);
+            context.stack.push(src.into());
+            context.free_reg(tmp.to_reg());
         }
     }
 
-    fn wrap(&mut self, src: Reg, dst: Reg) {
-        self.asm.mov_rr(src.into(), dst.into(), OperandSize::S32);
+    fn wrap(&mut self, dst: WritableReg, src: Reg) {
+        self.asm.mov_rr(src.into(), dst, OperandSize::S32);
     }
 
-    fn extend(&mut self, src: Reg, dst: Reg, kind: ExtendKind) {
+    fn extend(&mut self, dst: WritableReg, src: Reg, kind: ExtendKind) {
         if let ExtendKind::I64ExtendI32U = kind {
             self.asm.movzx_rr(src, dst, kind);
         } else {
@@ -841,8 +865,8 @@ impl Masm for MacroAssembler {
 
     fn signed_truncate(
         &mut self,
+        dst: WritableReg,
         src: Reg,
-        dst: Reg,
         src_size: OperandSize,
         dst_size: OperandSize,
         kind: TruncKind,
@@ -860,8 +884,8 @@ impl Masm for MacroAssembler {
 
     fn unsigned_truncate(
         &mut self,
+        dst: WritableReg,
         src: Reg,
-        dst: Reg,
         tmp_fpr: Reg,
         src_size: OperandSize,
         dst_size: OperandSize,
@@ -879,43 +903,49 @@ impl Masm for MacroAssembler {
         );
     }
 
-    fn signed_convert(&mut self, src: Reg, dst: Reg, src_size: OperandSize, dst_size: OperandSize) {
+    fn signed_convert(
+        &mut self,
+        dst: WritableReg,
+        src: Reg,
+        src_size: OperandSize,
+        dst_size: OperandSize,
+    ) {
         self.asm.cvt_sint_to_float(src, dst, src_size, dst_size);
     }
 
     fn unsigned_convert(
         &mut self,
+        dst: WritableReg,
         src: Reg,
-        dst: Reg,
         tmp_gpr: Reg,
         src_size: OperandSize,
         dst_size: OperandSize,
     ) {
         // Need to convert unsigned uint32 to uint64 for conversion instruction sequence.
         if let OperandSize::S32 = src_size {
-            self.extend(src, src, ExtendKind::I64ExtendI32U);
+            self.extend(writable!(src), src, ExtendKind::I64ExtendI32U);
         }
 
         self.asm
             .cvt_uint64_to_float_seq(src, dst, regs::scratch(), tmp_gpr, dst_size);
     }
 
-    fn reinterpret_float_as_int(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    fn reinterpret_float_as_int(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         self.asm.xmm_to_gpr(src, dst, size);
     }
 
-    fn reinterpret_int_as_float(&mut self, src: Reg, dst: Reg, size: OperandSize) {
+    fn reinterpret_int_as_float(&mut self, dst: WritableReg, src: Reg, size: OperandSize) {
         self.asm.gpr_to_xmm(src.into(), dst, size);
     }
 
-    fn demote(&mut self, src: Reg, dst: Reg) {
+    fn demote(&mut self, dst: WritableReg, src: Reg) {
         self.asm
             .cvt_float_to_float(src.into(), dst.into(), OperandSize::S64, OperandSize::S32);
     }
 
-    fn promote(&mut self, src: Reg, dst: Reg) {
+    fn promote(&mut self, dst: WritableReg, src: Reg) {
         self.asm
-            .cvt_float_to_float(src.into(), dst.into(), OperandSize::S32, OperandSize::S64);
+            .cvt_float_to_float(src.into(), dst, OperandSize::S32, OperandSize::S64);
     }
 
     fn unreachable(&mut self) {
@@ -944,9 +974,9 @@ impl Masm for MacroAssembler {
         // are greater.
         let max = default_index;
         let size = OperandSize::S32;
-        self.asm.mov_ir(max as u64, tmp, size);
+        self.asm.mov_ir(max as u64, writable!(tmp), size);
         self.asm.cmp_rr(tmp, index, size);
-        self.asm.cmov(tmp, index, IntCmpKind::LtU, size);
+        self.asm.cmov(tmp, writable!(index), IntCmpKind::LtU, size);
 
         let default = targets[default_index];
         let rest = &targets[0..default_index];
@@ -1015,7 +1045,7 @@ impl MacroAssembler {
         self.sp_offset -= bytes;
     }
 
-    fn load_constant(&mut self, constant: &I, dst: Reg, size: OperandSize) {
+    fn load_constant(&mut self, constant: &I, dst: WritableReg, size: OperandSize) {
         match constant {
             I::I32(v) => self.asm.mov_ir(*v as u64, dst, size),
             I::I64(v) => self.asm.mov_ir(*v, dst, size),
@@ -1024,11 +1054,11 @@ impl MacroAssembler {
     }
 
     /// A common implementation for zero-extend stack loads.
-    fn load_impl<M>(&mut self, src: Address, dst: Reg, size: OperandSize, flags: MemFlags)
+    fn load_impl<M>(&mut self, src: Address, dst: WritableReg, size: OperandSize, flags: MemFlags)
     where
         M: Masm,
     {
-        if dst.is_int() {
+        if dst.to_reg().is_int() {
             let access_bits = size.num_bits() as u16;
 
             let ext_mode = match access_bits {
@@ -1055,7 +1085,7 @@ impl MacroAssembler {
                         // If the immediate doesn't sign extend, use a scratch
                         // register.
                         let scratch = regs::scratch();
-                        self.asm.mov_ir(v, scratch, size);
+                        self.asm.mov_ir(v, writable!(scratch), size);
                         self.asm.mov_rm(scratch, &dst, size, flags);
                     }
                 },
@@ -1065,7 +1095,7 @@ impl MacroAssembler {
                     // Always trusted, since we are loading the constant from
                     // the constant pool.
                     self.asm
-                        .xmm_mov_mr(&addr, float_scratch, size, MemFlags::trusted());
+                        .xmm_mov_mr(&addr, writable!(float_scratch), size, MemFlags::trusted());
                     self.asm.xmm_mov_rm(float_scratch, &dst, size, flags);
                 }
                 I::F64(v) => {
@@ -1074,7 +1104,7 @@ impl MacroAssembler {
                     // Similar to above, always trusted since we are loading the
                     // constant from the constant pool.
                     self.asm
-                        .xmm_mov_mr(&addr, float_scratch, size, MemFlags::trusted());
+                        .xmm_mov_mr(&addr, writable!(float_scratch), size, MemFlags::trusted());
                     self.asm.xmm_mov_rm(float_scratch, &dst, size, flags);
                 }
                 I::V128(v) => {
@@ -1082,8 +1112,12 @@ impl MacroAssembler {
                     let vector_scratch = regs::scratch_xmm();
                     // Always trusted, since we are loading the constant from
                     // the constant pool.
-                    self.asm
-                        .xmm_mov_mr(&addr, vector_scratch, size, MemFlags::trusted());
+                    self.asm.xmm_mov_mr(
+                        &addr,
+                        writable!(vector_scratch),
+                        size,
+                        MemFlags::trusted(),
+                    );
                     self.asm.xmm_mov_rm(vector_scratch, &dst, size, flags);
                 }
             },
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index bf266fa74..f34fd9f69 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -1,6 +1,6 @@
 use crate::abi::{self, align_to, scratch, LocalSlot};
 use crate::codegen::{CodeGenContext, FuncEnv};
-use crate::isa::reg::Reg;
+use crate::isa::reg::{writable, Reg, WritableReg};
 use cranelift_codegen::{
     binemit::CodeOffset,
     ir::{Endianness, LibCall, MemFlags, RelSourceLoc, SourceLoc, UserExternalNameRef},
@@ -605,7 +605,7 @@ pub(crate) trait MacroAssembler {
     fn wasm_store(&mut self, src: Reg, dst: Self::Address, size: OperandSize);
 
     /// Perform a zero-extended stack load.
-    fn load(&mut self, src: Self::Address, dst: Reg, size: OperandSize);
+    fn load(&mut self, src: Self::Address, dst: WritableReg, size: OperandSize);
 
     /// Perform a WebAssembly load.
     /// A WebAssembly load introduces several additional invariants compared to
@@ -618,26 +618,26 @@ pub(crate) trait MacroAssembler {
     fn wasm_load(
         &mut self,
         src: Self::Address,
-        dst: Reg,
+        dst: WritableReg,
         size: OperandSize,
         kind: Option<ExtendKind>,
     );
 
     /// Alias for `MacroAssembler::load` with the operand size corresponding
     /// to the pointer size of the target.
-    fn load_ptr(&mut self, src: Self::Address, dst: Reg);
+    fn load_ptr(&mut self, src: Self::Address, dst: WritableReg);
 
     /// Loads the effective address into destination.
-    fn load_addr(&mut self, _src: Self::Address, _dst: Reg, _size: OperandSize);
+    fn load_addr(&mut self, _src: Self::Address, _dst: WritableReg, _size: OperandSize);
 
     /// Pop a value from the machine stack into the given register.
-    fn pop(&mut self, dst: Reg, size: OperandSize);
+    fn pop(&mut self, dst: WritableReg, size: OperandSize);
 
     /// Perform a move.
-    fn mov(&mut self, src: RegImm, dst: Reg, size: OperandSize);
+    fn mov(&mut self, dst: WritableReg, src: RegImm, size: OperandSize);
 
     /// Perform a conditional move.
-    fn cmov(&mut self, src: Reg, dst: Reg, cc: IntCmpKind, size: OperandSize);
+    fn cmov(&mut self, dst: WritableReg, src: Reg, cc: IntCmpKind, size: OperandSize);
 
     /// Performs a memory move of bytes from src to dest.
     /// Bytes are moved in blocks of 8 bytes, where possible.
@@ -661,7 +661,10 @@ pub(crate) trait MacroAssembler {
             dst_offs += word_bytes;
             src_offs += word_bytes;
 
-            self.load_ptr(self.address_from_sp(SPOffset::from_u32(src_offs)), scratch);
+            self.load_ptr(
+                self.address_from_sp(SPOffset::from_u32(src_offs)),
+                writable!(scratch),
+            );
             self.store_ptr(
                 scratch.into(),
                 self.address_from_sp(SPOffset::from_u32(dst_offs)),
@@ -677,7 +680,7 @@ pub(crate) trait MacroAssembler {
 
             self.load(
                 self.address_from_sp(SPOffset::from_u32(src_offs)),
-                scratch,
+                writable!(scratch),
                 ptr_size,
             );
             self.store(
@@ -689,47 +692,54 @@ pub(crate) trait MacroAssembler {
     }
 
     /// Perform add operation.
-    fn add(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
+    fn add(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
     /// Perform a checked unsigned integer addition, emitting the provided trap
     /// if the addition overflows.
-    fn checked_uadd(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize, trap: TrapCode);
+    fn checked_uadd(
+        &mut self,
+        dst: WritableReg,
+        lhs: Reg,
+        rhs: RegImm,
+        size: OperandSize,
+        trap: TrapCode,
+    );
 
     /// Perform subtraction operation.
-    fn sub(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
+    fn sub(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
     /// Perform multiplication operation.
-    fn mul(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
+    fn mul(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
     /// Perform a floating point add operation.
-    fn float_add(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_add(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point subtraction operation.
-    fn float_sub(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_sub(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point multiply operation.
-    fn float_mul(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_mul(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point divide operation.
-    fn float_div(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_div(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point minimum operation. In x86, this will emit
     /// multiple instructions.
-    fn float_min(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_min(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point maximum operation. In x86, this will emit
     /// multiple instructions.
-    fn float_max(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_max(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point copysign operation. In x86, this will emit
     /// multiple instructions.
-    fn float_copysign(&mut self, dst: Reg, lhs: Reg, rhs: Reg, size: OperandSize);
+    fn float_copysign(&mut self, dst: WritableReg, lhs: Reg, rhs: Reg, size: OperandSize);
 
     /// Perform a floating point abs operation.
-    fn float_abs(&mut self, dst: Reg, size: OperandSize);
+    fn float_abs(&mut self, dst: WritableReg, size: OperandSize);
 
     /// Perform a floating point negation operation.
-    fn float_neg(&mut self, dst: Reg, size: OperandSize);
+    fn float_neg(&mut self, dst: WritableReg, size: OperandSize);
 
     /// Perform a floating point floor operation.
     fn float_round<F: FnMut(&mut FuncEnv<Self::Ptr>, &mut CodeGenContext, &mut Self)>(
@@ -742,19 +752,26 @@ pub(crate) trait MacroAssembler {
     );
 
     /// Perform a floating point square root operation.
-    fn float_sqrt(&mut self, dst: Reg, src: Reg, size: OperandSize);
+    fn float_sqrt(&mut self, dst: WritableReg, src: Reg, size: OperandSize);
 
     /// Perform logical and operation.
-    fn and(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
+    fn and(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
     /// Perform logical or operation.
-    fn or(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
+    fn or(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
     /// Perform logical exclusive or operation.
-    fn xor(&mut self, dst: Reg, lhs: Reg, rhs: RegImm, size: OperandSize);
+    fn xor(&mut self, dst: WritableReg, lhs: Reg, rhs: RegImm, size: OperandSize);
 
     /// Perform a shift operation between a register and an immediate.
-    fn shift_ir(&mut self, dst: Reg, imm: u64, lhs: Reg, kind: ShiftKind, size: OperandSize);
+    fn shift_ir(
+        &mut self,
+        dst: WritableReg,
+        imm: u64,
+        lhs: Reg,
+        kind: ShiftKind,
+        size: OperandSize,
+    );
 
     /// Perform a shift operation between two registers.
     /// This case is special in that some architectures have specific expectations
@@ -794,15 +811,15 @@ pub(crate) trait MacroAssembler {
     /// The initial value in `dst` is the left-hand-side of the comparison and
     /// the initial value in `src` is the right-hand-side of the comparison.
     /// That means for `a < b` then `dst == a` and `src == b`.
-    fn cmp_with_set(&mut self, src: RegImm, dst: Reg, kind: IntCmpKind, size: OperandSize);
+    fn cmp_with_set(&mut self, dst: WritableReg, src: RegImm, kind: IntCmpKind, size: OperandSize);
 
     /// Compare floats in src1 and src2 and put the result in dst.
     /// In x86, this will emit multiple instructions.
     fn float_cmp_with_set(
         &mut self,
+        dst: WritableReg,
         src1: Reg,
         src2: Reg,
-        dst: Reg,
         kind: FloatCmpKind,
         size: OperandSize,
     );
@@ -810,12 +827,12 @@ pub(crate) trait MacroAssembler {
     /// Count the number of leading zeroes in src and put the result in dst.
     /// In x64, this will emit multiple instructions if the `has_lzcnt` flag is
     /// false.
-    fn clz(&mut self, src: Reg, dst: Reg, size: OperandSize);
+    fn clz(&mut self, dst: WritableReg, src: Reg, size: OperandSize);
 
     /// Count the number of trailing zeroes in src and put the result in dst.masm
     /// In x64, this will emit multiple instructions if the `has_tzcnt` flag is
     /// false.
-    fn ctz(&mut self, src: Reg, dst: Reg, size: OperandSize);
+    fn ctz(&mut self, dst: WritableReg, src: Reg, size: OperandSize);
 
     /// Push the register to the stack, returning the stack slot metadata.
     // NB
@@ -829,24 +846,24 @@ pub(crate) trait MacroAssembler {
     fn finalize(self, base: Option<SourceLoc>) -> MachBufferFinalized<Final>;
 
     /// Zero a particular register.
-    fn zero(&mut self, reg: Reg);
+    fn zero(&mut self, reg: WritableReg);
 
     /// Count the number of 1 bits in src and put the result in dst. In x64,
     /// this will emit multiple instructions if the `has_popcnt` flag is false.
     fn popcnt(&mut self, context: &mut CodeGenContext, size: OperandSize);
 
     /// Converts an i64 to an i32 by discarding the high 32 bits.
-    fn wrap(&mut self, src: Reg, dst: Reg);
+    fn wrap(&mut self, dst: WritableReg, src: Reg);
 
     /// Extends an integer of a given size to a larger size.
-    fn extend(&mut self, src: Reg, dst: Reg, kind: ExtendKind);
+    fn extend(&mut self, dst: WritableReg, src: Reg, kind: ExtendKind);
 
     /// Emits one or more instructions to perform a signed truncation of a
     /// float into an integer.
     fn signed_truncate(
         &mut self,
+        dst: WritableReg,
         src: Reg,
-        dst: Reg,
         src_size: OperandSize,
         dst_size: OperandSize,
         kind: TruncKind,
@@ -856,8 +873,8 @@ pub(crate) trait MacroAssembler {
     /// float into an integer.
     fn unsigned_truncate(
         &mut self,
+        dst: WritableReg,
         src: Reg,
-        dst: Reg,
         tmp_fpr: Reg,
         src_size: OperandSize,
         dst_size: OperandSize,
@@ -866,30 +883,36 @@ pub(crate) trait MacroAssembler {
 
     /// Emits one or more instructions to perform a signed convert of an
     /// integer into a float.
-    fn signed_convert(&mut self, src: Reg, dst: Reg, src_size: OperandSize, dst_size: OperandSize);
+    fn signed_convert(
+        &mut self,
+        dst: WritableReg,
+        src: Reg,
+        src_size: OperandSize,
+        dst_size: OperandSize,
+    );
 
     /// Emits one or more instructions to perform an unsigned convert of an
     /// integer into a float.
     fn unsigned_convert(
         &mut self,
+        dst: WritableReg,
         src: Reg,
-        dst: Reg,
         tmp_gpr: Reg,
         src_size: OperandSize,
         dst_size: OperandSize,
     );
 
     /// Reinterpret a float as an integer.
-    fn reinterpret_float_as_int(&mut self, src: Reg, dst: Reg, size: OperandSize);
+    fn reinterpret_float_as_int(&mut self, dst: WritableReg, src: Reg, size: OperandSize);
 
     /// Reinterpret an integer as a float.
-    fn reinterpret_int_as_float(&mut self, src: Reg, dst: Reg, size: OperandSize);
+    fn reinterpret_int_as_float(&mut self, dst: WritableReg, src: Reg, size: OperandSize);
 
     /// Demote an f64 to an f32.
-    fn demote(&mut self, src: Reg, dst: Reg);
+    fn demote(&mut self, dst: WritableReg, src: Reg);
 
     /// Promote an f32 to an f64.
-    fn promote(&mut self, src: Reg, dst: Reg);
+    fn promote(&mut self, dst: WritableReg, src: Reg);
 
     /// Zero a given memory range.
     ///
@@ -928,7 +951,7 @@ pub(crate) trait MacroAssembler {
             // given a considerably large amount of slots
             // this will be inefficient.
             let zero = scratch!(Self);
-            self.zero(zero);
+            self.zero(writable!(zero));
             let zero = RegImm::reg(zero);
 
             for step in (start..end).into_iter().step_by(word_size as usize) {
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index 615405dde..fe7b868dc 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -10,7 +10,7 @@ use crate::masm::{
     DivKind, ExtendKind, FloatCmpKind, IntCmpKind, MacroAssembler, MemMoveDirection, OperandSize,
     RegImm, RemKind, RoundingMode, SPOffset, ShiftKind, TruncKind,
 };
-use crate::reg::Reg;
+use crate::reg::{writable, Reg};
 use crate::stack::{TypedReg, Val};
 use regalloc2::RegClass;
 use smallvec::SmallVec;
@@ -278,7 +278,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_add(dst, dst, src, size);
+                masm.float_add(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -289,7 +289,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_add(dst, dst, src, size);
+                masm.float_add(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -300,7 +300,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_sub(dst, dst, src, size);
+                masm.float_sub(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -311,7 +311,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_sub(dst, dst, src, size);
+                masm.float_sub(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -322,7 +322,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_mul(dst, dst, src, size);
+                masm.float_mul(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -333,7 +333,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_mul(dst, dst, src, size);
+                masm.float_mul(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -344,7 +344,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_div(dst, dst, src, size);
+                masm.float_div(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -355,7 +355,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_div(dst, dst, src, size);
+                masm.float_div(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -366,7 +366,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_min(dst, dst, src, size);
+                masm.float_min(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -377,7 +377,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_min(dst, dst, src, size);
+                masm.float_min(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -388,7 +388,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_max(dst, dst, src, size);
+                masm.float_max(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -399,7 +399,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_max(dst, dst, src, size);
+                masm.float_max(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -410,7 +410,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_copysign(dst, dst, src, size);
+                masm.float_copysign(writable!(dst), dst, src, size);
                 TypedReg::f32(dst)
             },
         );
@@ -421,7 +421,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src, size| {
-                masm.float_copysign(dst, dst, src, size);
+                masm.float_copysign(writable!(dst), dst, src, size);
                 TypedReg::f64(dst)
             },
         );
@@ -430,7 +430,7 @@ where
     fn visit_f32_abs(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S32, &mut |masm, reg, size| {
-                masm.float_abs(reg, size);
+                masm.float_abs(writable!(reg), size);
                 TypedReg::f32(reg)
             });
     }
@@ -438,7 +438,7 @@ where
     fn visit_f64_abs(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S64, &mut |masm, reg, size| {
-                masm.float_abs(reg, size);
+                masm.float_abs(writable!(reg), size);
                 TypedReg::f64(reg)
             });
     }
@@ -446,7 +446,7 @@ where
     fn visit_f32_neg(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S32, &mut |masm, reg, size| {
-                masm.float_neg(reg, size);
+                masm.float_neg(writable!(reg), size);
                 TypedReg::f32(reg)
             });
     }
@@ -454,7 +454,7 @@ where
     fn visit_f64_neg(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S64, &mut |masm, reg, size| {
-                masm.float_neg(reg, size);
+                masm.float_neg(writable!(reg), size);
                 TypedReg::f64(reg)
             });
     }
@@ -566,7 +566,7 @@ where
     fn visit_f32_sqrt(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S32, &mut |masm, reg, size| {
-                masm.float_sqrt(reg, reg, size);
+                masm.float_sqrt(writable!(reg), reg, size);
                 TypedReg::f32(reg)
             });
     }
@@ -574,7 +574,7 @@ where
     fn visit_f64_sqrt(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S64, &mut |masm, reg, size| {
-                masm.float_sqrt(reg, reg, size);
+                masm.float_sqrt(writable!(reg), reg, size);
                 TypedReg::f64(reg)
             });
     }
@@ -584,7 +584,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Eq, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Eq, size);
             },
         );
     }
@@ -594,7 +594,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Eq, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Eq, size);
             },
         );
     }
@@ -604,7 +604,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Ne, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Ne, size);
             },
         );
     }
@@ -614,7 +614,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Ne, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Ne, size);
             },
         );
     }
@@ -624,7 +624,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Lt, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Lt, size);
             },
         );
     }
@@ -634,7 +634,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Lt, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Lt, size);
             },
         );
     }
@@ -644,7 +644,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Gt, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Gt, size);
             },
         );
     }
@@ -654,7 +654,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Gt, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Gt, size);
             },
         );
     }
@@ -664,7 +664,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Le, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Le, size);
             },
         );
     }
@@ -674,7 +674,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Le, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Le, size);
             },
         );
     }
@@ -684,7 +684,7 @@ where
             self.masm,
             OperandSize::S32,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Ge, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Ge, size);
             },
         );
     }
@@ -694,7 +694,7 @@ where
             self.masm,
             OperandSize::S64,
             &mut |masm: &mut M, dst, src1, src2, size| {
-                masm.float_cmp_with_set(src1, src2, dst, FloatCmpKind::Ge, size);
+                masm.float_cmp_with_set(writable!(dst), src1, src2, FloatCmpKind::Ge, size);
             },
         );
     }
@@ -702,7 +702,7 @@ where
     fn visit_f32_convert_i32_s(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::F32, |masm, dst, src, dst_size| {
-                masm.signed_convert(src, dst, OperandSize::S32, dst_size);
+                masm.signed_convert(writable!(dst), src, OperandSize::S32, dst_size);
             });
     }
 
@@ -712,7 +712,7 @@ where
             WasmValType::F32,
             RegClass::Int,
             |masm, dst, src, tmp_gpr, dst_size| {
-                masm.unsigned_convert(src, dst, tmp_gpr, OperandSize::S32, dst_size);
+                masm.unsigned_convert(writable!(dst), src, tmp_gpr, OperandSize::S32, dst_size);
             },
         );
     }
@@ -720,7 +720,7 @@ where
     fn visit_f32_convert_i64_s(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::F32, |masm, dst, src, dst_size| {
-                masm.signed_convert(src, dst, OperandSize::S64, dst_size);
+                masm.signed_convert(writable!(dst), src, OperandSize::S64, dst_size);
             });
     }
 
@@ -730,7 +730,7 @@ where
             WasmValType::F32,
             RegClass::Int,
             |masm, dst, src, tmp_gpr, dst_size| {
-                masm.unsigned_convert(src, dst, tmp_gpr, OperandSize::S64, dst_size);
+                masm.unsigned_convert(writable!(dst), src, tmp_gpr, OperandSize::S64, dst_size);
             },
         );
     }
@@ -738,7 +738,7 @@ where
     fn visit_f64_convert_i32_s(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::F64, |masm, dst, src, dst_size| {
-                masm.signed_convert(src, dst, OperandSize::S32, dst_size);
+                masm.signed_convert(writable!(dst), src, OperandSize::S32, dst_size);
             });
     }
 
@@ -748,7 +748,7 @@ where
             WasmValType::F64,
             RegClass::Int,
             |masm, dst, src, tmp_gpr, dst_size| {
-                masm.unsigned_convert(src, dst, tmp_gpr, OperandSize::S32, dst_size);
+                masm.unsigned_convert(writable!(dst), src, tmp_gpr, OperandSize::S32, dst_size);
             },
         );
     }
@@ -756,7 +756,7 @@ where
     fn visit_f64_convert_i64_s(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::F64, |masm, dst, src, dst_size| {
-                masm.signed_convert(src, dst, OperandSize::S64, dst_size);
+                masm.signed_convert(writable!(dst), src, OperandSize::S64, dst_size);
             });
     }
 
@@ -766,7 +766,7 @@ where
             WasmValType::F64,
             RegClass::Int,
             |masm, dst, src, tmp_gpr, dst_size| {
-                masm.unsigned_convert(src, dst, tmp_gpr, OperandSize::S64, dst_size);
+                masm.unsigned_convert(writable!(dst), src, tmp_gpr, OperandSize::S64, dst_size);
             },
         );
     }
@@ -774,21 +774,21 @@ where
     fn visit_f32_reinterpret_i32(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::F32, |masm, dst, src, size| {
-                masm.reinterpret_int_as_float(src.into(), dst, size);
+                masm.reinterpret_int_as_float(writable!(dst), src.into(), size);
             });
     }
 
     fn visit_f64_reinterpret_i64(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::F64, |masm, dst, src, size| {
-                masm.reinterpret_int_as_float(src.into(), dst, size);
+                masm.reinterpret_int_as_float(writable!(dst), src.into(), size);
             });
     }
 
     fn visit_f32_demote_f64(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S64, &mut |masm, reg, _size| {
-                masm.demote(reg, reg);
+                masm.demote(writable!(reg), reg);
                 TypedReg::f32(reg)
             });
     }
@@ -796,49 +796,49 @@ where
     fn visit_f64_promote_f32(&mut self) {
         self.context
             .unop(self.masm, OperandSize::S32, &mut |masm, reg, _size| {
-                masm.promote(reg, reg);
+                masm.promote(writable!(reg), reg);
                 TypedReg::f64(reg)
             });
     }
 
     fn visit_i32_add(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.add(dst, dst, src, size);
+            masm.add(writable!(dst), dst, src, size);
             TypedReg::i32(dst)
         });
     }
 
     fn visit_i64_add(&mut self) {
         self.context.i64_binop(self.masm, |masm, dst, src, size| {
-            masm.add(dst, dst, src, size);
+            masm.add(writable!(dst), dst, src, size);
             TypedReg::i64(dst)
         });
     }
 
     fn visit_i32_sub(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.sub(dst, dst, src, size);
+            masm.sub(writable!(dst), dst, src, size);
             TypedReg::i32(dst)
         });
     }
 
     fn visit_i64_sub(&mut self) {
         self.context.i64_binop(self.masm, |masm, dst, src, size| {
-            masm.sub(dst, dst, src, size);
+            masm.sub(writable!(dst), dst, src, size);
             TypedReg::i64(dst)
         });
     }
 
     fn visit_i32_mul(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.mul(dst, dst, src, size);
+            masm.mul(writable!(dst), dst, src, size);
             TypedReg::i32(dst)
         });
     }
 
     fn visit_i64_mul(&mut self) {
         self.context.i64_binop(self.masm, |masm, dst, src, size| {
-            masm.mul(dst, dst, src, size);
+            masm.mul(writable!(dst), dst, src, size);
             TypedReg::i64(dst)
         });
     }
@@ -983,7 +983,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, size| {
-            masm.cmp_with_set(RegImm::i32(0), reg.into(), IntCmpKind::Eq, size);
+            masm.cmp_with_set(writable!(reg.into()), RegImm::i32(0), IntCmpKind::Eq, size);
             TypedReg::i32(reg)
         });
     }
@@ -992,7 +992,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, size| {
-            masm.cmp_with_set(RegImm::i64(0), reg.into(), IntCmpKind::Eq, size);
+            masm.cmp_with_set(writable!(reg.into()), RegImm::i64(0), IntCmpKind::Eq, size);
             TypedReg::i32(reg) // Return value for `i64.eqz` is an `i32`.
         });
     }
@@ -1001,7 +1001,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, size| {
-            masm.clz(reg, reg, size);
+            masm.clz(writable!(reg), reg, size);
             TypedReg::i32(reg)
         });
     }
@@ -1010,7 +1010,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, size| {
-            masm.clz(reg, reg, size);
+            masm.clz(writable!(reg), reg, size);
             TypedReg::i64(reg)
         });
     }
@@ -1019,7 +1019,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, size| {
-            masm.ctz(reg, reg, size);
+            masm.ctz(writable!(reg), reg, size);
             TypedReg::i32(reg)
         });
     }
@@ -1028,49 +1028,49 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, size| {
-            masm.ctz(reg, reg, size);
+            masm.ctz(writable!(reg), reg, size);
             TypedReg::i64(reg)
         });
     }
 
     fn visit_i32_and(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.and(dst, dst, src, size);
+            masm.and(writable!(dst), dst, src, size);
             TypedReg::i32(dst)
         });
     }
 
     fn visit_i64_and(&mut self) {
         self.context.i64_binop(self.masm, |masm, dst, src, size| {
-            masm.and(dst, dst, src, size);
+            masm.and(writable!(dst), dst, src, size);
             TypedReg::i64(dst)
         });
     }
 
     fn visit_i32_or(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.or(dst, dst, src, size);
+            masm.or(writable!(dst), dst, src, size);
             TypedReg::i32(dst)
         });
     }
 
     fn visit_i64_or(&mut self) {
         self.context.i64_binop(self.masm, |masm, dst, src, size| {
-            masm.or(dst, dst, src, size);
+            masm.or(writable!(dst), dst, src, size);
             TypedReg::i64(dst)
         });
     }
 
     fn visit_i32_xor(&mut self) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.xor(dst, dst, src, size);
+            masm.xor(writable!(dst), dst, src, size);
             TypedReg::i32(dst)
         });
     }
 
     fn visit_i64_xor(&mut self) {
         self.context.i64_binop(self.masm, |masm, dst, src, size| {
-            masm.xor(dst, dst, src, size);
+            masm.xor(writable!(dst), dst, src, size);
             TypedReg::i64(dst)
         });
     }
@@ -1159,7 +1159,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, _size| {
-            masm.wrap(reg, reg);
+            masm.wrap(writable!(reg), reg);
             TypedReg::i32(reg)
         });
     }
@@ -1168,7 +1168,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I64ExtendI32S);
+            masm.extend(writable!(reg), reg, ExtendKind::I64ExtendI32S);
             TypedReg::i64(reg)
         });
     }
@@ -1177,7 +1177,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I64ExtendI32U);
+            masm.extend(writable!(reg), reg, ExtendKind::I64ExtendI32U);
             TypedReg::i64(reg)
         });
     }
@@ -1186,7 +1186,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I32Extend8S);
+            masm.extend(writable!(reg), reg, ExtendKind::I32Extend8S);
             TypedReg::i32(reg)
         });
     }
@@ -1195,7 +1195,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S32, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I32Extend16S);
+            masm.extend(writable!(reg), reg, ExtendKind::I32Extend16S);
             TypedReg::i32(reg)
         });
     }
@@ -1204,7 +1204,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I64Extend8S);
+            masm.extend(writable!(reg), reg, ExtendKind::I64Extend8S);
             TypedReg::i64(reg)
         });
     }
@@ -1213,7 +1213,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I64Extend16S);
+            masm.extend(writable!(reg), reg, ExtendKind::I64Extend16S);
             TypedReg::i64(reg)
         });
     }
@@ -1222,7 +1222,7 @@ where
         use OperandSize::*;
 
         self.context.unop(self.masm, S64, &mut |masm, reg, _size| {
-            masm.extend(reg, reg, ExtendKind::I64Extend32S);
+            masm.extend(writable!(reg), reg, ExtendKind::I64Extend32S);
             TypedReg::i64(reg)
         });
     }
@@ -1232,7 +1232,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I32, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S32, dst_size, TruncKind::Unchecked);
+                masm.signed_truncate(writable!(dst), src, S32, dst_size, TruncKind::Unchecked);
             });
     }
 
@@ -1244,7 +1244,14 @@ where
             WasmValType::I32,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S32, dst_size, TruncKind::Unchecked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S32,
+                    dst_size,
+                    TruncKind::Unchecked,
+                );
             },
         );
     }
@@ -1254,7 +1261,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I32, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S64, dst_size, TruncKind::Unchecked);
+                masm.signed_truncate(writable!(dst), src, S64, dst_size, TruncKind::Unchecked);
             });
     }
 
@@ -1266,7 +1273,14 @@ where
             WasmValType::I32,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S64, dst_size, TruncKind::Unchecked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S64,
+                    dst_size,
+                    TruncKind::Unchecked,
+                );
             },
         );
     }
@@ -1276,7 +1290,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I64, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S32, dst_size, TruncKind::Unchecked);
+                masm.signed_truncate(writable!(dst), src, S32, dst_size, TruncKind::Unchecked);
             });
     }
 
@@ -1288,7 +1302,14 @@ where
             WasmValType::I64,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S32, dst_size, TruncKind::Unchecked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S32,
+                    dst_size,
+                    TruncKind::Unchecked,
+                );
             },
         );
     }
@@ -1298,7 +1319,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I64, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S64, dst_size, TruncKind::Unchecked);
+                masm.signed_truncate(writable!(dst), src, S64, dst_size, TruncKind::Unchecked);
             });
     }
 
@@ -1310,7 +1331,14 @@ where
             WasmValType::I64,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S64, dst_size, TruncKind::Unchecked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S64,
+                    dst_size,
+                    TruncKind::Unchecked,
+                );
             },
         );
     }
@@ -1318,14 +1346,14 @@ where
     fn visit_i32_reinterpret_f32(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::I32, |masm, dst, src, size| {
-                masm.reinterpret_float_as_int(src.into(), dst, size);
+                masm.reinterpret_float_as_int(writable!(dst), src.into(), size);
             });
     }
 
     fn visit_i64_reinterpret_f64(&mut self) {
         self.context
             .convert_op(self.masm, WasmValType::I64, |masm, dst, src, size| {
-                masm.reinterpret_float_as_int(src.into(), dst, size);
+                masm.reinterpret_float_as_int(writable!(dst), src.into(), size);
             });
     }
 
@@ -1518,7 +1546,7 @@ where
                         self.emit_compute_table_elem_addr(index.into(), base, &table_data);
                     // Set the initialized bit.
                     self.masm.or(
-                        value.into(),
+                        writable!(value.into()),
                         value.into(),
                         RegImm::i64(FUNCREF_INIT_BIT as i64),
                         ptr_type.into(),
@@ -1638,7 +1666,7 @@ where
             // the result of the memory32_grow builtin.
             (WasmValType::I64, WasmValType::I32) => {
                 let top: Reg = self.context.pop_to_reg(self.masm, None).into();
-                self.masm.wrap(top.into(), top.into());
+                self.masm.wrap(writable!(top.into()), top.into());
                 self.context.stack.push(TypedReg::i32(top).into());
             }
             _ => unimplemented!("Support for 32-bit platforms"),
@@ -1874,7 +1902,7 @@ where
         let index = GlobalIndex::from_u32(global_index);
         let (ty, addr) = self.emit_get_global_addr(index);
         let dst = self.context.reg_for_type(ty, self.masm);
-        self.masm.load(addr, dst, ty.into());
+        self.masm.load(addr, writable!(dst), ty.into());
         self.context.stack.push(Val::reg(dst, ty));
     }
 
@@ -1903,8 +1931,12 @@ where
             .cmp(cond.reg.into(), RegImm::i32(0), OperandSize::S32);
         // Conditionally move val1 to val2 if the comparison is
         // not zero.
-        self.masm
-            .cmov(val1.into(), val2.into(), IntCmpKind::Ne, val1.ty.into());
+        self.masm.cmov(
+            writable!(val2.into()),
+            val1.into(),
+            IntCmpKind::Ne,
+            val1.ty.into(),
+        );
         self.context.stack.push(val2.into());
         self.context.free_reg(val1.reg);
         self.context.free_reg(cond);
@@ -2040,7 +2072,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I32, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S32, dst_size, TruncKind::Checked);
+                masm.signed_truncate(writable!(dst), src, S32, dst_size, TruncKind::Checked);
             });
     }
 
@@ -2052,7 +2084,14 @@ where
             WasmValType::I32,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S32, dst_size, TruncKind::Checked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S32,
+                    dst_size,
+                    TruncKind::Checked,
+                );
             },
         );
     }
@@ -2062,7 +2101,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I32, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S64, dst_size, TruncKind::Checked);
+                masm.signed_truncate(writable!(dst), src, S64, dst_size, TruncKind::Checked);
             });
     }
 
@@ -2074,7 +2113,14 @@ where
             WasmValType::I32,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S64, dst_size, TruncKind::Checked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S64,
+                    dst_size,
+                    TruncKind::Checked,
+                );
             },
         );
     }
@@ -2084,7 +2130,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I64, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S32, dst_size, TruncKind::Checked);
+                masm.signed_truncate(writable!(dst), src, S32, dst_size, TruncKind::Checked);
             });
     }
 
@@ -2096,7 +2142,14 @@ where
             WasmValType::I64,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S32, dst_size, TruncKind::Checked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S32,
+                    dst_size,
+                    TruncKind::Checked,
+                );
             },
         );
     }
@@ -2106,7 +2159,7 @@ where
 
         self.context
             .convert_op(self.masm, WasmValType::I64, |masm, dst, src, dst_size| {
-                masm.signed_truncate(src, dst, S64, dst_size, TruncKind::Checked);
+                masm.signed_truncate(writable!(dst), src, S64, dst_size, TruncKind::Checked);
             });
     }
 
@@ -2118,7 +2171,14 @@ where
             WasmValType::I64,
             RegClass::Float,
             |masm, dst, src, tmp_fpr, dst_size| {
-                masm.unsigned_truncate(src, dst, tmp_fpr, S64, dst_size, TruncKind::Checked);
+                masm.unsigned_truncate(
+                    writable!(dst),
+                    src,
+                    tmp_fpr,
+                    S64,
+                    dst_size,
+                    TruncKind::Checked,
+                );
             },
         );
     }
@@ -2132,7 +2192,7 @@ where
 {
     fn cmp_i32s(&mut self, kind: IntCmpKind) {
         self.context.i32_binop(self.masm, |masm, dst, src, size| {
-            masm.cmp_with_set(src, dst, kind, size);
+            masm.cmp_with_set(writable!(dst), src, kind, size);
             TypedReg::i32(dst)
         });
     }
@@ -2140,7 +2200,7 @@ where
     fn cmp_i64s(&mut self, kind: IntCmpKind) {
         self.context
             .i64_binop(self.masm, move |masm, dst, src, size| {
-                masm.cmp_with_set(src, dst, kind, size);
+                masm.cmp_with_set(writable!(dst), src, kind, size);
                 TypedReg::i32(dst) // Return value for comparisons is an `i32`.
             });
     }


Test patch:
diff --git a/crates/test-programs/src/bin/api_reactor.rs b/crates/test-programs/src/bin/api_reactor.rs
index ae8ecf9f9..360c96fab 100644
--- a/crates/test-programs/src/bin/api_reactor.rs
+++ b/crates/test-programs/src/bin/api_reactor.rs
@@ -1,3 +1,5 @@
+use std::sync::{Mutex, MutexGuard};
+
 wit_bindgen::generate!({
     world: "test-reactor",
     path: "../wasi/wit",
@@ -8,57 +10,59 @@ struct T;
 
 export!(T);
 
-static mut STATE: Vec<String> = Vec::new();
+fn state() -> MutexGuard<'static, Vec<String>> {
+    static STATE: Mutex<Vec<String>> = Mutex::new(Vec::new());
+    STATE.lock().unwrap()
+}
 
 impl Guest for T {
     fn add_strings(ss: Vec<String>) -> u32 {
+        let mut state = state();
         for s in ss {
             match s.split_once("$") {
                 Some((prefix, var)) if prefix.is_empty() => match std::env::var(var) {
-                    Ok(val) => unsafe { STATE.push(val) },
-                    Err(_) => unsafe { STATE.push("undefined".to_owned()) },
+                    Ok(val) => state.push(val),
+                    Err(_) => state.push("undefined".to_owned()),
                 },
-                _ => unsafe { STATE.push(s) },
+                _ => state.push(s),
             }
         }
-        unsafe { STATE.len() as u32 }
+        state.len() as u32
     }
     fn get_strings() -> Vec<String> {
-        unsafe { STATE.clone() }
+        state().clone()
     }
 
     fn write_strings_to(o: OutputStream) -> Result<(), ()> {
         let pollable = o.subscribe();
-        unsafe {
-            for s in STATE.iter() {
-                let mut out = s.as_bytes();
-                while !out.is_empty() {
-                    pollable.block();
-                    let n = match o.check_write() {
-                        Ok(n) => n,
-                        Err(_) => return Err(()),
-                    };
+        for s in state().iter() {
+            let mut out = s.as_bytes();
+            while !out.is_empty() {
+                pollable.block();
+                let n = match o.check_write() {
+                    Ok(n) => n,
+                    Err(_) => return Err(()),
+                };
 
-                    let len = (n as usize).min(out.len());
-                    match o.write(&out[..len]) {
-                        Ok(_) => out = &out[len..],
-                        Err(_) => return Err(()),
-                    }
+                let len = (n as usize).min(out.len());
+                match o.write(&out[..len]) {
+                    Ok(_) => out = &out[len..],
+                    Err(_) => return Err(()),
                 }
             }
+        }
 
-            match o.flush() {
-                Ok(_) => {}
-                Err(_) => return Err(()),
-            }
-            pollable.block();
-            match o.check_write() {
-                Ok(_) => {}
-                Err(_) => return Err(()),
-            }
-
-            Ok(())
+        match o.flush() {
+            Ok(_) => {}
+            Err(_) => return Err(()),
         }
+        pollable.block();
+        match o.check_write() {
+            Ok(_) => {}
+            Err(_) => return Err(()),
+        }
+
+        Ok(())
     }
     fn pass_an_imported_record(stat: wasi::filesystem::types::DescriptorStat) -> String {
         format!("{stat:?}")
diff --git a/crates/test-programs/src/bin/cli_stdin_empty.rs b/crates/test-programs/src/bin/cli_stdin_empty.rs
new file mode 100644
index 000000000..c5df2f090
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_stdin_empty.rs
@@ -0,0 +1,16 @@
+use test_programs::preview1::STDIN_FD;
+
+fn main() {
+    let mut buffer = [0_u8; 0];
+
+    unsafe {
+        wasi::fd_read(
+            STDIN_FD,
+            &[wasi::Iovec {
+                buf: buffer.as_mut_ptr(),
+                buf_len: 0,
+            }],
+        )
+        .expect("empty read should succeed");
+    }
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 7f8b6b2f2..797fe4c64 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1160,6 +1160,27 @@ mod test_programs {
         Ok(())
     }
 
+    #[test]
+    fn cli_stdin_empty() -> Result<()> {
+        let mut child = get_wasmtime_command()?
+            .args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT])
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .stdin(Stdio::piped())
+            .spawn()?;
+        child
+            .stdin
+            .take()
+            .unwrap()
+            .write_all(b"not to be read")
+            .unwrap();
+        let output = child.wait_with_output()?;
+        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+        assert!(output.status.success());
+        Ok(())
+    }
+
     #[test]
     fn cli_stdin() -> Result<()> {
         let mut child = get_wasmtime_command()?