Repository: bytecodealliance/wasm-tools
Pull request #1483 (2024-04-02 15:45:25)

threads: add atomic versions of `global.get|set`
The shared-everything-threads [proposal] allows marking globals as `shared` and thus adds `global.atomic.get|set` for choosing the memory ordering of these accesses. These changes also include the memory `ordering` flags for marking accesses as sequentially consistent (`seq_cst`) or acquire-release (`acq_rel`).

[proposal]: https://github.com/WebAssembly/shared-everything-threads

alexcrichton: Lemme know if you'd like to handle the TODO for anyref here or defer that to later, I'm fine either way
abrown: > Lemme know if you'd like to handle the TODO for anyref here or defer that to later, I'm fine either way

I couldn't find what I was looking for yesterday. I think d683529 should resolve this?
abrown: > it should be validated on the definition of the global

Yup, that's more "right". I'll tweak this all next week.
abrown: Scratch that: I think we may have to validate at the instruction level because different instructions have different type restrictions.
alexcrichton: That'd be two layers of validation, right? One layer for when the global is defined and then another layer for specific opcodes having more restrictions?

Patch:
diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs
index 76bfd7af..4b04b0b7 100644
--- a/crates/wasm-encoder/src/core/code.rs
+++ b/crates/wasm-encoder/src/core/code.rs
@@ -274,6 +274,34 @@ impl Encode for MemArg {
     }
 }
 
+/// The memory ordering for atomic instructions.
+///
+/// For an in-depth explanation of memory orderings, see the C++ documentation
+/// for [`memory_order`] or the Rust documentation for [`atomic::Ordering`].
+///
+/// [`memory_order`]: https://en.cppreference.com/w/cpp/atomic/memory_order
+/// [`atomic::Ordering`]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
+#[derive(Clone, Copy, Debug)]
+pub enum Ordering {
+    /// For a load, it acquires; this orders all operations before the last
+    /// "releasing" store. For a store, it releases; this orders all operations
+    /// before it at the next "acquiring" load.
+    AcqRel,
+    /// Like `AcqRel` but all threads see all sequentially consistent operations
+    /// in the same order.
+    SeqCst,
+}
+
+impl Encode for Ordering {
+    fn encode(&self, sink: &mut Vec<u8>) {
+        let flag: u8 = match self {
+            Ordering::SeqCst => 0,
+            Ordering::AcqRel => 1,
+        };
+        sink.push(flag);
+    }
+}
+
 /// Describe an unchecked SIMD lane index.
 pub type Lane = u8;
 
@@ -980,6 +1008,16 @@ pub enum Instruction<'a> {
     I64AtomicRmw8CmpxchgU(MemArg),
     I64AtomicRmw16CmpxchgU(MemArg),
     I64AtomicRmw32CmpxchgU(MemArg),
+
+    // More atomic instructions (the shared-everything-threads proposal)
+    GlobalAtomicGet {
+        ordering: Ordering,
+        global_index: u32,
+    },
+    GlobalAtomicSet {
+        ordering: Ordering,
+        global_index: u32,
+    },
 }
 
 impl Encode for Instruction<'_> {
@@ -2787,7 +2825,7 @@ impl Encode for Instruction<'_> {
                 0x113u32.encode(sink);
             }
 
-            // Atmoic instructions from the thread proposal
+            // Atomic instructions from the thread proposal
             Instruction::MemoryAtomicNotify(memarg) => {
                 sink.push(0xFE);
                 sink.push(0x00);
@@ -3123,6 +3161,26 @@ impl Encode for Instruction<'_> {
                 sink.push(0x4E);
                 memarg.encode(sink);
             }
+
+            // Atomic instructions from the shared-everything-threads proposal
+            Instruction::GlobalAtomicGet {
+                ordering,
+                global_index,
+            } => {
+                sink.push(0xFE);
+                sink.push(0x4F);
+                ordering.encode(sink);
+                global_index.encode(sink);
+            }
+            Instruction::GlobalAtomicSet {
+                ordering,
+                global_index,
+            } => {
+                sink.push(0xFE);
+                sink.push(0x50);
+                ordering.encode(sink);
+                global_index.encode(sink);
+            }
         }
     }
 }
diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs
index ca9e2998..4ddb7dd7 100644
--- a/crates/wasm-mutate/src/mutators/translate.rs
+++ b/crates/wasm-mutate/src/mutators/translate.rs
@@ -108,6 +108,13 @@ pub trait Translator {
         memarg(self.as_obj(), arg)
     }
 
+    fn translate_ordering(&mut self, arg: &wasmparser::Ordering) -> Result<Ordering> {
+        Ok(match arg {
+            wasmparser::Ordering::SeqCst => Ordering::SeqCst,
+            wasmparser::Ordering::AcqRel => Ordering::AcqRel,
+        })
+    }
+
     fn remap(&mut self, item: Item, idx: u32) -> Result<u32> {
         let _ = item;
         Ok(idx)
@@ -370,6 +377,7 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result<Instruction<'stat
         (map $arg:ident from_ref_type) => (t.translate_refty($arg)?);
         (map $arg:ident to_ref_type) => (t.translate_refty($arg)?);
         (map $arg:ident memarg) => (t.translate_memarg($arg)?);
+        (map $arg:ident ordering) => (t.translate_ordering($arg)?);
         (map $arg:ident local_index) => (*$arg);
         (map $arg:ident value) => ($arg);
         (map $arg:ident lane) => (*$arg);
diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs
index da139c16..9fde1a02 100644
--- a/crates/wasmparser/src/binary_reader.rs
+++ b/crates/wasmparser/src/binary_reader.rs
@@ -277,6 +277,18 @@ impl<'a> BinaryReader<'a> {
         })
     }
 
+    fn read_ordering(&mut self) -> Result<Ordering> {
+        let byte = self.read_var_u32()?;
+        match byte {
+            0 => Ok(Ordering::SeqCst),
+            1 => Ok(Ordering::AcqRel),
+            x => Err(BinaryReaderError::new(
+                &format!("invalid atomic consistency ordering {}", x),
+                self.original_position() - 1,
+            )),
+        }
+    }
+
     fn read_br_table(&mut self) -> Result<BrTable<'a>> {
         let cnt = self.read_size(MAX_WASM_BR_TABLE_SIZE, "br_table")?;
         let start = self.position;
