Skip to content

Commit 076dfc1

Browse files
authored
threads: add atomic versions of global.get|set (#1483)
* 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 * review: use variable u32 encoding for ordering * Allow subtypes of `anyref` * Tighten up subtyping of declared globals; improve tests * Fix typo * review: fix type check to use `anyref` * fix: remove special validation of global type * review: check shared-ness of global type
1 parent dabd016 commit 076dfc1

File tree

19 files changed

+364
-20
lines changed

19 files changed

+364
-20
lines changed

crates/wasm-encoder/src/core/code.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,34 @@ impl Encode for MemArg {
274274
}
275275
}
276276

277+
/// The memory ordering for atomic instructions.
278+
///
279+
/// For an in-depth explanation of memory orderings, see the C++ documentation
280+
/// for [`memory_order`] or the Rust documentation for [`atomic::Ordering`].
281+
///
282+
/// [`memory_order`]: https://en.cppreference.com/w/cpp/atomic/memory_order
283+
/// [`atomic::Ordering`]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
284+
#[derive(Clone, Copy, Debug)]
285+
pub enum Ordering {
286+
/// For a load, it acquires; this orders all operations before the last
287+
/// "releasing" store. For a store, it releases; this orders all operations
288+
/// before it at the next "acquiring" load.
289+
AcqRel,
290+
/// Like `AcqRel` but all threads see all sequentially consistent operations
291+
/// in the same order.
292+
SeqCst,
293+
}
294+
295+
impl Encode for Ordering {
296+
fn encode(&self, sink: &mut Vec<u8>) {
297+
let flag: u8 = match self {
298+
Ordering::SeqCst => 0,
299+
Ordering::AcqRel => 1,
300+
};
301+
sink.push(flag);
302+
}
303+
}
304+
277305
/// Describe an unchecked SIMD lane index.
278306
pub type Lane = u8;
279307

@@ -980,6 +1008,16 @@ pub enum Instruction<'a> {
9801008
I64AtomicRmw8CmpxchgU(MemArg),
9811009
I64AtomicRmw16CmpxchgU(MemArg),
9821010
I64AtomicRmw32CmpxchgU(MemArg),
1011+
1012+
// More atomic instructions (the shared-everything-threads proposal)
1013+
GlobalAtomicGet {
1014+
ordering: Ordering,
1015+
global_index: u32,
1016+
},
1017+
GlobalAtomicSet {
1018+
ordering: Ordering,
1019+
global_index: u32,
1020+
},
9831021
}
9841022

9851023
impl Encode for Instruction<'_> {
@@ -2787,7 +2825,7 @@ impl Encode for Instruction<'_> {
27872825
0x113u32.encode(sink);
27882826
}
27892827

2790-
// Atmoic instructions from the thread proposal
2828+
// Atomic instructions from the thread proposal
27912829
Instruction::MemoryAtomicNotify(memarg) => {
27922830
sink.push(0xFE);
27932831
sink.push(0x00);
@@ -3123,6 +3161,26 @@ impl Encode for Instruction<'_> {
31233161
sink.push(0x4E);
31243162
memarg.encode(sink);
31253163
}
3164+
3165+
// Atomic instructions from the shared-everything-threads proposal
3166+
Instruction::GlobalAtomicGet {
3167+
ordering,
3168+
global_index,
3169+
} => {
3170+
sink.push(0xFE);
3171+
sink.push(0x4F);
3172+
ordering.encode(sink);
3173+
global_index.encode(sink);
3174+
}
3175+
Instruction::GlobalAtomicSet {
3176+
ordering,
3177+
global_index,
3178+
} => {
3179+
sink.push(0xFE);
3180+
sink.push(0x50);
3181+
ordering.encode(sink);
3182+
global_index.encode(sink);
3183+
}
31263184
}
31273185
}
31283186
}

crates/wasm-mutate/src/mutators/translate.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ pub trait Translator {
108108
memarg(self.as_obj(), arg)
109109
}
110110

