Skip to content

Commit 3537229

Browse files
committed
implement atomic.fence, add wait/notify edge case tests
1 parent 7fb4187 commit 3537229

File tree

10 files changed

+82
-2
lines changed

10 files changed

+82
-2
lines changed

interpreter/binary/decode.ml

+1
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ let rec instr s =
466466
| 0x00 -> let a, o = memop s in memory_atomic_notify a o
467467
| 0x01 -> let a, o = memop s in memory_atomic_wait32 a o
468468
| 0x02 -> let a, o = memop s in memory_atomic_wait64 a o
469+
| 0x03 -> expect 0x00 s "zero flag expected"; atomic_fence
469470

470471
| 0x10 -> let a, o = memop s in i32_atomic_load a o
471472
| 0x11 -> let a, o = memop s in i64_atomic_load a o

interpreter/binary/encode.ml

+3
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ let encode m =
221221
assert false
222222
| MemoryAtomicWait {ty = F32Type | F64Type; _} -> assert false
223223

224+
| AtomicFence ->
225+
op 0xfe; op 0x03; op 0x00
226+
224227
| AtomicLoad ({ty = I32Type; sz = None; _} as mo) ->
225228
op 0xfe; op 0x10; memop mo
226229
| AtomicLoad ({ty = I64Type; sz = None; _} as mo) ->

interpreter/exec/eval.ml

+15-1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ let check_align addr ty sz at =
132132
if not (Memory.is_aligned addr ty sz) then
133133
Trap.error at "unaligned atomic memory access"
134134

135+
let check_shared mem at =
136+
if (shared_memory_type (Memory.type_of mem)) != Shared then
137+
Trap.error at "expected shared memory"
138+
135139

136140
(* Evaluation *)
137141

@@ -319,18 +323,28 @@ let rec step_thread (t : thread) : thread =
319323
(try
320324
assert (sz = None);
321325
check_align addr ty sz e.at;
326+
check_shared mem e.at;
322327
let v = Memory.load_value mem addr offset ty in
323328
if v = ve then
324329
assert false (* TODO *)
325330
else
326331
I32 1l :: vs', [] (* Not equal *)
327332
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
328333

329-
| MemoryAtomicNotify x, I32 count :: I32 i :: vs' ->
334+
| MemoryAtomicNotify {offset; ty; sz; _}, I32 count :: I32 i :: vs' ->
335+
let mem = memory frame.inst (0l @@ e.at) in
336+
let addr = I64_convert.extend_i32_u i in
337+
(try
338+
check_align addr ty sz e.at;
339+
let _ = Memory.load_value mem addr offset ty in
330340
if count = 0l then
331341
I32 0l :: vs', [] (* Trivial case waking 0 waiters *)
332342
else
333343
assert false (* TODO *)
344+
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
345+
346+
| AtomicFence, vs ->
347+
vs, []
334348

335349
| MemorySize, vs ->
336350
let mem = memory frame.inst (0l @@ e.at) in

interpreter/syntax/ast.ml

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ and instr' =
106106
| Convert of cvtop (* conversion *)
107107
| MemoryAtomicWait of atomicop (* atomically wait for notification at address *)
108108
| MemoryAtomicNotify of atomicop (* atomically notify all waiters at address *)
109+
| AtomicFence (* perform an atomic fence *)
109110
| AtomicLoad of atomicop (* atomically read memory at address *)
110111
| AtomicStore of atomicop (* atomically write memory at address *)
111112
| AtomicRmw of rmwop * atomicop (* atomically read, modify, write memory at address *)

interpreter/syntax/operators.ml

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ let memory_atomic_wait32 align offset =
7878
let memory_atomic_wait64 align offset =
7979
MemoryAtomicWait {ty = I64Type; align; offset; sz = None}
8080

81+
let atomic_fence =
82+
AtomicFence
83+
8184
let i32_atomic_load align offset =
8285
AtomicLoad {ty = I32Type; align; offset; sz = None}
8386
let i64_atomic_load align offset =

interpreter/text/arrange.ml

+1
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ let rec instr e =
311311
| Convert op -> cvtop op, []
312312
| MemoryAtomicWait op -> memoryatomicwaitop op, []
313313
| MemoryAtomicNotify op -> memoryatomicnotifyop op, []
314+
| AtomicFence -> "atomic.fence", []
314315
| AtomicLoad op -> atomicloadop op, []
315316
| AtomicStore op -> atomicstoreop op, []
316317
| AtomicRmw (rmwop, op) -> atomicrmwop op rmwop, []

interpreter/text/lexer.mll

+1
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ rule token = parse
259259
intop ("i" ^ sz)
260260
(memory_atomic_wait32 (opt a 2))
261261
(memory_atomic_wait64 (opt a 3)) o) }
262+
| "atomic.fence" { ATOMIC_FENCE }
262263
| (ixx as t)".atomic.load"
263264
{ ATOMIC_LOAD (fun a o ->
264265
intop t (i32_atomic_load (opt a 2)) (i64_atomic_load (opt a 3)) o) }

interpreter/text/parser.mly

