From a34bbfe5c0038b6ebcdc71d61a3b518f5aaf47ef Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 16 Jun 2026 21:17:44 +0000 Subject: [PATCH 1/2] Relaxed atomic fence --- CHANGELOG.md | 5 +++ scripts/test/generate_atomic_spec_test.py | 33 ++++++++++++++++--- .../test/relaxed_atomic_execution_tests.py | 5 +++ src/binaryen-c.cpp | 17 ++++++---- src/binaryen-c.h | 7 ++-- src/js/binaryen.js-post.js | 4 +-- src/parser/contexts.h | 7 ++-- src/parser/parsers.h | 9 +++-- src/passes/Print.cpp | 5 ++- src/tools/fuzzing/fuzzing.cpp | 2 +- src/wasm-builder.h | 6 +++- src/wasm-ir-builder.h | 2 +- src/wasm.h | 5 +-- src/wasm/wasm-binary.cpp | 9 +++-- src/wasm/wasm-ir-builder.cpp | 5 +-- src/wasm/wasm-stack.cpp | 17 ++++++++-- src/wasm/wasm-validator.cpp | 18 +++++++--- test/binaryen.js/atomics.js | 2 +- test/binaryen.js/expressions.js | 10 +++--- test/binaryen.js/expressions.js.txt | 2 +- test/binaryen.js/kitchen-sink.js | 9 +++-- test/binaryen.js/kitchen-sink.js.txt | 4 ++- test/example/c-api-kitchen-sink.c | 10 ++++-- test/example/c-api-kitchen-sink.txt | 2 ++ test/example/match.cpp | 2 +- test/lit/basic/relaxed-atomics.wast | 4 +++ test/lit/validation/relaxed-atomics.wast | 1 + test/spec/relaxed-atomics.wast | 18 ++++++++-- 28 files changed, 163 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a2e838529..f077802c417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ full changeset diff at the end of each section. Current Trunk ------------- +- Add relaxed atomics support for atomic fences (#8845). Breaks the C and JS + apis; the BinaryenAtomicFence / module.atomic.fence now take a memory order + param. Use BinaryenMemoryOrderSeqCst() / binaryen.MemoryOrder.seqcst to + preserve the original behavior. + v130 ---- diff --git a/scripts/test/generate_atomic_spec_test.py b/scripts/test/generate_atomic_spec_test.py index 149bb928c89..e3acdfcd4f0 100644 --- a/scripts/test/generate_atomic_spec_test.py +++ b/scripts/test/generate_atomic_spec_test.py @@ -38,7 +38,8 @@ class Template: bin: bytes = b"" -templates = [ +atomic_fence_template = Template(op="atomic.fence", value_type=None, args=0, should_drop=False, bin=b"\xfe\x03") +load_store_acqrel_templates = [ Template(op="i32.atomic.load", value_type=ValueType.i32, args=1, should_drop=True, bin=b"\xfe\x10"), Template(op="i64.atomic.load", value_type=ValueType.i64, args=1, should_drop=True, bin=b"\xfe\x11"), Template(op="i32.atomic.load8_u", value_type=ValueType.i64, args=1, should_drop=True, bin=b"\xfe\x12"), @@ -116,11 +117,20 @@ def all_combinations() -> Iterator[(Template, (int, ValueType), Ordering)]: # See the memory section defined in `binary_test` memories = [(None, ValueType.i32), (0, ValueType.i32), (1, ValueType.i64)] - return itertools.product(templates, memories, [None, Ordering.acqrel, Ordering.seqcst]) + yield from itertools.product(load_store_acqrel_templates, memories, [None, Ordering.acqrel, Ordering.seqcst]) + + for ordering in None, Ordering.acqrel, Ordering.seqcst: + yield atomic_fence_template, (None, None), ordering def statement(template, mem_idx: int | None, mem_ptr_type: ValueType, ordering: Ordering | None): """Return a statement exercising the op in `template` e.g. (i32.atomic.store 1 acqrel (i64.const 42) (i32.const 42)).""" + if template.op == "atomic.fence": + assert mem_idx is None + assert mem_ptr_type is None + + return f"(atomic.fence{' ' + ordering.name if ordering is not None else ''})" + memargs = [] if mem_idx is not None: memargs.append(str(mem_idx)) @@ -139,7 +149,7 @@ def statement(template, mem_idx: int | None, mem_ptr_type: ValueType, ordering: def func(): - """Return a func exercising all ops in `templates`. + """Return a func exercising all atomic ops. e.g. (func $test-all-ops @@ -156,7 +166,7 @@ def func(): def text_test(): - """Return a (module ...) that exercises all ops in `templates`.""" + """Return a (module ...) that exercises all atomic ops.""" return f'''(module (memory i32 1 1) (memory i64 1 1) @@ -194,6 +204,16 @@ def bin_statement_lines(template: Template, mem_idx: int, mem_ptr_type: ValueTyp The entire iterator represents a complete expression using the `template`. e.g. (drop (i32.atomic.load (i32.const 42))) """ + if template.op == "atomic.fence": + assert mem_idx is None + assert mem_ptr_type is None + + assert ordering is not None + + yield template.bin, template.op + yield int.to_bytes(ordering.value), f"{ordering.name} memory ordering" + return + arg_one_bin = i64_const(0) if mem_ptr_type == ValueType.i64 else i32_const(0) yield arg_one_bin, f"({mem_ptr_type.name}.const 0)" for _ in range(template.args - 1): @@ -272,6 +292,11 @@ def binary_func_body(): statement_bins = [] for template, (mem_idx, mem_ptr_type), ordering in all_combinations(): + # In the binary encoding for atomic.fence, there's no 'default' without + # a memory ordering. Skip this case. + if template.op == "atomic.fence" and ordering is None: + continue + bin, s = bin_statement(template, mem_idx, mem_ptr_type, ordering) statement_bins.append(bin) statement_strs.append(s) diff --git a/scripts/test/relaxed_atomic_execution_tests.py b/scripts/test/relaxed_atomic_execution_tests.py index 0ca0d66556d..ddc76d3c82f 100644 --- a/scripts/test/relaxed_atomic_execution_tests.py +++ b/scripts/test/relaxed_atomic_execution_tests.py @@ -79,6 +79,7 @@ (func (export "i64.atomic.rmw16.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw16.cmpxchg_u acqrel (local.get $addr) (local.get $expected) (local.get $value))) (func (export "i64.atomic.rmw32.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw32.cmpxchg_u acqrel (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "atomic.fence") (atomic.fence acqrel)) ) ;; *.atomic.load* @@ -390,6 +391,10 @@ (assert_return (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 0) (i64.const 0x11111111) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) (assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111cabba6e5)) +;; atomic.fence + +(invoke "atomic.fence") + ;; unaligned accesses diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 038a049551a..d098d71c04e 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1500,8 +1500,10 @@ BinaryenExpressionRef BinaryenAtomicNotify(BinaryenModuleRef module, 0, getMemoryName(module, memoryName))); } -BinaryenExpressionRef BinaryenAtomicFence(BinaryenModuleRef module) { - return static_cast(Builder(*(Module*)module).makeAtomicFence()); +BinaryenExpressionRef BinaryenAtomicFence(BinaryenModuleRef module, + BinaryenMemoryOrder order) { + return static_cast( + Builder(*(Module*)module).makeAtomicFence(static_cast(order))); } BinaryenExpressionRef BinaryenSIMDExtract(BinaryenModuleRef module, BinaryenOp op, @@ -3381,15 +3383,18 @@ void BinaryenAtomicNotifySetNotifyCount(BinaryenExpressionRef expr, (Expression*)notifyCountExpr; } // AtomicFence -uint8_t BinaryenAtomicFenceGetOrder(BinaryenExpressionRef expr) { +BinaryenMemoryOrder BinaryenAtomicFenceGetOrder(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; assert(expression->is()); - return static_cast(expression)->order; + return static_cast( + static_cast(expression)->order); } -void BinaryenAtomicFenceSetOrder(BinaryenExpressionRef expr, uint8_t order) { +void BinaryenAtomicFenceSetOrder(BinaryenExpressionRef expr, + BinaryenMemoryOrder order) { auto* expression = (Expression*)expr; assert(expression->is()); - static_cast(expression)->order = order; + static_cast(expression)->order = + static_cast(order); } // SIMDExtract BinaryenOp BinaryenSIMDExtractGetOp(BinaryenExpressionRef expr) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index ded15aaf1d2..fa4d53078fd 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -930,7 +930,7 @@ BinaryenAtomicNotify(BinaryenModuleRef module, BinaryenExpressionRef notifyCount, const char* memoryName); BINARYEN_API BinaryenExpressionRef -BinaryenAtomicFence(BinaryenModuleRef module); +BinaryenAtomicFence(BinaryenModuleRef module, BinaryenMemoryOrder order); BINARYEN_API BinaryenExpressionRef BinaryenSIMDExtract(BinaryenModuleRef module, BinaryenOp op, @@ -1961,10 +1961,11 @@ BinaryenAtomicNotifySetNotifyCount(BinaryenExpressionRef expr, // AtomicFence // Gets the order of an `atomic.fence` expression. -BINARYEN_API uint8_t BinaryenAtomicFenceGetOrder(BinaryenExpressionRef expr); +BINARYEN_API BinaryenMemoryOrder +BinaryenAtomicFenceGetOrder(BinaryenExpressionRef expr); // Sets the order of an `atomic.fence` expression. BINARYEN_API void BinaryenAtomicFenceSetOrder(BinaryenExpressionRef expr, - uint8_t order); + BinaryenMemoryOrder order); // SIMDExtract diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 5a3c6e3f842..5d4d928bc1f 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2481,8 +2481,8 @@ function wrapModule(module, self = {}) { }; self['atomic'] = { - 'fence'() { - return Module['_BinaryenAtomicFence'](module); + 'fence'(order) { + return Module['_BinaryenAtomicFence'](module, order); } }; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 39c37d07530..31a3aa253e3 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -617,7 +617,7 @@ struct NullInstrParserCtx { MemargT) { return Ok{}; } - Result<> makeAtomicFence(Index, const std::vector&) { + Result<> makeAtomicFence(Index, const std::vector&, MemoryOrder) { return Ok{}; } Result<> makePause(Index, const std::vector&) { return Ok{}; } @@ -2448,8 +2448,9 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { } Result<> makeAtomicFence(Index pos, - const std::vector& annotations) { - return withLoc(pos, irBuilder.makeAtomicFence()); + const std::vector& annotations, + MemoryOrder order) { + return withLoc(pos, irBuilder.makeAtomicFence(order)); } Result<> makePause(Index pos, const std::vector& annotations) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index ffbcdfae233..d6f2297ff57 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -150,7 +150,8 @@ Result<> makeAtomicWait(Ctx&, Index, const std::vector&, Type type); template Result<> makeAtomicNotify(Ctx&, Index, const std::vector&); template -Result<> makeAtomicFence(Ctx&, Index, const std::vector&); +Result<> +makeAtomicFence(Ctx&, Index, const std::vector&, MemoryOrder); template Result<> makePause(Ctx&, Index, const std::vector&); template @@ -1958,12 +1959,14 @@ Result<> makeAtomicNotify(Ctx& ctx, CHECK_ERR(arg); return ctx.makeAtomicNotify(pos, annotations, mem.getPtr(), *arg); } - template Result<> makeAtomicFence(Ctx& ctx, Index pos, const std::vector& annotations) { - return ctx.makeAtomicFence(pos, annotations); + auto maybeOrder = maybeMemOrder(ctx); + CHECK_ERR(maybeOrder); + MemoryOrder order = maybeOrder ? *maybeOrder : MemoryOrder::SeqCst; + return ctx.makeAtomicFence(pos, annotations, order); } template diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 2180749eac0..d171caa3080 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -647,7 +647,10 @@ struct PrintExpressionContents o << " offset=" << curr->offset; } } - void visitAtomicFence(AtomicFence* curr) { printMedium(o, "atomic.fence"); } + void visitAtomicFence(AtomicFence* curr) { + printMedium(o, "atomic.fence"); + printMemoryOrder(curr->order); + } void visitPause(Pause* curr) { printMedium(o, "pause"); } void visitSIMDExtract(SIMDExtract* curr) { prepareColor(o); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 2e1e9281b07..7edba72623f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -5191,7 +5191,7 @@ Expression* TranslateToFuzzReader::makeAtomic(Type type) { } wasm.memories[0]->shared = true; if (type == Type::none) { - return builder.makeAtomicFence(); + return builder.makeAtomicFence(MemoryOrder::SeqCst); } if (type == Type::i32 && oneIn(2)) { if (ATOMIC_WAITS && oneIn(2)) { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0bf270ab8ef..de26f090dc5 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -428,7 +428,11 @@ class Builder { notify->memory = memory; return notify; } - AtomicFence* makeAtomicFence() { return wasm.allocator.alloc(); } + AtomicFence* makeAtomicFence(MemoryOrder order) { + auto* ret = wasm.allocator.alloc(); + ret->order = order; + return ret; + } Pause* makePause() { return wasm.allocator.alloc(); } Store* makeStore(unsigned bytes, Address offset, diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 21ed4650202..8c07ce54649 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -169,7 +169,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { unsigned bytes, Address offset, Type type, Name mem, MemoryOrder order); Result<> makeAtomicWait(Type type, Address offset, Name mem); Result<> makeAtomicNotify(Address offset, Name mem); - Result<> makeAtomicFence(); + Result<> makeAtomicFence(MemoryOrder order); Result<> makePause(); Result<> makeSIMDExtract(SIMDExtractOp op, uint8_t lane); Result<> makeSIMDReplace(SIMDReplaceOp op, uint8_t lane); diff --git a/src/wasm.h b/src/wasm.h index 046cd271d0c..f5cade544e6 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1110,10 +1110,7 @@ class AtomicFence : public SpecificExpression { AtomicFence() = default; AtomicFence(MixedArena& allocator) : AtomicFence() {} - // Current wasm threads only supports sequentially consistent atomics, but - // other orderings may be added in the future. This field is reserved for - // that, and currently set to 0. - uint8_t order = 0; + MemoryOrder order = MemoryOrder::SeqCst; }; class Pause : public SpecificExpression { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 1e4e3f78b98..2702fe6e1db 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3991,11 +3991,10 @@ Result<> WasmBinaryReader::readInst() { auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); return builder.makeAtomicNotify(offset, mem); } - case BinaryConsts::AtomicFence: - if (getInt8() != 0) { - return Err{"expected 0x00 byte immediate on atomic.fence"}; - } - return builder.makeAtomicFence(); + case BinaryConsts::AtomicFence: { + MemoryOrder order = getMemoryOrder(/*isRMW=*/false); + return builder.makeAtomicFence(order); + } case BinaryConsts::Pause: return builder.makePause(); case BinaryConsts::StructAtomicGet: diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 9a66bb50fe2..4c3f7c8635c 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1635,8 +1635,9 @@ Result<> IRBuilder::makeAtomicNotify(Address offset, Name mem) { return Ok{}; } -Result<> IRBuilder::makeAtomicFence() { - push(builder.makeAtomicFence()); +Result<> IRBuilder::makeAtomicFence(MemoryOrder order) { + auto* fence = builder.makeAtomicFence(order); + push(fence); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 1b235ae9301..ad9c5a8b16d 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -632,8 +632,21 @@ void BinaryInstWriter::visitAtomicNotify(AtomicNotify* curr) { void BinaryInstWriter::visitAtomicFence(AtomicFence* curr) { o << static_cast(BinaryConsts::AtomicPrefix) - << static_cast(BinaryConsts::AtomicFence) - << static_cast(curr->order); + << static_cast(BinaryConsts::AtomicFence); + + switch (curr->order) { + case MemoryOrder::SeqCst: { + o << static_cast(0); + break; + } + case MemoryOrder::AcqRel: { + o << static_cast(1); + break; + } + case MemoryOrder::Unordered: { + WASM_UNREACHABLE("Unexpected memory ordering for atomic.fence"); + } + } } void BinaryInstWriter::visitPause(Pause* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a28faacc3c0..83ad12a81b3 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -1446,10 +1446,20 @@ void FunctionValidator::visitAtomicFence(AtomicFence* curr) { shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operations require threads [--enable-threads]"); - shouldBeTrue(curr->order == 0, - curr, - "Currently only sequentially consistent atomics are supported, " - "so AtomicFence's order should be 0"); + switch (curr->order) { + case MemoryOrder::AcqRel: { + shouldBeTrue(getModule()->features.hasRelaxedAtomics(), + curr, + "Acquire/release operations require relaxed atomics " + "[--enable-relaxed-atomics]"); + break; + } + case MemoryOrder::SeqCst: + break; + case MemoryOrder::Unordered: + shouldBeTrue(false, curr, "Atomic fence cannot be unordered"); + break; + } } void FunctionValidator::visitPause(Pause* curr) { diff --git a/test/binaryen.js/atomics.js b/test/binaryen.js/atomics.js index 471e4c52923..5eabd211e2e 100644 --- a/test/binaryen.js/atomics.js +++ b/test/binaryen.js/atomics.js @@ -79,7 +79,7 @@ module.addFunction("main", binaryen.none, binaryen.none, [], module.block("", [ ) ), // fence - module.atomic.fence() + module.atomic.fence(binaryen.MemoryOrder.seqcst) ])); module.setFeatures(binaryen.Features.Atomics); diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index a2b7319b9e7..494b802fa87 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -1372,10 +1372,10 @@ console.log("# AtomicFence"); (function testAtomicFence() { const module = new binaryen.Module(); - const theAtomicFence = binaryen.AtomicFence(module.atomic.fence()); + const theAtomicFence = binaryen.AtomicFence(module.atomic.fence(binaryen.MemoryOrder.seqcst)); assert(theAtomicFence instanceof binaryen.AtomicFence); assert(theAtomicFence instanceof binaryen.Expression); - assert(theAtomicFence.order === 0); // reserved, not yet used + assert(theAtomicFence.order === binaryen.MemoryOrder.seqcst); assert(theAtomicFence.type === binaryen.none); var info = binaryen.getExpressionInfo(theAtomicFence); @@ -1383,8 +1383,8 @@ console.log("# AtomicFence"); assert(info.type === theAtomicFence.type); assert(info.order === theAtomicFence.order); - theAtomicFence.order = 1; - assert(theAtomicFence.order === 1); + theAtomicFence.order = binaryen.MemoryOrder.acqrel; + assert(theAtomicFence.order === binaryen.MemoryOrder.acqrel); info = binaryen.getExpressionInfo(theAtomicFence); assert(info.type === theAtomicFence.type); @@ -1394,7 +1394,7 @@ console.log("# AtomicFence"); assert( theAtomicFence.toText() == - "(atomic.fence)\n" + "(atomic.fence acqrel)\n" ); module.dispose(); diff --git a/test/binaryen.js/expressions.js.txt b/test/binaryen.js/expressions.js.txt index ec0c0e518e6..853c8f7f5fc 100644 --- a/test/binaryen.js/expressions.js.txt +++ b/test/binaryen.js/expressions.js.txt @@ -161,7 +161,7 @@ ) # AtomicFence -(atomic.fence) +(atomic.fence acqrel) # SIMDExtract (i16x8.extract_lane_s 1 diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 4e76afd6970..3e2b010e3e6 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -650,7 +650,7 @@ function test_core() { module.i32.const(0) ) ), - module.atomic.fence(), + module.atomic.fence(binaryen.MemoryOrder.acqrel), // Tuples module.tuple.make( @@ -1266,11 +1266,16 @@ function test_relaxed_atomics() { binaryen.AtomicCmpxchg.setMemoryOrder(cmpxchg, binaryen.MemoryOrder.acqrel); console.log("Cmpxchg memory order: " + binaryen.AtomicCmpxchg.getMemoryOrder(cmpxchg)); + var fence = module.atomic.fence(binaryen.MemoryOrder.seqcst) + binaryen.AtomicFence.setOrder(fence, binaryen.MemoryOrder.acqrel) + console.log("Fence memory order: " + binaryen.AtomicFence.getOrder(fence)); + var body = module.block("body", [ module.drop(load), store, module.drop(rmw), - module.drop(cmpxchg) + module.drop(cmpxchg), + fence, ], binaryen.auto); module.addFunction("relaxed-atomics", binaryen.none, binaryen.none, [], body); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 59aabef6ea6..06984b93b4a 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -2145,7 +2145,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (i32.const 0) ) ) - (atomic.fence) + (atomic.fence acqrel) (tuple.drop 4 (tuple.make 4 (i32.const 13) @@ -2871,6 +2871,7 @@ Load memory order: 2 Store memory order: 2 RMW memory order: 2 Cmpxchg memory order: 2 +Fence memory order: 2 (module (type $0 (func)) (memory $0 1 1) @@ -2899,6 +2900,7 @@ Cmpxchg memory order: 2 (i32.const 1) ) ) + (atomic.fence acqrel) ) ) ) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index d749665aae1..04c69d453a4 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -1128,7 +1128,7 @@ void test_core() { BinaryenAtomicWait( module, temp6, temp6, temp16, BinaryenTypeInt32(), "0")), BinaryenDrop(module, BinaryenAtomicNotify(module, temp6, temp6, "0")), - BinaryenAtomicFence(module), + BinaryenAtomicFence(module, BinaryenMemoryOrderSeqCst()), // Tuples BinaryenTupleMake(module, tupleElements4a, 4), BinaryenTupleExtract( @@ -2365,10 +2365,16 @@ void test_relaxed_atomics() { printf("Cmpxchg memory order: %d\n", BinaryenAtomicCmpxchgGetMemoryOrder(cmpxchg)); + BinaryenExpressionRef fence = + BinaryenAtomicFence(module, BinaryenMemoryOrderSeqCst()); + BinaryenAtomicFenceSetOrder(fence, BinaryenMemoryOrderAcqRel()); + printf("Fence memory order: %d\n", BinaryenAtomicFenceGetOrder(fence)); + BinaryenExpressionRef statements[] = {BinaryenDrop(module, load), store, BinaryenDrop(module, rmw), - BinaryenDrop(module, cmpxchg)}; + BinaryenDrop(module, cmpxchg), + fence}; BinaryenExpressionRef value = BinaryenBlock(module, diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index ce938059f18..0addded551f 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -3101,6 +3101,7 @@ Load memory order: 2 Store memory order: 2 RMW memory order: 2 Cmpxchg memory order: 2 +Fence memory order: 2 (module (type $0 (func)) (memory $0 1 1) @@ -3129,6 +3130,7 @@ Cmpxchg memory order: 2 (i32.const 1) ) ) + (atomic.fence acqrel) ) ) ) diff --git a/test/example/match.cpp b/test/example/match.cpp index 125ce8e9296..5fdb1b459c6 100644 --- a/test/example/match.cpp +++ b/test/example/match.cpp @@ -61,7 +61,7 @@ void test_internal_any() { Expression* builtExpr = builder.makeNop(); Nop* builtNop = builder.makeNop(); - AtomicFence* builtFence = builder.makeAtomicFence(); + AtomicFence* builtFence = builder.makeAtomicFence(MemoryOrder::SeqCst); assert(Internal::Any(&expr).matches(builtExpr)); assert(expr == builtExpr); diff --git a/test/lit/basic/relaxed-atomics.wast b/test/lit/basic/relaxed-atomics.wast index 0c26d26398e..e726d64c6f7 100644 --- a/test/lit/basic/relaxed-atomics.wast +++ b/test/lit/basic/relaxed-atomics.wast @@ -6,6 +6,7 @@ (memory $0 23 256 shared) ;; RTRIP: (func $acqrel (type $0) + ;; RTRIP-NEXT: (atomic.fence acqrel) ;; RTRIP-NEXT: (i32.atomic.store acqrel ;; RTRIP-NEXT: (i32.const 1) ;; RTRIP-NEXT: (i32.const 1) @@ -17,6 +18,7 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $acqrel + (atomic.fence acqrel) (i32.atomic.store acqrel (i32.const 1) (i32.const 1)) (drop (i32.atomic.load acqrel @@ -26,6 +28,7 @@ ;; Optional seqcst ordering is dropped in text output. ;; RTRIP: (func $seqcst (type $0) + ;; RTRIP-NEXT: (atomic.fence) ;; RTRIP-NEXT: (i32.atomic.store ;; RTRIP-NEXT: (i32.const 1) ;; RTRIP-NEXT: (i32.const 1) @@ -46,6 +49,7 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $seqcst + (atomic.fence seqcst) (i32.atomic.store seqcst (i32.const 1) (i32.const 1)) (i32.atomic.store 0 seqcst (i32.const 1) (i32.const 1)) (drop (i32.atomic.load seqcst diff --git a/test/lit/validation/relaxed-atomics.wast b/test/lit/validation/relaxed-atomics.wast index 71731153b1f..6a23203d9f2 100644 --- a/test/lit/validation/relaxed-atomics.wast +++ b/test/lit/validation/relaxed-atomics.wast @@ -16,5 +16,6 @@ (i32.atomic.load acqrel (i32.const 1) ) + (atomic.fence) ) ) diff --git a/test/spec/relaxed-atomics.wast b/test/spec/relaxed-atomics.wast index a4ee0438dab..3c0d2cf914e 100644 --- a/test/spec/relaxed-atomics.wast +++ b/test/spec/relaxed-atomics.wast @@ -574,6 +574,9 @@ (drop (i64.atomic.rmw32.cmpxchg_u 1 (i64.const 0) (i64.const 42) (i64.const 42))) (drop (i64.atomic.rmw32.cmpxchg_u 1 acqrel (i64.const 0) (i64.const 42) (i64.const 42))) (drop (i64.atomic.rmw32.cmpxchg_u 1 seqcst (i64.const 0) (i64.const 42) (i64.const 42))) + (atomic.fence) + (atomic.fence acqrel) + (atomic.fence seqcst) ) ) @@ -591,8 +594,8 @@ "\05\07\02" ;; Memory section "\01\01\01" ;; (memory i32 1 1) "\05\01\01" ;; (memory i64 1 1) - "\0a\a9\2d\01" ;; Code section - "\a6\2d\00" ;; func $test-all-ops + "\0a\af\2d\01" ;; Code section + "\ac\2d\00" ;; func $test-all-ops "\41\00" ;; (i32.const 0) "\fe\10" ;; i32.atomic.load @@ -5256,6 +5259,12 @@ "\00" ;; offset "\1a" ;; drop + "\fe\03" ;; atomic.fence + "\01" ;; acqrel memory ordering + + "\fe\03" ;; atomic.fence + "\00" ;; seqcst memory ordering + "\0b" ;; end ) @@ -5338,6 +5347,7 @@ (func (export "i64.atomic.rmw16.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw16.cmpxchg_u acqrel (local.get $addr) (local.get $expected) (local.get $value))) (func (export "i64.atomic.rmw32.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw32.cmpxchg_u acqrel (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "atomic.fence") (atomic.fence acqrel)) ) ;; *.atomic.load* @@ -5649,6 +5659,10 @@ (assert_return (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 0) (i64.const 0x11111111) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) (assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111cabba6e5)) +;; atomic.fence + +(invoke "atomic.fence") + ;; unaligned accesses From c280e997a1b4a5f2769746c4df39a8d65e11bce1 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 18 Jun 2026 17:55:44 +0000 Subject: [PATCH 2/2] Small fix --- CHANGELOG.md | 4 ++-- src/binaryen-c.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f077802c417..9c126b32f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ Current Trunk ------------- - Add relaxed atomics support for atomic fences (#8845). Breaks the C and JS - apis; the BinaryenAtomicFence / module.atomic.fence now take a memory order - param. Use BinaryenMemoryOrderSeqCst() / binaryen.MemoryOrder.seqcst to + APIs; BinaryenAtomicFence / module.atomic.fence now take a memory order param. + Use BinaryenMemoryOrderSeqCst() / binaryen.MemoryOrder.seqcst to preserve the original behavior. v130 diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index d098d71c04e..4f605f54fd6 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1502,8 +1502,8 @@ BinaryenExpressionRef BinaryenAtomicNotify(BinaryenModuleRef module, } BinaryenExpressionRef BinaryenAtomicFence(BinaryenModuleRef module, BinaryenMemoryOrder order) { - return static_cast( - Builder(*(Module*)module).makeAtomicFence(static_cast(order))); + return Builder(*(Module*)module) + .makeAtomicFence(static_cast(order)); } BinaryenExpressionRef BinaryenSIMDExtract(BinaryenModuleRef module, BinaryenOp op,