Skip to content

Commit 281c4c9

Browse files
authored
Back atomic instructions by C11 stdatomic.h (#308)
This is a prerequisite for a pthread-backed https://github.com/WebAssembly/wasi-threads implementation.
1 parent ec9c866 commit 281c4c9

9 files changed

Lines changed: 183 additions & 137 deletions

File tree

Sources/WasmKit/Execution/DispatchInstruction.swift

Lines changed: 57 additions & 49 deletions
Large diffs are not rendered by default.

Sources/WasmKit/Execution/Instructions/Instruction.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,8 @@ enum Instruction: Equatable {
555555
case memoryAtomicWait64(Instruction.AtomicWaitOperand)
556556
/// WebAssembly Core Instruction `memory.atomic.notify`
557557
case memoryAtomicNotify(Instruction.AtomicNotifyOperand)
558+
/// WebAssembly Core Instruction `atomic.fence`
559+
case atomicFence
558560
}
559561

560562
extension Instruction {
@@ -1690,6 +1692,7 @@ extension Instruction {
16901692
case .memoryAtomicWait32: return 266
16911693
case .memoryAtomicWait64: return 267
16921694
case .memoryAtomicNotify: return 268
1695+
case .atomicFence: return 269
16931696
}
16941697
}
16951698
}
@@ -1970,6 +1973,7 @@ extension Instruction {
19701973
case 266: return .memoryAtomicWait32(Instruction.AtomicWaitOperand.load(from: &pc))
19711974
case 267: return .memoryAtomicWait64(Instruction.AtomicWaitOperand.load(from: &pc))
19721975
case 268: return .memoryAtomicNotify(Instruction.AtomicNotifyOperand.load(from: &pc))
1976+
case 269: return .atomicFence
19731977
default: fatalError("Unknown instruction opcode: \(opcode)")
19741978
}
19751979
}
@@ -2253,6 +2257,7 @@ extension Instruction {
22532257
case 266: return "memoryAtomicWait32"
22542258
case 267: return "memoryAtomicWait64"
22552259
case 268: return "memoryAtomicNotify"
2260+
case 269: return "atomicFence"
22562261
default: fatalError("Unknown instruction index: \(opcode)")
22572262
}
22582263
}

Sources/WasmKit/Execution/Instructions/Memory.swift

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import WasmParser
12
/// > Note:
23
/// <https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions>
3-
import WasmParser
4+
import _CWasmKit
45