+2-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ let inline_type_explicit (c : context) x ft at =
169169
%token CALL CALL_INDIRECT RETURN
170170
%token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET
171171
%token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT
172-
%token MEMORY_ATOMIC_WAIT MEMORY_ATOMIC_NOTIFY
172+
%token MEMORY_ATOMIC_WAIT MEMORY_ATOMIC_NOTIFY ATOMIC_FENCE
173173
%token ATOMIC_LOAD ATOMIC_STORE ATOMIC_RMW ATOMIC_RMW_CMPXCHG
174174
%token CONST UNARY BINARY TEST COMPARE CONVERT
175175
%token UNREACHABLE MEMORY_SIZE MEMORY_GROW
@@ -358,6 +358,7 @@ plain_instr :
358358
| CONVERT { fun c -> $1 }
359359
| MEMORY_ATOMIC_WAIT offset_opt align_opt { fun c -> $1 $3 $2 }
360360
| MEMORY_ATOMIC_NOTIFY offset_opt align_opt { fun c -> $1 $3 $2 }
361+
| ATOMIC_FENCE { fun c -> atomic_fence }
361362
| ATOMIC_LOAD offset_opt align_opt { fun c -> $1 $3 $2 }
362363
| ATOMIC_STORE offset_opt align_opt { fun c -> $1 $3 $2 }
363364
| ATOMIC_RMW offset_opt align_opt { fun c -> $1 $3 $2 }

interpreter/valid/valid.ml

+3
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type =
308308
check_memop c memop (fun sz -> sz) e.at;
309309
[I32Type; memop.ty; I64Type] --> [I32Type]
310310

311+
| AtomicFence ->
312+
[] --> []
313+
311314
| AtomicLoad memop ->
312315
check_memop c memop (fun sz -> sz) e.at;
313316
[I32Type] --> [memop.ty]

test/core/atomic.wast

+52
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@
417417
(assert_trap (invoke "i64.atomic.rmw16.cmpxchg_u" (i32.const 1) (i64.const 0) (i64.const 0)) "unaligned atomic")
418418
(assert_trap (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 1) (i64.const 0) (i64.const 0)) "unaligned atomic")
419419

420+
;; wait/notify
420421
(module
421422
(memory 1 1 shared)
422423

@@ -431,10 +432,54 @@
431432
)
432433

433434
(invoke "init" (i64.const 0xffffffffffff))
435+
436+
;; wait returns immediately if values do not match
434437
(assert_return (invoke "memory.atomic.wait32" (i32.const 0) (i32.const 0) (i64.const 0)) (i32.const 1))
435438
(assert_return (invoke "memory.atomic.wait64" (i32.const 0) (i64.const 0) (i64.const 0)) (i32.const 1))
439+
440+
;; notify always returns
441+
(assert_return (invoke "memory.atomic.notify" (i32.const 0) (i32.const 0)) (i32.const 0))
442+
443+
;; OOB wait and notify always trap
444+
(assert_trap (invoke "memory.atomic.wait32" (i32.const 65536) (i32.const 0) (i64.const 0)) "out of bounds memory access")
445+
(assert_trap (invoke "memory.atomic.wait64" (i32.const 65536) (i64.const 0) (i64.const 0)) "out of bounds memory access")
446+
447+
;; in particular, notify always traps even if waking 0 threads
448+
(assert_trap (invoke "memory.atomic.notify" (i32.const 65536) (i32.const 0)) "out of bounds memory access")
449+
450+
;; similarly, unaligned wait and notify always trap
451+
(assert_trap (invoke "memory.atomic.wait32" (i32.const 65531) (i32.const 0) (i64.const 0)) "unaligned atomic")
452+
(assert_trap (invoke "memory.atomic.wait64" (i32.const 65524) (i64.const 0) (i64.const 0)) "unaligned atomic")
453+
454+
(assert_trap (invoke "memory.atomic.notify" (i32.const 65531) (i32.const 0)) "unaligned atomic")
455+
456+
;; atomic.wait traps on unshared memory even if it wouldn't block
457+
(module
458+
(memory 1 1)
459+
460+
(func (export "init") (param $value i64) (i64.store (i32.const 0) (local.get $value)))
461+
462+
(func (export "memory.atomic.notify") (param $addr i32) (param $count i32) (result i32)
463+
(memory.atomic.notify (local.get 0) (local.get 1)))
464+
(func (export "memory.atomic.wait32") (param $addr i32) (param $expected i32) (param $timeout i64) (result i32)
465+
(memory.atomic.wait32 (local.get 0) (local.get 1) (local.get 2)))
466+
(func (export "memory.atomic.wait64") (param $addr i32) (param $expected i64) (param $timeout i64) (result i32)
467+
(memory.atomic.wait64 (local.get 0) (local.get 1) (local.get 2)))
468+
)
469+
470+
(invoke "init" (i64.const 0xffffffffffff))
471+
472+
(assert_trap (invoke "memory.atomic.wait32" (i32.const 0) (i32.const 0) (i64.const 0)) "expected shared memory")
473+
(assert_trap (invoke "memory.atomic.wait64" (i32.const 0) (i64.const 0) (i64.const 0)) "expected shared memory")
474+
475+
;; notify still works
436476
(assert_return (invoke "memory.atomic.notify" (i32.const 0) (i32.const 0)) (i32.const 0))
437477

478+
;; OOB and unaligned notify still trap
479+
(assert_trap (invoke "memory.atomic.notify" (i32.const 65536) (i32.const 0)) "out of bounds memory access")
480+
(assert_trap (invoke "memory.atomic.notify" (i32.const 65531) (i32.const 0)) "unaligned atomic")
481+
482+
438483
;; unshared memory is OK
439484
(module
440485
(memory 1 1)
@@ -488,6 +533,13 @@
488533
(func (drop (i64.atomic.rmw32.cmpxchg_u (i32.const 0) (i64.const 0) (i64.const 0))))
489534
)
490535

536+
;; atomic.fence: no memory is ok
537+
(module
538+
(func (export "fence") (atomic.fence))
539+
)
540+
541+
(assert_return (invoke "fence"))
542+
491543
;; Fails with no memory
492544
(assert_invalid (module (func (drop (memory.atomic.notify (i32.const 0) (i32.const 0))))) "unknown memory")
493545
(assert_invalid (module (func (drop (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const 0))))) "unknown memory")

0 commit comments

Comments
 (0)