111+
fn translate_ordering(&mut self, arg: &wasmparser::Ordering) -> Result<Ordering> {
112+
Ok(match arg {
113+
wasmparser::Ordering::SeqCst => Ordering::SeqCst,
114+
wasmparser::Ordering::AcqRel => Ordering::AcqRel,
115+
})
116+
}
117+
111118
fn remap(&mut self, item: Item, idx: u32) -> Result<u32> {
112119
let _ = item;
113120
Ok(idx)
@@ -370,6 +377,7 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result<Instruction<'stat
370377
(map $arg:ident from_ref_type) => (t.translate_refty($arg)?);
371378
(map $arg:ident to_ref_type) => (t.translate_refty($arg)?);
372379
(map $arg:ident memarg) => (t.translate_memarg($arg)?);
380+
(map $arg:ident ordering) => (t.translate_ordering($arg)?);
373381
(map $arg:ident local_index) => (*$arg);
374382
(map $arg:ident value) => ($arg);
375383
(map $arg:ident lane) => (*$arg);

crates/wasmparser/src/binary_reader.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,18 @@ impl<'a> BinaryReader<'a> {
277277
})
278278
}
279279

280+
fn read_ordering(&mut self) -> Result<Ordering> {
281+
let byte = self.read_var_u32()?;
282+
match byte {
283+
0 => Ok(Ordering::SeqCst),
284+
1 => Ok(Ordering::AcqRel),
285+
x => Err(BinaryReaderError::new(
286+
&format!("invalid atomic consistency ordering {}", x),
287+
self.original_position() - 1,
288+
)),
289+
}
290+
}
291+
280292
fn read_br_table(&mut self) -> Result<BrTable<'a>> {
281293
let cnt = self.read_size(MAX_WASM_BR_TABLE_SIZE, "br_table")?;
282294
let start = self.position;
@@ -1626,6 +1638,10 @@ impl<'a> BinaryReader<'a> {
16261638
0x4d => visitor.visit_i64_atomic_rmw16_cmpxchg_u(self.read_memarg(1)?),
16271639
0x4e => visitor.visit_i64_atomic_rmw32_cmpxchg_u(self.read_memarg(2)?),
16281640

1641+
// Decode shared-everything-threads proposal.
1642+
0x4f => visitor.visit_global_atomic_get(self.read_ordering()?, self.read_var_u32()?),
1643+
0x50 => visitor.visit_global_atomic_set(self.read_ordering()?, self.read_var_u32()?),
1644+
16291645
_ => bail!(pos, "unknown 0xfe subopcode: 0x{code:x}"),
16301646
})
16311647
}