56
extension Execution {
67
@inline(never) func throwOutOfBoundsMemoryAccess() throws -> Never {
@@ -118,11 +119,16 @@ extension Execution {
118119
}
119120
let (endAddress, isEndOverflow) = i.addingReportingOverflow(length &+ loadOperand.offset)
120121
if _fastPath(!isEndOverflow && endAddress <= ms) {
121-
let ptr = md.unsafelyUnwrapped.advanced(by: Int(address))
122-
.bindMemory(to: T.self, capacity: 1)
123-
// Use atomic load with acquire ordering (sequentially consistent)
124-
let loaded = ptr.pointee
125-
sp[loadOperand.result] = castToValue(loaded.littleEndian)
122+
let rawPtr = md.unsafelyUnwrapped.advanced(by: Int(address))
123+
let loaded: T
124+
switch T.bitWidth {
125+
case 8: loaded = T(wasmkit_atomic_load_8(rawPtr))
126+
case 16: loaded = T(wasmkit_atomic_load_16(rawPtr))
127+
case 32: loaded = T(wasmkit_atomic_load_32(rawPtr))
128+
case 64: loaded = T(wasmkit_atomic_load_64(rawPtr))
129+
default: fatalError()
130+
}
131+
sp[loadOperand.result] = castToValue(loaded)
126132
} else {
127133
try throwOutOfBoundsMemoryAccess()
128134
}
@@ -143,10 +149,14 @@ extension Execution {
143149
let (endAddress, isEndOverflow) = i.addingReportingOverflow(length &+ storeOperand.offset)
144150
if _fastPath(!isEndOverflow && endAddress <= ms) {
145151
let toStore = castFromValue(value)
146-
let ptr = md.unsafelyUnwrapped.advanced(by: Int(address))
147-
.bindMemory(to: T.self, capacity: 1)
148-
// Atomic store
149-
ptr.pointee = toStore.littleEndian
152+
let rawPtr = md.unsafelyUnwrapped.advanced(by: Int(address))
153+
switch T.bitWidth {
154+
case 8: wasmkit_atomic_store_8(rawPtr, UInt8(truncatingIfNeeded: toStore))
155+
case 16: wasmkit_atomic_store_16(rawPtr, UInt16(truncatingIfNeeded: toStore))
156+
case 32: wasmkit_atomic_store_32(rawPtr, UInt32(truncatingIfNeeded: toStore))
157+
case 64: wasmkit_atomic_store_64(rawPtr, UInt64(truncatingIfNeeded: toStore))
158+
default: fatalError()
159+
}
150160
} else {
151161
try throwOutOfBoundsMemoryAccess()
152162
}
@@ -158,7 +168,7 @@ extension Execution {
158168
mutating func atomicRmw<T: FixedWidthInteger>(
159169
sp: Sp, md: Md, ms: Ms, rmwOperand: Instruction.RmwOperand,
160170
loadAs _: T.Type = T.self,
161-
operation: (T, T) -> T,
171+
atomicOp: (UnsafeMutableRawPointer, T) -> T,
162172
castFromValue: (UntypedValue) -> T,
163173
castToValue: (T) -> UntypedValue
164174
) throws {
@@ -171,13 +181,9 @@ extension Execution {
171181
}
172182
let (endAddress, isEndOverflow) = i.addingReportingOverflow(length &+ rmwOperand.offset)
173183
if _fastPath(!isEndOverflow && endAddress <= ms) {
174-
let ptr = md.unsafelyUnwrapped.advanced(by: Int(address))
175-
.bindMemory(to: T.self, capacity: 1)
184+
let rawPtr = md.unsafelyUnwrapped.advanced(by: Int(address))
176185
let value = castFromValue(sp[rmwOperand.value])
177-
// Atomic read-modify-write
178-
let oldValue = ptr.pointee.littleEndian
179-
let newValue = operation(oldValue, value)
180-
ptr.pointee = newValue.littleEndian
186+
let oldValue = atomicOp(rawPtr, value)
181187
sp[rmwOperand.result] = castToValue(oldValue)
182188
} else {
183189
try throwOutOfBoundsMemoryAccess()
@@ -188,6 +194,7 @@ extension Execution {
188194
mutating func atomicCmpxchg<T: FixedWidthInteger>(
189195
sp: Sp, md: Md, ms: Ms, cmpxchgOperand: Instruction.CmpxchgOperand,
190196
loadAs _: T.Type = T.self,
197+
atomicCmpxchg: (UnsafeMutableRawPointer, T, T) -> T,
191198
castFromValue: (UntypedValue) -> T,
192199
castToValue: (T) -> UntypedValue
193200
) throws {
@@ -200,15 +207,11 @@ extension Execution {
200207
}
201208
let (endAddress, isEndOverflow) = i.addingReportingOverflow(length &+ cmpxchgOperand.offset)
202209
if _fastPath(!isEndOverflow && endAddress <= ms) {
203-
let ptr = md.unsafelyUnwrapped.advanced(by: Int(address))
204-
.bindMemory(to: T.self, capacity: 1)
210+
let rawPtr = md.unsafelyUnwrapped.advanced(by: Int(address))
205211
let expectedValue = castFromValue(sp[cmpxchgOperand.expected])
206212
let replacementValue = castFromValue(sp[cmpxchgOperand.replacement])
207-
let currentValue = ptr.pointee.littleEndian
208-
if currentValue == expectedValue {
209-
ptr.pointee = replacementValue.littleEndian
210-
}
211-
sp[cmpxchgOperand.result] = castToValue(currentValue)
213+
let resultValue = atomicCmpxchg(rawPtr, expectedValue, replacementValue)
214+
sp[cmpxchgOperand.result] = castToValue(resultValue)
212215
} else {
213216
try throwOutOfBoundsMemoryAccess()
214217
}
@@ -226,9 +229,8 @@ extension Execution {
226229
}
227230
let (endAddress, isEndOverflow) = i.addingReportingOverflow(4 &+ waitOperand.offset)
228231
if _fastPath(!isEndOverflow && endAddress <= ms) {
229-
let ptr = md.unsafelyUnwrapped.advanced(by: Int(address))
230-
.bindMemory(to: UInt32.self, capacity: 1)
231-
let currentValue = ptr.pointee.littleEndian
232+
let rawPtr = md.unsafelyUnwrapped.advanced(by: Int(address))
233+
let currentValue = wasmkit_atomic_load_32(rawPtr)
232234
let expectedValue = sp[waitOperand.expected].i32
233235
let timeout = sp[waitOperand.timeout].i64
234236

@@ -257,7 +259,7 @@ extension Execution {
257259
address: UInt64(address),
258260
validate: {
259261
// Re-check the value atomically
260-
let currentValue = ptr.pointee.littleEndian
262+
let currentValue = wasmkit_atomic_load_32(rawPtr)
261263
return currentValue == expectedValue
262264
},
263265
deadline: deadline,
@@ -286,9 +288,8 @@ extension Execution {
286288
}
287289
let (endAddress, isEndOverflow) = i.addingReportingOverflow(8 &+ waitOperand.offset)
288290
if _fastPath(!isEndOverflow && endAddress <= ms) {
289-
let ptr = md.unsafelyUnwrapped.advanced(by: Int(address))
290-
.bindMemory(to: UInt64.self, capacity: 1)
291-
let currentValue = ptr.pointee.littleEndian
291+
let rawPtr = md.unsafelyUnwrapped.advanced(by: Int(address))
292+
let currentValue = wasmkit_atomic_load_64(rawPtr)
292293
let expectedValue = sp[waitOperand.expected].i64
293294
let timeout = sp[waitOperand.timeout].i64
294295

@@ -317,7 +318,7 @@ extension Execution {
317318
address: UInt64(address),
318319
validate: {
319320
// Re-check the value atomically
320-
let currentValue = ptr.pointee.littleEndian
321+
let currentValue = wasmkit_atomic_load_64(rawPtr)
321322
return currentValue == expectedValue
322323
},
323324
deadline: deadline,
@@ -350,4 +351,9 @@ extension Execution {
350351
try throwOutOfBoundsMemoryAccess()
351352
}
352353
}
354+
355+
/// Atomic fence - sequential consistency barrier
356+
mutating func atomicFence(sp: Sp) {
357+
wasmkit_atomic_fence()
358+
}
353359
}

Sources/WasmKit/Translator.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,10 +3132,7 @@ struct InstructionTranslator: InstructionVisitor {
31323132
}
31333133

31343134
mutating func visitAtomicFence() throws -> Output {
3135-
// No-op: In an interpreter, instructions are executed sequentially,
3136-
// so no reordering can occur. atomic.fence is only meaningful for
3137-
// compiled code with actual CPU-level reordering.
3138-
// Do not emit any instruction.
3135+
emit(.atomicFence)
31393136
}
31403137
}
31413138

Sources/_CWasmKit/include/DirectThreadedCode.inc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,6 +1750,12 @@ SWIFT_CC(swiftasync) static inline void wasmkit_tc_memoryAtomicNotify(Sp sp, Pc
17501750
if (error) return wasmkit_execution_state_set_error(error, sp, state);
17511751
return ((wasmkit_tc_exec)next)(sp, pc, md, ms, state);
17521752
}
1753+
SWIFT_CC(swiftasync) static inline void wasmkit_tc_atomicFence(Sp sp, Pc pc, Md md, Ms ms, SWIFT_CONTEXT void *state) {
1754+
SWIFT_CC(swift) uint64_t wasmkit_execute_atomicFence(Sp *sp, Pc *pc, Md *md, Ms *ms, SWIFT_CONTEXT void *state, SWIFT_ERROR_RESULT void **error);
1755+
void * _Nullable error = NULL; uint64_t next;
1756+
INLINE_CALL next = wasmkit_execute_atomicFence(&sp, &pc, &md, &ms, state, &error);
1757+
return ((wasmkit_tc_exec)next)(sp, pc, md, ms, state);
1758+
}
17531759
static const uintptr_t wasmkit_tc_exec_handlers[] = {
17541760
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_copyStack),
17551761
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_globalGet),
@@ -2020,4 +2026,5 @@ static const uintptr_t wasmkit_tc_exec_handlers[] = {
20202026
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_memoryAtomicWait32),
20212027
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_memoryAtomicWait64),
20222028
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_memoryAtomicNotify),
2029+
(uintptr_t)((wasmkit_tc_exec)&wasmkit_tc_atomicFence),
20232030
};

Sources/_CWasmKit/include/_CWasmKit.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,55 @@
99

1010
#include "Platform.h"
1111

12+
// MARK: - Hardware Atomic Operations for Wasm Shared Memory
13+
14+
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
15+
#error "WasmKit atomic operations only support little-endian platforms"
16+
#endif
17+
18+
#define WASMKIT_DEFINE_ATOMICS(WIDTH, CTYPE) \
19+
static inline CTYPE wasmkit_atomic_load_##WIDTH(const void *_Nonnull ptr) { \
20+
return __atomic_load_n((const CTYPE *)ptr, __ATOMIC_SEQ_CST); \
21+
} \
22+
static inline void wasmkit_atomic_store_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
23+
__atomic_store_n((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
24+
} \
25+
static inline CTYPE wasmkit_atomic_rmw_add_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
26+
return __atomic_fetch_add((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
27+
} \
28+
static inline CTYPE wasmkit_atomic_rmw_sub_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
29+
return __atomic_fetch_sub((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
30+
} \
31+
static inline CTYPE wasmkit_atomic_rmw_and_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
32+
return __atomic_fetch_and((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
33+
} \
34+
static inline CTYPE wasmkit_atomic_rmw_or_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
35+
return __atomic_fetch_or((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
36+
} \
37+
static inline CTYPE wasmkit_atomic_rmw_xor_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
38+
return __atomic_fetch_xor((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
39+
} \
40+
static inline CTYPE wasmkit_atomic_rmw_xchg_##WIDTH(void *_Nonnull ptr, CTYPE val) { \
41+
return __atomic_exchange_n((CTYPE *)ptr, val, __ATOMIC_SEQ_CST); \
42+
} \
43+
static inline _Bool wasmkit_atomic_cmpxchg_##WIDTH( \
44+
void *_Nonnull ptr, CTYPE *_Nonnull expected, CTYPE desired \
45+
) { \
46+
return __atomic_compare_exchange_n( \
47+
(CTYPE *)ptr, expected, desired, 0, \
48+
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \
49+
}
50+
51+
WASMKIT_DEFINE_ATOMICS(8, uint8_t)
52+
WASMKIT_DEFINE_ATOMICS(16, uint16_t)
53+
WASMKIT_DEFINE_ATOMICS(32, uint32_t)
54+
WASMKIT_DEFINE_ATOMICS(64, uint64_t)
55+
#undef WASMKIT_DEFINE_ATOMICS
56+
57+
static inline void wasmkit_atomic_fence(void) {
58+
__atomic_thread_fence(__ATOMIC_SEQ_CST);
59+
}
60+
1261
// MARK: - Execution Parameters
1362
// See ExecutionContext.swift for more information about each execution
1463
// parameter.

0 commit comments

Comments
 (0)