/src/wasm3/source/m3_exec_atomic.h
Line | Count | Source |
1 | | // |
2 | | // m3_exec_atomic.h |
3 | | // |
4 | | // The atomic instructions of the threads proposal, included from m3_exec.h. |
5 | | // |
6 | | // Operands follow Compile_Atomic: the topmost is in _r0, and the rest are read |
7 | | // from slots in the order the stack gives them, topmost first, with the address |
8 | | // always the last. The access itself is an immediate after them. A load has only |
9 | | // the address, so it is in _r0. |
10 | | // |
11 | | // The trap order is the spec's: an address that is not aligned traps before one |
12 | | // that is out of bounds, and both before anything is read or written. |
13 | | // |
14 | | |
15 | | #if d_m3HasAtomics |
16 | | |
17 | | // WIDTH is the size of the cell in bytes, and EA a u64 already holding the effective |
18 | | // address. An access leaves the bounds to m3MemCheck, which a guarded memory answers |
19 | | // with the fault its reservation raises; notify and wait may never touch the cell, so |
20 | | // they always check. |
21 | | # define d_m3AtomicCheckWith(IN_BOUNDS, WIDTH, EA) \ |
22 | 1.53M | if (M3_UNLIKELY((EA) & ((WIDTH) - 1))) { \ |
23 | 22 | newTrap(m3Err_trapUnalignedAtomic); \ |
24 | 22 | } \ |
25 | 1.53M | if (not IN_BOUNDS((EA) + (WIDTH) <= _mem->length)) { \ |
26 | 15 | d_outOfBoundsMemOp((size_t)(EA), (u32)(WIDTH)); \ |
27 | 15 | } |
28 | | |
29 | 1.53M | # define d_m3AtomicCheck(WIDTH, EA) d_m3AtomicCheckWith(m3MemCheck, WIDTH, EA) |
30 | 4.88k | # define d_m3AtomicCheckAlways(WIDTH, EA) d_m3AtomicCheckWith(M3_LIKELY, WIDTH, EA) |
31 | | |
32 | 109k | # define d_m3AtomicCell(EA) (m3MemData(_mem) + (EA)) |
33 | | |
34 | | |
35 | | // One op per kind of access. The last immediate describes the instruction: |
36 | | // |
37 | | // bits 0-1 log2 of the width of the cell |
38 | | // bit 2 the value type is i64 rather than i32 |
39 | | // bits 3-5 for a read-modify-write, which one (c_atomicAdd and the rest) |
40 | 1.53M | # define d_m3AtomicLog2Width(DESC) ((DESC) & 3u) |
41 | 1.79M | # define d_m3AtomicIs64(DESC) (((DESC) >> 2) & 1u) |
42 | | # define d_m3AtomicKind(DESC) (((DESC) >> 3) & 7u) |
43 | | |
44 | | // an i32 result is kept sign-extended in the register, as an i32 load leaves it |
45 | 1.42M | # define d_m3AtomicResult(DESC, V) (d_m3AtomicIs64(DESC) ? (m3reg_t)(V) : (m3reg_t)(i32)(V)) |
46 | | |
47 | | d_m3Op(AtomicLoad) |
48 | 268k | { |
49 | 268k | u64 ea = (u64)(u32)_r0; |
50 | 268k | ea += immediate(u32); |
51 | 268k | u32 desc = immediate(u32); |
52 | 268k | u32 lw = d_m3AtomicLog2Width(desc); |
53 | 268k | d_m3AtomicCheck((u64)1 << lw, ea); |
54 | | |
55 | 268k | _r0 = d_m3AtomicResult(desc, m3_AtomicLoadW(d_m3AtomicCell(ea), lw)); |
56 | 268k | nextOp(); |
57 | 268k | } |
58 | | |
59 | | d_m3Op(AtomicStore) |
60 | 106k | { |
61 | 106k | u64 ea = slot(u32); |
62 | 106k | ea += immediate(u32); |
63 | 106k | u32 desc = immediate(u32); |
64 | 106k | u32 lw = d_m3AtomicLog2Width(desc); |
65 | 106k | d_m3AtomicCheck((u64)1 << lw, ea); |
66 | | |
67 | 106k | m3_AtomicStoreW(d_m3AtomicCell(ea), lw, (u64)_r0); |
68 | 106k | nextOp(); |
69 | 106k | } |
70 | | |
71 | | d_m3Op(AtomicRmw) |
72 | 784k | { |
73 | 784k | u64 ea = slot(u32); |
74 | 784k | ea += immediate(u32); |
75 | 784k | u32 desc = immediate(u32); |
76 | 784k | u32 lw = d_m3AtomicLog2Width(desc); |
77 | 784k | d_m3AtomicCheck((u64)1 << lw, ea); |
78 | | |
79 | 784k | _r0 = d_m3AtomicResult(desc, m3_AtomicRmwW(d_m3AtomicCell(ea), lw, d_m3AtomicKind(desc), (u64)_r0)); |
80 | 784k | nextOp(); |
81 | 784k | } |
82 | | |
83 | | // the value compared against is cut to the width of the cell. Its slot comes before |
84 | | // the description that says how wide it is, so it is taken by number first. |
85 | | d_m3Op(AtomicCmpxchg) |
86 | 370k | { |
87 | 370k | i32 expectedSlot = immediate(i32); |
88 | 370k | u64 ea = slot(u32); |
89 | 370k | ea += immediate(u32); |
90 | 370k | u32 desc = immediate(u32); |
91 | 370k | u32 lw = d_m3AtomicLog2Width(desc); |
92 | 370k | d_m3AtomicCheck((u64)1 << lw, ea); |
93 | | |
94 | 370k | u64 expected = d_m3AtomicIs64(desc) ? *(u64*)(_sp + expectedSlot) : (u64) * (u32*)(_sp + expectedSlot); |
95 | | |
96 | 370k | _r0 = d_m3AtomicResult(desc, m3_AtomicCmpxchgW(d_m3AtomicCell(ea), lw, expected, (u64)_r0)); |
97 | 370k | nextOp(); |
98 | 370k | } |
99 | | |
100 | | |
101 | | // memory.atomic.notify: nothing waits on a memory only one thread can reach |
102 | | d_m3Op(AtomicNotify) |
103 | 2.03k | { |
104 | 2.03k | u64 ea = slot(u32); |
105 | 2.03k | ea += immediate(u32); |
106 | 2.03k | d_m3AtomicCheckAlways(sizeof(u32), ea); |
107 | | |
108 | 2.02k | _r0 = 0; |
109 | 2.02k | nextOp(); |
110 | 2.03k | } |
111 | | |
112 | | |
113 | | // The wait itself, for a cell of type T. The result is 1 when the cell did not hold |
114 | | // the expected value, and 2 when the timeout ran out. A negative timeout is |
115 | | // infinite, which with one thread is a wait that nothing can end. |
116 | | # define d_m3AtomicWaitOp(NAME, T) \ |
117 | | d_m3Op(NAME) \ |
118 | 2.85k | { \ |
119 | 2.85k | i64 timeout = (i64)_r0; \ |
120 | 2.85k | T expected = (T)slot(T); \ |
121 | 2.85k | u64 ea = slot(u32); \ |
122 | 2.85k | ea += immediate(u32); \ |
123 | 2.85k | d_m3AtomicCheckAlways(sizeof(T), ea); \ |
124 | 2.84k | \ |
125 | 2.84k | if (M3_UNLIKELY(not m3MemInfo(_mem)->isShared)) { \ |
126 | 2 | newTrap(m3Err_trapExpectedSharedMemory); \ |
127 | 2 | } \ |
128 | 2.84k | \ |
129 | 2.84k | if (m3_AtomicLoad_##T(d_m3AtomicCell(ea)) != expected) { \ |
130 | 2.08k | _r0 = 1; \ |
131 | 2.08k | nextOp(); \ |
132 | 2.08k | } \ |
133 | 2.84k | \ |
134 | 2.84k | if (timeout < 0) { \ |
135 | 16 | newTrap(m3Err_trapWaitForever); \ |
136 | 16 | } \ |
137 | 756 | \ |
138 | 756 | m3_HostSleepNs((u64)timeout); \ |
139 | 740 | _r0 = 2; \ |
140 | 740 | nextOp(); \ |
141 | 756 | } m3_compile.c:op_AtomicWait32 Line | Count | Source | 118 | 4 | { \ | 119 | 4 | i64 timeout = (i64)_r0; \ | 120 | 4 | T expected = (T)slot(T); \ | 121 | 4 | u64 ea = slot(u32); \ | 122 | 4 | ea += immediate(u32); \ | 123 | 4 | d_m3AtomicCheckAlways(sizeof(T), ea); \ | 124 | 1 | \ | 125 | 1 | if (M3_UNLIKELY(not m3MemInfo(_mem)->isShared)) { \ | 126 | 1 | newTrap(m3Err_trapExpectedSharedMemory); \ | 127 | 1 | } \ | 128 | 1 | \ | 129 | 1 | if (m3_AtomicLoad_##T(d_m3AtomicCell(ea)) != expected) { \ | 130 | 0 | _r0 = 1; \ | 131 | 0 | nextOp(); \ | 132 | 0 | } \ | 133 | 0 | \ | 134 | 0 | if (timeout < 0) { \ | 135 | 0 | newTrap(m3Err_trapWaitForever); \ | 136 | 0 | } \ | 137 | 0 | \ | 138 | 0 | m3_HostSleepNs((u64)timeout); \ | 139 | 0 | _r0 = 2; \ | 140 | 0 | nextOp(); \ | 141 | 0 | } |
m3_compile.c:op_AtomicWait64 Line | Count | Source | 118 | 2.84k | { \ | 119 | 2.84k | i64 timeout = (i64)_r0; \ | 120 | 2.84k | T expected = (T)slot(T); \ | 121 | 2.84k | u64 ea = slot(u32); \ | 122 | 2.84k | ea += immediate(u32); \ | 123 | 2.84k | d_m3AtomicCheckAlways(sizeof(T), ea); \ | 124 | 2.84k | \ | 125 | 2.84k | if (M3_UNLIKELY(not m3MemInfo(_mem)->isShared)) { \ | 126 | 1 | newTrap(m3Err_trapExpectedSharedMemory); \ | 127 | 1 | } \ | 128 | 2.84k | \ | 129 | 2.84k | if (m3_AtomicLoad_##T(d_m3AtomicCell(ea)) != expected) { \ | 130 | 2.08k | _r0 = 1; \ | 131 | 2.08k | nextOp(); \ | 132 | 2.08k | } \ | 133 | 2.84k | \ | 134 | 2.84k | if (timeout < 0) { \ | 135 | 16 | newTrap(m3Err_trapWaitForever); \ | 136 | 16 | } \ | 137 | 756 | \ | 138 | 756 | m3_HostSleepNs((u64)timeout); \ | 139 | 740 | _r0 = 2; \ | 140 | 740 | nextOp(); \ | 141 | 756 | } |
|
142 | | |
143 | | d_m3AtomicWaitOp(AtomicWait32, u32); |
144 | | d_m3AtomicWaitOp(AtomicWait64, u64); |
145 | | |
146 | | #endif // d_m3HasAtomics |