@@ -1626,6 +1638,10 @@ impl<'a> BinaryReader<'a> {
             0x4d => visitor.visit_i64_atomic_rmw16_cmpxchg_u(self.read_memarg(1)?),
             0x4e => visitor.visit_i64_atomic_rmw32_cmpxchg_u(self.read_memarg(2)?),
 
+            // Decode shared-everything-threads proposal.
+            0x4f => visitor.visit_global_atomic_get(self.read_ordering()?, self.read_var_u32()?),
+            0x50 => visitor.visit_global_atomic_set(self.read_ordering()?, self.read_var_u32()?),
+
             _ => bail!(pos, "unknown 0xfe subopcode: 0x{code:x}"),
         })
     }
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index 3ca342bc..d2b6ced8 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -468,6 +468,12 @@ macro_rules! for_each_operator {
             @threads I64AtomicRmw16CmpxchgU { memarg: $crate::MemArg } => visit_i64_atomic_rmw16_cmpxchg_u
             @threads I64AtomicRmw32CmpxchgU { memarg: $crate::MemArg } => visit_i64_atomic_rmw32_cmpxchg_u
 
+            // Also 0xFE prefixed operators
+            // shared-everything threads
+            // https://github.com/WebAssembly/shared-everything-threads
+            @shared_everything_threads GlobalAtomicGet { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_get
+            @shared_everything_threads GlobalAtomicSet { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_set
+
             // 0xFD operators
             // 128-bit SIMD
             // - https://github.com/webassembly/simd
diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs
index 004982e7..7b5b3854 100644
--- a/crates/wasmparser/src/readers/core/operators.rs
+++ b/crates/wasmparser/src/readers/core/operators.rs
@@ -107,6 +107,24 @@ impl V128 {
     }
 }
 
+/// Represents the memory ordering for atomic instructions.
+///
+/// For an in-depth explanation of memory orderings, see the C++ documentation
+/// for [`memory_order`] or the Rust documentation for [`atomic::Ordering`].
+///
+/// [`memory_order`]: https://en.cppreference.com/w/cpp/atomic/memory_order
+/// [`atomic::Ordering`]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub enum Ordering {
+    /// For a load, it acquires; this orders all operations before the last
+    /// "releasing" store. For a store, it releases; this orders all operations
+    /// before it at the next "acquiring" load.
+    AcqRel,
+    /// Like `AcqRel` but all threads see all sequentially consistent operations
+    /// in the same order.
+    SeqCst,
+}
+
 macro_rules! define_operator {
     ($(@$proposal:ident $op:ident $({ $($payload:tt)* })? => $visit:ident)*) => {
         /// Instructions as defined [here].
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index e753ab1e..20f06fe2 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -780,6 +780,15 @@ impl ValType {
         }
     }
 
+    /// Whether the type is `shared`.
+    pub fn is_shared(&self) -> bool {
+        match *self {
+            Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
+            // TODO: parsing of `shared` refs is not yet implemented.
+            Self::Ref(_) => false,
+        }
+    }
+
     /// Maps any `UnpackedIndex` via the specified closure.
     pub(crate) fn remap_indices(
         &mut self,
@@ -1076,9 +1085,8 @@ impl RefType {
 
     /// Create a new `RefType`.
     ///
-    /// Returns `None` when the heap type's type index (if any) is
-    /// beyond this crate's implementation limits and therfore is not
-    /// representable.
+    /// Returns `None` when the heap type's type index (if any) is beyond this
+    /// crate's implementation limits and therefore is not representable.
     pub fn new(nullable: bool, heap_type: HeapType) -> Option<Self> {
         let nullable32 = Self::NULLABLE_BIT * (nullable as u32);
         match heap_type {
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index 5999538f..1fb8f834 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -510,6 +510,7 @@ impl ModuleState {
     }
 }
 
+#[derive(Debug)]
 pub(crate) struct Module {
     // This is set once the code section starts.
     // `WasmModuleResources` implementations use the snapshot to
@@ -1053,13 +1054,22 @@ impl Module {
         features: &WasmFeatures,
         offset: usize,
     ) -> Result<()> {
-        if ty.shared && !features.contains(WasmFeatures::SHARED_EVERYTHING_THREADS) {
-            return Err(BinaryReaderError::new(
-                "shared globals require the shared-everything-threads proposal",
-                offset,
-            ));
+        self.check_value_type(&mut ty.content_type, features, offset)?;
+        if ty.shared {
+            if !features.contains(WasmFeatures::SHARED_EVERYTHING_THREADS) {
+                return Err(BinaryReaderError::new(
+                    "shared globals require the shared-everything-threads proposal",
+                    offset,
+                ));
+            }
+            if !ty.content_type.is_shared() {
+                return Err(BinaryReaderError::new(
+                    "shared globals must have a shared value type",
+                    offset,
+                ));
+            }
         }
-        self.check_value_type(&mut ty.content_type, features, offset)
+        Ok(())
     }
 
     fn check_limits<T>(&self, initial: T, maximum: Option<T>, offset: usize) -> Result<()>
@@ -1271,6 +1281,7 @@ impl WasmModuleResources for OperatorValidatorResources<'_> {
 
 /// The implementation of [`WasmModuleResources`] used by
 /// [`Validator`](crate::Validator).
+#[derive(Debug)]
 pub struct ValidatorResources(pub(crate) Arc<Module>);
 
 impl WasmModuleResources for ValidatorResources {
diff --git a/crates/wasmparser/src/validator/func.rs b/crates/wasmparser/src/validator/func.rs
index 2f18fb55..0543538b 100644
--- a/crates/wasmparser/src/validator/func.rs
+++ b/crates/wasmparser/src/validator/func.rs
@@ -9,6 +9,7 @@ use crate::{FunctionBody, Operator, WasmFeatures, WasmModuleResources};
 /// is created per-function in a WebAssembly module. This structure is suitable
 /// for sending to other threads while the original
 /// [`Validator`](crate::Validator) continues processing other functions.
+#[derive(Debug)]
 pub struct FuncToValidate<T> {
     /// Reusable, heap allocated resources to drive the Wasm validation.
     pub resources: T,
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 5ad1c96a..540420bf 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -1064,6 +1064,24 @@ where
         self.push_operand(sub_ty)
     }
 
+    /// Common helper for checking the types of global types accessed
+    /// atomically.
+    fn check_atomic_global_ty(&self, global_index: u32) -> Result<()> {
+        let ty = self
+            .resources
+            .global_at(global_index)
+            .expect("existence should be checked prior to this point");
+        let ty = ty.content_type;
+        let supertype = RefType::ANYREF.into();
+        if !(ty == ValType::I32 || ty == ValType::I64 || self.resources.is_subtype(ty, supertype)) {
+            bail!(
+                    self.offset,
+                    "invalid type: `global.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`"
+                );
+        }
+        Ok(())
+    }
+
     fn element_type_at(&self, elem_index: u32) -> Result<RefType> {
         match self.resources.element_type_at(elem_index) {
             Some(ty) => Ok(ty),
@@ -1213,6 +1231,7 @@ macro_rules! validate_proposal {
     (desc simd) => ("SIMD");
     (desc relaxed_simd) => ("relaxed SIMD");
     (desc threads) => ("threads");
+    (desc shared_everything_threads) => ("shared-everything-threads");
     (desc saturating_float_to_int) => ("saturating float to int conversions");
     (desc reference_types) => ("reference types");
     (desc bulk_memory) => ("bulk memory");
@@ -1233,6 +1252,7 @@ macro_rules! validate_proposal {
     (bitflags reference_types) => (WasmFeatures::REFERENCE_TYPES);
     (bitflags function_references) => (WasmFeatures::FUNCTION_REFERENCES);
     (bitflags threads) => (WasmFeatures::THREADS);
+    (bitflags shared_everything_threads) => (WasmFeatures::SHARED_EVERYTHING_THREADS);
     (bitflags gc) => (WasmFeatures::GC);
     (bitflags memory_control) => (WasmFeatures::MEMORY_CONTROL);
 }
@@ -1625,6 +1645,17 @@ where
         };
         Ok(())
     }
+    fn visit_global_atomic_get(
+        &mut self,
+        _ordering: crate::Ordering,
+        global_index: u32,
+    ) -> Self::Output {
+        self.visit_global_get(global_index)?;
+        // No validation of `ordering` is needed because `global.atomic.get` can
+        // be used on both shared and unshared globals.
+        self.check_atomic_global_ty(global_index)?;
+        Ok(())
+    }
     fn visit_global_set(&mut self, global_index: u32) -> Self::Output {
         if let Some(ty) = self.resources.global_at(global_index) {
             if !ty.mutable {
@@ -1639,6 +1670,17 @@ where
         };
         Ok(())
     }
+    fn visit_global_atomic_set(
+        &mut self,
+        _ordering: crate::Ordering,
+        global_index: u32,
+    ) -> Self::Output {
+        self.visit_global_set(global_index)?;
+        // No validation of `ordering` is needed because `global.atomic.get` can
+        // be used on both shared and unshared globals.
+        self.check_atomic_global_ty(global_index)?;
+        Ok(())
+    }
     fn visit_i32_load(&mut self, memarg: MemArg) -> Self::Output {
         let ty = self.check_memarg(memarg)?;
         self.pop_operand(Some(ty))?;
diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs
index 1640ed8d..62c99980 100644
--- a/crates/wasmparser/src/validator/types.rs
+++ b/crates/wasmparser/src/validator/types.rs
@@ -2267,6 +2267,7 @@ where
 //
 // Only public because it shows up in a public trait's `doc(hidden)` method.
 #[doc(hidden)]
+#[derive(Debug)]
 pub struct SnapshotList<T> {
     // All previous snapshots, the "head" of the list that this type represents.
     // The first entry in this pair is the starting index for all elements
@@ -2286,6 +2287,7 @@ pub struct SnapshotList<T> {
     cur: Vec<T>,
 }
 
+#[derive(Debug)]
 struct Snapshot<T> {
     prior_types: usize,
     items: Vec<T>,
@@ -2394,7 +2396,7 @@ impl<T> Default for SnapshotList<T> {
 /// component model's {component, instance, defined, func} types are in the same
 /// index space). However, we store each of them in their own type-specific
 /// snapshot list and give each of them their own identifier type.
-#[derive(Default)]
+#[derive(Default, Debug)]
 // Only public because it shows up in a public trait's `doc(hidden)` method.
 #[doc(hidden)]
 pub struct TypeList {
diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs
index d91ee609..efcfa98d 100644
--- a/crates/wasmprinter/src/operator.rs
+++ b/crates/wasmprinter/src/operator.rs
@@ -1,7 +1,7 @@
 use super::{Printer, State};
 use anyhow::{anyhow, bail, Result};
 use std::fmt::Write;
-use wasmparser::{BlockType, BrTable, Catch, MemArg, RefType, TryTable, VisitOperator};
+use wasmparser::{BlockType, BrTable, Catch, MemArg, Ordering, RefType, TryTable, VisitOperator};
 
 pub struct PrintOperator<'a, 'b> {
     pub(super) printer: &'a mut Printer,
@@ -322,6 +322,18 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
         Ok(())
     }
 
+    fn ordering(&mut self, ordering: Ordering) -> Result<()> {
+        write!(
+            self.result(),
+            "{}",
+            match ordering {
+                Ordering::SeqCst => "seq_cst",
+                Ordering::AcqRel => "acq_rel",
+            }
+        )?;
+        Ok(())
+    }
+
     fn try_table(&mut self, table: TryTable) -> Result<()> {
         let has_name = self.blockty_without_label_comment(table.ty)?;
 
@@ -1122,6 +1134,8 @@ macro_rules! define_visit {
     (name Catch) => ("catch");
     (name CatchAll) => ("catch_all");
     (name Delegate) => ("delegate");
+    (name GlobalAtomicGet) => ("global.atomic.get");
+    (name GlobalAtomicSet) => ("global.atomic.set");
 }
 
 impl<'a> VisitOperator<'a> for PrintOperator<'_, '_> {
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 0337ec9f..769e1201 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -800,6 +800,23 @@ impl Encode for MemArg<'_> {
     }
 }
 
+impl Encode for Ordering {
+    fn encode(&self, buf: &mut Vec<u8>) {
+        let flag: u8 = match self {
+            Ordering::SeqCst => 0,
+            Ordering::AcqRel => 1,
+        };
+        flag.encode(buf);
+    }
+}
+
+impl Encode for OrderedAccess<'_> {
+    fn encode(&self, buf: &mut Vec<u8>) {
+        self.ordering.encode(buf);
+        self.index.encode(buf);
+    }
+}
+
 impl Encode for LoadOrStoreLane<'_> {
     fn encode(&self, e: &mut Vec<u8>) {
         self.memarg.encode(e);
diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs
index 3b7a5285..df0abb7c 100644
--- a/crates/wast/src/core/expr.rs
+++ b/crates/wast/src/core/expr.rs
@@ -800,6 +800,10 @@ instructions! {
         I64AtomicRmw16CmpxchgU(MemArg<2>) : [0xfe, 0x4d] : "i64.atomic.rmw16.cmpxchg_u",
         I64AtomicRmw32CmpxchgU(MemArg<4>) : [0xfe, 0x4e] : "i64.atomic.rmw32.cmpxchg_u",
 
+        // proposal: shared-everything-threads
+        GlobalAtomicGet(OrderedAccess<'a>) : [0xfe, 0x4f] : "global.atomic.get",
+        GlobalAtomicSet(OrderedAccess<'a>) : [0xfe, 0x50] : "global.atomic.set",
+
         // proposal: simd
         //
         // https://webassembly.github.io/simd/core/binary/instructions.html
@@ -1730,6 +1734,55 @@ impl<'a> Parse<'a> for BrOnCastFail<'a> {
     }
 }
 
+/// The memory ordering for atomic instructions.
+///
+/// For an in-depth explanation of memory orderings, see the C++ documentation
+/// for [`memory_order`] or the Rust documentation for [`atomic::Ordering`].
+///
+/// [`memory_order`]: https://en.cppreference.com/w/cpp/atomic/memory_order
+/// [`atomic::Ordering`]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
+#[derive(Clone, Debug)]
+pub enum Ordering {
+    /// Like `AcqRel` but all threads see all sequentially consistent operations
+    /// in the same order.
+    AcqRel,
+    /// For a load, it acquires; this orders all operations before the last
+    /// "releasing" store. For a store, it releases; this orders all operations
+    /// before it at the next "acquiring" load.
+    SeqCst,
+}
+
+impl<'a> Parse<'a> for Ordering {
+    fn parse(parser: Parser<'a>) -> Result<Self> {
+        if parser.peek::<kw::seq_cst>()? {
+            parser.parse::<kw::seq_cst>()?;
+            Ok(Ordering::SeqCst)
+        } else if parser.peek::<kw::acq_rel>()? {
+            parser.parse::<kw::acq_rel>()?;
+            Ok(Ordering::AcqRel)
+        } else {
+            Err(parser.error("expected a memory ordering: `seq_cst` or `acq_rel`"))
+        }
+    }
+}
+
+/// Extra data associated with the `global.atomic.*` instructions.
+#[derive(Clone, Debug)]
+pub struct OrderedAccess<'a> {
+    /// The memory ordering for this atomic instruction.
+    pub ordering: Ordering,
+    /// The index of the global to access.
+    pub index: Index<'a>,
+}
+
+impl<'a> Parse<'a> for OrderedAccess<'a> {
+    fn parse(parser: Parser<'a>) -> Result<Self> {
+        let ordering = parser.parse()?;
+        let index = parser.parse()?;
+        Ok(OrderedAccess { ordering, index })
+    }
+}
+
 /// Different ways to specify a `v128.const` instruction
 #[derive(Clone, Debug)]
 #[allow(missing_docs)]
diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs
index 54590138..f17590bb 100644
--- a/crates/wast/src/core/resolve/names.rs
+++ b/crates/wast/src/core/resolve/names.rs
@@ -457,6 +457,10 @@ impl<'a, 'b> ExprResolver<'a, 'b> {
                 self.resolver.resolve(i, Ns::Global)?;
             }
 
+            GlobalAtomicSet(i) | GlobalAtomicGet(i) => {
+                self.resolver.resolve(&mut i.index, Ns::Global)?;
+            }
+
             LocalSet(i) | LocalGet(i) | LocalTee(i) => {
                 assert!(self.scopes.len() > 0);
                 // Resolve a local by iterating over scopes from most recent
diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs
index 37bc5f7c..4964e8df 100644
--- a/crates/wast/src/lib.rs
+++ b/crates/wast/src/lib.rs
@@ -472,12 +472,14 @@ pub mod kw {
     custom_keyword!(ref_null = "ref.null");
     custom_keyword!(register);
     custom_keyword!(rec);
+    custom_keyword!(acq_rel);
     custom_keyword!(rep);
     custom_keyword!(resource);
     custom_keyword!(resource_new = "resource.new");
     custom_keyword!(resource_drop = "resource.drop");
     custom_keyword!(resource_rep = "resource.rep");
     custom_keyword!(result);
+    custom_keyword!(seq_cst);
     custom_keyword!(shared);
     custom_keyword!(start);
     custom_keyword!(sub);
diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs
index a6b1a008..92947745 100644
--- a/crates/wit-component/src/gc.rs
+++ b/crates/wit-component/src/gc.rs
@@ -1046,6 +1046,7 @@ macro_rules! define_visit {
     (mark_live $self:ident $arg:ident field_index) => {};
     (mark_live $self:ident $arg:ident from_type_nullable) => {};
     (mark_live $self:ident $arg:ident to_type_nullable) => {};
+    (mark_live $self:ident $arg:ident ordering) => {};
     (mark_live $self:ident $arg:ident try_table) => {unimplemented!();};
 }
 
@@ -1097,6 +1098,13 @@ impl Encoder {
         }
     }
 
+    fn ordering(&self, ord: Ordering) -> wasm_encoder::Ordering {
+        match ord {
+            Ordering::AcqRel => wasm_encoder::Ordering::AcqRel,
+            Ordering::SeqCst => wasm_encoder::Ordering::SeqCst,
+        }
+    }
+
     fn blockty(&self, ty: BlockType) -> wasm_encoder::BlockType {
         match ty {
             BlockType::Empty => wasm_encoder::BlockType::Empty,
@@ -1213,6 +1221,7 @@ macro_rules! define_encode {
     // Individual cases of mapping one argument type to another, similar to the
     // `define_visit` macro above.
     (map $self:ident $arg:ident memarg) => {$self.memarg($arg)};
+    (map $self:ident $arg:ident ordering) => {$self.ordering($arg)};
     (map $self:ident $arg:ident blockty) => {$self.blockty($arg)};
     (map $self:ident $arg:ident hty) => {$self.heapty($arg)};
     (map $self:ident $arg:ident from_ref_type) => {$self.refty($arg)};


Test patch:
diff --git a/tests/local/missing-features/globals.wast b/tests/local/missing-features/globals.wast
index fdfdf429..2439fc28 100644
--- a/tests/local/missing-features/globals.wast
+++ b/tests/local/missing-features/globals.wast
@@ -32,3 +32,10 @@
     (global (import "spectest" "global_i64") (shared mut i64))
   )
   "shared globals require the shared-everything-threads proposal")
+
+(assert_invalid
+  (module
+    (global $a (import "spectest" "global_i32") i32)
+    (func (result i32) (global.atomic.get seq_cst $a))
+  )
+  "shared-everything-threads support is not enabled")
diff --git a/tests/local/shared-everything-threads/global.wast b/tests/local/shared-everything-threads/global.wast
index e09340e9..2281017c 100644
--- a/tests/local/shared-everything-threads/global.wast
+++ b/tests/local/shared-everything-threads/global.wast
@@ -1,12 +1,37 @@
 ;; Styled after ../../testsuite/global.wast
 
 (module
-  (global (import "spectest" "global_i32") (shared i32))
-  (global (import "spectest" "global_f64") (shared mut f64))
-  (global $a (shared i64) (i64.const 0))
-  (global $b (shared mut i64) (i64.const 1))
+  (global $a (import "spectest" "global_i32") (shared i32))
+  (global $b (import "spectest" "global_i64") (shared mut i64))
+  (global $d (shared i32) (i32.const 0))
+  (global $e (shared mut i64) (i64.const 1))
+
+  (func (export "get-a-seqcst") (result i32) (global.atomic.get seq_cst $a))
+  (func (export "set-b-seqcst") (global.atomic.set seq_cst $b (i64.const 1)))
+  (func (export "get-d-acqrel") (result i32) (global.atomic.get acq_rel $d))
+  (func (export "set-e-acqrel") (global.atomic.set acq_rel $e (i64.const 2)))
 )
 
 (assert_malformed
   (module quote "(global (mut shared i64) (i64.const -1))")
   "unexpected token")
+
+(assert_invalid
+  (module
+    (global $a (shared i32) (i32.const 0))
+    (func (export "set-shared") (global.atomic.set seq_cst $a (i32.const 1)))
+  )
+  "global is immutable")
+
+(assert_invalid
+  (module
+    (global $a (shared mut f64) (f64.const 0))
+    (func (export "set-shared") (global.atomic.set acq_rel $a (f64.const 1.0)))
+  )
+  "invalid type")
+
+(assert_invalid
+  (module
+    (global $a (import "spectest" "global_ref") (shared funcref))
+  )
+  "shared value type")
diff --git a/tests/snapshots/local/missing-features/globals.wast.json b/tests/snapshots/local/missing-features/globals.wast.json
index 3ea942e9..f40cbdd0 100644
--- a/tests/snapshots/local/missing-features/globals.wast.json
+++ b/tests/snapshots/local/missing-features/globals.wast.json
@@ -35,6 +35,13 @@
       "filename": "globals.4.wasm",
       "text": "shared globals require the shared-everything-threads proposal",
       "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 37,
+      "filename": "globals.5.wasm",
+      "text": "shared-everything-threads support is not enabled",
+      "module_type": "binary"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/local/shared-everything-threads/global.wast.json b/tests/snapshots/local/shared-everything-threads/global.wast.json
index 5c142a3c..1ff22c67 100644
--- a/tests/snapshots/local/shared-everything-threads/global.wast.json
+++ b/tests/snapshots/local/shared-everything-threads/global.wast.json
@@ -8,10 +8,31 @@
     },
     {
       "type": "assert_malformed",
-      "line": 11,
+      "line": 16,
       "filename": "global.1.wat",
       "text": "unexpected token",
       "module_type": "text"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 20,
+      "filename": "global.2.wasm",
+      "text": "global is immutable",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 27,
+      "filename": "global.3.wasm",
+      "text": "invalid type",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 34,
+      "filename": "global.4.wasm",
+      "text": "shared value type",
+      "module_type": "binary"
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/snapshots/local/shared-everything-threads/global.wast/0.print b/tests/snapshots/local/shared-everything-threads/global.wast/0.print
index a9743b6f..9fddda89 100644
--- a/tests/snapshots/local/shared-everything-threads/global.wast/0.print
+++ b/tests/snapshots/local/shared-everything-threads/global.wast/0.print
@@ -1,6 +1,26 @@
 (module
-  (import "spectest" "global_i32" (global (;0;) (shared i32)))
-  (import "spectest" "global_f64" (global (;1;) (shared mut f64)))
-  (global $a (;2;) (shared i64) i64.const 0)
-  (global $b (;3;) (shared mut i64) i64.const 1)
+  (type (;0;) (func (result i32)))
+  (type (;1;) (func))
+  (import "spectest" "global_i32" (global $a (;0;) (shared i32)))
+  (import "spectest" "global_i64" (global $b (;1;) (shared mut i64)))
+  (func (;0;) (type 0) (result i32)
+    global.atomic.get seq_cst $a
+  )
+  (func (;1;) (type 1)
+    i64.const 1
+    global.atomic.set seq_cst $b
+  )
+  (func (;2;) (type 0) (result i32)
+    global.atomic.get acq_rel $d
+  )
+  (func (;3;) (type 1)
+    i64.const 2
+    global.atomic.set acq_rel $e
+  )
+  (global $d (;2;) (shared i32) i32.const 0)
+  (global $e (;3;) (shared mut i64) i64.const 1)
+  (export "get-a-seqcst" (func 0))
+  (export "set-b-seqcst" (func 1))
+  (export "get-d-acqrel" (func 2))
+  (export "set-e-acqrel" (func 3))
 )