crates/wasmparser/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,12 @@ macro_rules! for_each_operator {
468468
@threads I64AtomicRmw16CmpxchgU { memarg: $crate::MemArg } => visit_i64_atomic_rmw16_cmpxchg_u
469469
@threads I64AtomicRmw32CmpxchgU { memarg: $crate::MemArg } => visit_i64_atomic_rmw32_cmpxchg_u
470470

471+
// Also 0xFE prefixed operators
472+
// shared-everything threads
473+
// https://github.com/WebAssembly/shared-everything-threads
474+
@shared_everything_threads GlobalAtomicGet { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_get
475+
@shared_everything_threads GlobalAtomicSet { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_set
476+
471477
// 0xFD operators
472478
// 128-bit SIMD
473479
// - https://github.com/webassembly/simd

crates/wasmparser/src/readers/core/operators.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ impl V128 {
107107
}
108108
}
109109

110+
/// Represents the memory ordering for atomic instructions.
111+
///
112+
/// For an in-depth explanation of memory orderings, see the C++ documentation
113+
/// for [`memory_order`] or the Rust documentation for [`atomic::Ordering`].
114+
///
115+
/// [`memory_order`]: https://en.cppreference.com/w/cpp/atomic/memory_order
116+
/// [`atomic::Ordering`]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
117+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
118+
pub enum Ordering {
119+
/// For a load, it acquires; this orders all operations before the last
120+
/// "releasing" store. For a store, it releases; this orders all operations
121+
/// before it at the next "acquiring" load.
122+
AcqRel,
123+
/// Like `AcqRel` but all threads see all sequentially consistent operations
124+
/// in the same order.
125+
SeqCst,
126+
}
127+
110128
macro_rules! define_operator {
111129
($(@$proposal:ident $op:ident $({ $($payload:tt)* })? => $visit:ident)*) => {
112130
/// Instructions as defined [here].

crates/wasmparser/src/readers/core/types.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,15 @@ impl ValType {
780780
}
781781
}
782782

783+
/// Whether the type is `shared`.
784+
pub fn is_shared(&self) -> bool {
785+
match *self {
786+
Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
787+
// TODO: parsing of `shared` refs is not yet implemented.
788+
Self::Ref(_) => false,
789+
}
790+
}
791+
783792
/// Maps any `UnpackedIndex` via the specified closure.
784793
pub(crate) fn remap_indices(
785794
&mut self,
@@ -1076,9 +1085,8 @@ impl RefType {
10761085

10771086
/// Create a new `RefType`.
10781087
///
1079-
/// Returns `None` when the heap type's type index (if any) is
1080-
/// beyond this crate's implementation limits and therfore is not
1081-
/// representable.
1088+
/// Returns `None` when the heap type's type index (if any) is beyond this
1089+
/// crate's implementation limits and therefore is not representable.
10821090
pub fn new(nullable: bool, heap_type: HeapType) -> Option<Self> {
10831091
let nullable32 = Self::NULLABLE_BIT * (nullable as u32);
10841092
match heap_type {

crates/wasmparser/src/validator/core.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,13 +1054,22 @@ impl Module {
10541054
features: &WasmFeatures,
10551055
offset: usize,
10561056
) -> Result<()> {
1057-
if ty.shared && !features.contains(WasmFeatures::SHARED_EVERYTHING_THREADS) {
1058-
return Err(BinaryReaderError::new(
1059-
"shared globals require the shared-everything-threads proposal",
1060-
offset,
1061-
));
1057+
self.check_value_type(&mut ty.content_type, features, offset)?;
1058+
if ty.shared {
1059+
if !features.contains(WasmFeatures::SHARED_EVERYTHING_THREADS) {
1060+
return Err(BinaryReaderError::new(
1061+
"shared globals require the shared-everything-threads proposal",
1062+
offset,
1063+
));
1064+
}
1065+
if !ty.content_type.is_shared() {
1066+
return Err(BinaryReaderError::new(
1067+
"shared globals must have a shared value type",
1068+
offset,
1069+
));
1070+
}
10621071
}
1063-
self.check_value_type(&mut ty.content_type, features, offset)
1072+
Ok(())
10641073
}
10651074

10661075
fn check_limits<T>(&self, initial: T, maximum: Option<T>, offset: usize) -> Result<()>

crates/wasmparser/src/validator/operators.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,24 @@ where
10641064
self.push_operand(sub_ty)
10651065
}
10661066

1067+
/// Common helper for checking the types of global types accessed
1068+
/// atomically.
1069+
fn check_atomic_global_ty(&self, global_index: u32) -> Result<()> {
1070+
let ty = self
1071+
.resources
1072+
.global_at(global_index)
1073+
.expect("existence should be checked prior to this point");
1074+
let ty = ty.content_type;
1075+
let supertype = RefType::ANYREF.into();
1076+
if !(ty == ValType::I32 || ty == ValType::I64 || self.resources.is_subtype(ty, supertype)) {
1077+
bail!(
1078+
self.offset,
1079+
"invalid type: `global.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`"
1080+
);
1081+
}
1082+
Ok(())
1083+
}
1084+
10671085
fn element_type_at(&self, elem_index: u32) -> Result<RefType> {
10681086
match self.resources.element_type_at(elem_index) {
10691087
Some(ty) => Ok(ty),
@@ -1213,6 +1231,7 @@ macro_rules! validate_proposal {
12131231
(desc simd) => ("SIMD");
12141232
(desc relaxed_simd) => ("relaxed SIMD");
12151233
(desc threads) => ("threads");
1234+
(desc shared_everything_threads) => ("shared-everything-threads");
12161235
(desc saturating_float_to_int) => ("saturating float to int conversions");
12171236
(desc reference_types) => ("reference types");
12181237
(desc bulk_memory) => ("bulk memory");
@@ -1233,6 +1252,7 @@ macro_rules! validate_proposal {
12331252
(bitflags reference_types) => (WasmFeatures::REFERENCE_TYPES);
12341253
(bitflags function_references) => (WasmFeatures::FUNCTION_REFERENCES);
12351254
(bitflags threads) => (WasmFeatures::THREADS);
1255+
(bitflags shared_everything_threads) => (WasmFeatures::SHARED_EVERYTHING_THREADS);
12361256
(bitflags gc) => (WasmFeatures::GC);
12371257
(bitflags memory_control) => (WasmFeatures::MEMORY_CONTROL);
12381258
}
@@ -1625,6 +1645,17 @@ where
16251645
};
16261646
Ok(())
16271647
}
1648+
fn visit_global_atomic_get(
1649+
&mut self,
1650+
_ordering: crate::Ordering,
1651+
global_index: u32,
1652+
) -> Self::Output {
1653+
self.visit_global_get(global_index)?;
1654+
// No validation of `ordering` is needed because `global.atomic.get` can
1655+
// be used on both shared and unshared globals.
1656+
self.check_atomic_global_ty(global_index)?;
1657+
Ok(())
1658+
}
16281659
fn visit_global_set(&mut self, global_index: u32) -> Self::Output {
16291660
if let Some(ty) = self.resources.global_at(global_index) {
16301661
if !ty.mutable {
@@ -1639,6 +1670,17 @@ where
16391670
};
16401671
Ok(())
16411672
}
1673+
fn visit_global_atomic_set(
1674+
&mut self,
1675+
_ordering: crate::Ordering,
1676+
global_index: u32,
1677+
) -> Self::Output {
1678+
self.visit_global_set(global_index)?;
1679+
// No validation of `ordering` is needed because `global.atomic.get` can
1680+
// be used on both shared and unshared globals.
1681+
self.check_atomic_global_ty(global_index)?;
1682+
Ok(())
1683+
}
16421684
fn visit_i32_load(&mut self, memarg: MemArg) -> Self::Output {
16431685
let ty = self.check_memarg(memarg)?;
16441686
self.pop_operand(Some(ty))?;

crates/wasmprinter/src/operator.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{Printer, State};
22
use anyhow::{anyhow, bail, Result};
33
use std::fmt::Write;
4-
use wasmparser::{BlockType, BrTable, Catch, MemArg, RefType, TryTable, VisitOperator};
4+
use wasmparser::{BlockType, BrTable, Catch, MemArg, Ordering, RefType, TryTable, VisitOperator};
55

66
pub struct PrintOperator<'a, 'b> {
77
pub(super) printer: &'a mut Printer,
@@ -322,6 +322,18 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
322322
Ok(())
323323
}
324324

325+
fn ordering(&mut self, ordering: Ordering) -> Result<()> {
326+
write!(
327+
self.result(),
328+
"{}",
329+
match ordering {
330+
Ordering::SeqCst => "seq_cst",
331+
Ordering::AcqRel => "acq_rel",
332+
}
333+
)?;
334+
Ok(())
335+
}
336+
325337
fn try_table(&mut self, table: TryTable) -> Result<()> {
326338
let has_name = self.blockty_without_label_comment(table.ty)?;
327339

@@ -1122,6 +1134,8 @@ macro_rules! define_visit {
11221134
(name Catch) => ("catch");
11231135
(name CatchAll) => ("catch_all");
11241136
(name Delegate) => ("delegate");
1137+
(name GlobalAtomicGet) => ("global.atomic.get");
1138+
(name GlobalAtomicSet) => ("global.atomic.set");
11251139
}
11261140

11271141
impl<'a> VisitOperator<'a> for PrintOperator<'_, '_> {

crates/wast/src/core/binary.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,23 @@ impl Encode for MemArg<'_> {
800800
}
801801
}
802802

803+
impl Encode for Ordering {
804+
fn encode(&self, buf: &mut Vec<u8>) {
805+
let flag: u8 = match self {
806+
Ordering::SeqCst => 0,
807+
Ordering::AcqRel => 1,
808+
};
809+
flag.encode(buf);
810+
}
811+
}
812+
813+
impl Encode for OrderedAccess<'_> {
814+
fn encode(&self, buf: &mut Vec<u8>) {
815+
self.ordering.encode(buf);
816+
self.index.encode(buf);
817+
}
818+
}
819+
803820
impl Encode for LoadOrStoreLane<'_> {
804821
fn encode(&self, e: &mut Vec<u8>) {
805822
self.memarg.encode(e);

0 commit comments

Comments
 (0)