Skip to content

Commit 90fbbc7

Browse files
Add parsing/printing for relaxed atomics in RMW
1 parent 482f36f commit 90fbbc7

7 files changed

Lines changed: 301 additions & 135 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import itertools
2+
from dataclasses import astuple, dataclass
3+
4+
# Workaround for python <3.10, escape characters can't appear in f-strings.
5+
# Although we require 3.10 in some places, the formatter complains without this.
6+
newline = "\n"
7+
8+
backslash = '\\'
9+
10+
11+
@dataclass
12+
class instruction_test:
13+
op: str
14+
arg: str
15+
should_drop: bool
16+
bin: bytes
17+
18+
19+
ALL_OPS = [
20+
instruction_test("i32.atomic.load", "(i32.const 51)", True, b"\x41\x33\xfe\x10%(align)s%(memidx)s%(ordering)s\x00\x1a"),
21+
instruction_test("i32.atomic.store", "(i32.const 51) (i32.const 51)", False, b"\x41\x33\x41\x33\xfe\x17%(align)s%(memidx)s%(ordering)s\x00"),
22+
instruction_test("i32.atomic.rmw.add", "(i32.const 51) (i32.const 51)", True, b"\x41\x33\x41\x33\xfe\x1e%(align)s%(memidx)s%(ordering)s\x00\x1a"),
23+
]
24+
25+
26+
def indent(s):
27+
return "\n".join(f" {line}" if line else "" for line in s.split("\n"))
28+
29+
30+
# skips None for convenience
31+
def instruction(*args):
32+
return f"({' '.join(arg for arg in args if arg is not None)})"
33+
34+
35+
def atomic_instruction(op, memid, ordering, /, *args, drop):
36+
if drop:
37+
return f"(drop {instruction(op, memid, ordering, *args)})"
38+
return instruction(op, memid, ordering, *args)
39+
40+
41+
def func(memid, ordering):
42+
return f'''(func ${ordering if ordering is not None else "no_ordering"}{"_with_memid" if memid is not None else "_without_memid"}
43+
{indent(newline.join(atomic_instruction(op, memid, ordering, arg, drop=should_drop) for op, arg, should_drop, _ in map(astuple, ALL_OPS)))}
44+
)'''
45+
46+
47+
def module(*statements):
48+
return f'''(module
49+
{newline.join(map(indent, statements))}
50+
)'''
51+
52+
53+
def module_binary(bin):
54+
return f'''(module binary "{''.join(f'{backslash}{byte:02x}' for byte in bin)}")'''
55+
56+
57+
def assert_invalid(module, reason):
58+
return f'''(assert_invalid {module} "{reason}")'''
59+
60+
61+
def text_test():
62+
# Declare two memories so we have control over whether the memory idx is printed
63+
# A memory idx of 0 is allowed to be omitted.
64+
return module(
65+
"(memory 1 1 shared)",
66+
"(memory 1 1 shared)",
67+
"",
68+
"\n\n".join([f'{func(memid, ordering)}' for memid in [None, "1"] for ordering in [None, "acqrel", "seqcst"]]))
69+
70+
71+
def to_unsigned_leb(num):
72+
ret = bytearray()
73+
74+
if num == 0:
75+
ret = bytearray()
76+
ret.append(0)
77+
return ret
78+
ret = bytearray()
79+
while num > 0:
80+
rem = num >> 7
81+
ret.append((num & 0x7F) | (bool(rem) << 7))
82+
83+
num = rem
84+
return ret
85+
86+
87+
def bin_to_str(bin):
88+
return ''.join(f'{backslash}{byte:02x}' for byte in bin)
89+
90+
91+
@dataclass
92+
class statement:
93+
bin: bytes
94+
text: str
95+
96+
97+
@dataclass
98+
class function:
99+
body: [statement]
100+
memidx: bytes
101+
ordering: bytes
102+
103+
104+
def normalize_spaces(s):
105+
return " ".join(s.split())
106+
107+
108+
def binary_line(bin):
109+
return f'"{bin_to_str(bin)}"\n'
110+
111+
112+
def binary_test_example():
113+
return r'''(module binary
114+
"\00asm\01\00\00\00" ;; header + version
115+
"\01\05\01\60\00\01\7f\03\02\01\00\05\05\01\03\17\80\02" ;; other sections
116+
"\0a\0c\01" ;; code section
117+
"\0a\00" ;; func size + decl count
118+
"\41\33" ;; i32.const 51
119+
"\fe\10" ;; i32.atomic.load
120+
"\62" ;; 2 | (1<<5) | (1<<6): Alignment of 2 (32-bit load), with bit 5 set indicating that the next byte is a memory ordering
121+
"\00" ;; memory index
122+
"\01" ;; acqrel ordering
123+
"\00" ;; offset
124+
"\0b" ;; end
125+
)'''
126+
127+
128+
def binary_tests():
129+
funcs: [function] = []
130+
for (memidx_bytes, memidx), (ordering_bytes, ordering) in itertools.product([(b'', None), (b'\x01', "1")], [(b'', None), (b'\x00', "seqcst"), (b'\x01', "acqrel")]):
131+
func = function([], memidx, ordering)
132+
for test_case in ALL_OPS:
133+
align = 2 | (bool(memidx_bytes) << 6) | (bool(ordering_bytes) << 5)
134+
s = statement(
135+
bin=test_case.bin % {b'align': int.to_bytes(align), b'ordering': ordering_bytes if "rmw" not in test_case.op else int.to_bytes(ordering_bytes[0] | ordering_bytes[0] << 4) if ordering_bytes else b'', b'memidx': memidx_bytes},
136+
text=atomic_instruction(test_case.op, memidx, ordering, test_case.arg, drop=test_case.should_drop))
137+
func.body.append(s)
138+
139+
# Functions end with 0x0b.
140+
func.body[-1].bin += b'\x0b'
141+
funcs.append(func)
142+
143+
str_builder = []
144+
145+
for func in funcs:
146+
bin_size = sum(len(statement.bin) for statement in func.body)
147+
# body size plus 1 byte for the number of locals (0)
148+
func_bytes = to_unsigned_leb(bin_size + 1)
149+
# number of locals, none in our case
150+
func_bytes.append(0x00)
151+
str_builder.append(f'"{bin_to_str(func_bytes)}" ;; func\n')
152+
for stmt in func.body:
153+
str_builder.append(f'"{bin_to_str(stmt.bin)}" ;; {stmt.text}\n')
154+
155+
section_size = (
156+
# function body size
157+
sum(len(statement.bin) for func in funcs for statement in func.body) +
158+
# function count byte
159+
1 +
160+
# num locals per function (always 0)
161+
len(funcs) +
162+
# each function declares its size, add bytes for the LEB encoding of each function's size
163+
sum(len(to_unsigned_leb(sum(len(statement.bin) for statement in func.body))) for func in funcs))
164+
165+
code_section = bytearray(b"\x0a") + to_unsigned_leb(section_size) + to_unsigned_leb(len(funcs))
166+
167+
'''(module
168+
(memory 1 1 shared)
169+
(memory 1 1 shared)
170+
)
171+
'''
172+
module = b"\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x04\01\x60\x00\x00\x03\x07\06\x00\x00\x00\x00\x00\x00\x05\07\x02\x03\x01\x01\x03\x01\x01"
173+
with open("asdf.wasm", "wb") as f:
174+
f.write(module + code_section)
175+
176+
str_builder = [binary_line(module), f'"{bin_to_str(code_section)}" ;; code section\n'] + str_builder
177+
178+
return f"(module binary\n{indent(''.join(str_builder))})"
179+
180+
181+
def failing_test(instruction, arg, /, memidx, drop):
182+
"""Module assertion that sets a memory ordering immediate for a non-atomic instruction"""
183+
184+
func = f"(func ${''.join(filter(str.isalnum, instruction))} {atomic_instruction(instruction, memidx, 'acqrel', arg, drop=drop)})"
185+
return assert_invalid(module("(memory 1 1 shared)", "", func), f"Can't set memory ordering for non-atomic {instruction}")
186+
187+
188+
def drop_atomic(instruction):
189+
first, atomic, last = instruction.partition(".atomic")
190+
return first + last
191+
192+
193+
def failing_tests():
194+
inst = ALL_OPS[0]
195+
op = drop_atomic(inst.op)
196+
197+
return failing_test(op, inst.arg, memidx=None, drop=inst.should_drop)
198+
199+
200+
def main():
201+
print(text_test())
202+
print()
203+
print(binary_test_example())
204+
print()
205+
print(binary_tests())
206+
print()
207+
print(failing_tests())
208+
print()
209+
210+
211+
if __name__ == "__main__":
212+
main()

src/parser/parsers.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,10 +1815,20 @@ Result<> makeAtomicRMW(Ctx& ctx,
18151815
uint8_t bytes) {
18161816
auto mem = maybeMemidx(ctx);
18171817
CHECK_ERR(mem);
1818+
1819+
auto maybeOrder = maybeMemOrder(ctx);
1820+
CHECK_ERR(maybeOrder);
1821+
18181822
auto arg = memarg(ctx, bytes);
18191823
CHECK_ERR(arg);
1820-
return ctx.makeAtomicRMW(
1821-
pos, annotations, op, type, bytes, mem.getPtr(), *arg, MemoryOrder::SeqCst);
1824+
return ctx.makeAtomicRMW(pos,
1825+
annotations,
1826+
op,
1827+
type,
1828+
bytes,
1829+
mem.getPtr(),
1830+
*arg,
1831+
maybeOrder ? *maybeOrder : MemoryOrder::SeqCst);
18221832
}
18231833

18241834
template<typename Ctx>

src/passes/Print.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ struct PrintExpressionContents
661661
}
662662
restoreNormalColor(o);
663663
printMemoryName(curr->memory, o, wasm);
664+
printMemoryOrder(curr->order);
664665
if (curr->offset) {
665666
o << " offset=" << curr->offset;
666667
}

src/wasm/wasm-binary.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3705,37 +3705,37 @@ Result<> WasmBinaryReader::readInst() {
37053705
case BinaryConsts::I32AtomicRMW##op: { \
37063706
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37073707
return builder.makeAtomicRMW( \
3708-
RMW##op, 4, offset, Type::i32, mem, MemoryOrder::SeqCst); \
3708+
RMW##op, 4, offset, Type::i32, mem, memoryOrder); \
37093709
} \
37103710
case BinaryConsts::I32AtomicRMW##op##8U: { \
37113711
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37123712
return builder.makeAtomicRMW( \
3713-
RMW##op, 1, offset, Type::i32, mem, MemoryOrder::SeqCst); \
3713+
RMW##op, 1, offset, Type::i32, mem, memoryOrder); \
37143714
} \
37153715
case BinaryConsts::I32AtomicRMW##op##16U: { \
37163716
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37173717
return builder.makeAtomicRMW( \
3718-
RMW##op, 2, offset, Type::i32, mem, MemoryOrder::SeqCst); \
3718+
RMW##op, 2, offset, Type::i32, mem, memoryOrder); \
37193719
} \
37203720
case BinaryConsts::I64AtomicRMW##op: { \
37213721
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37223722
return builder.makeAtomicRMW( \
3723-
RMW##op, 8, offset, Type::i64, mem, MemoryOrder::SeqCst); \
3723+
RMW##op, 8, offset, Type::i64, mem, memoryOrder); \
37243724
} \
37253725
case BinaryConsts::I64AtomicRMW##op##8U: { \
37263726
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37273727
return builder.makeAtomicRMW( \
3728-
RMW##op, 1, offset, Type::i64, mem, MemoryOrder::SeqCst); \
3728+
RMW##op, 1, offset, Type::i64, mem, memoryOrder); \
37293729
} \
37303730
case BinaryConsts::I64AtomicRMW##op##16U: { \
37313731
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37323732
return builder.makeAtomicRMW( \
3733-
RMW##op, 2, offset, Type::i64, mem, MemoryOrder::SeqCst); \
3733+
RMW##op, 2, offset, Type::i64, mem, memoryOrder); \
37343734
} \
37353735
case BinaryConsts::I64AtomicRMW##op##32U: { \
37363736
auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \
37373737
return builder.makeAtomicRMW( \
3738-
RMW##op, 4, offset, Type::i64, mem, MemoryOrder::SeqCst); \
3738+
RMW##op, 4, offset, Type::i64, mem, memoryOrder); \
37393739
}
37403740

37413741
RMW(Add);

src/wasm/wasm-ir-builder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,7 +1075,7 @@ Result<> IRBuilder::visitEnd() {
10751075
tryy->name = scope.label;
10761076
tryy->finalize(tryy->type);
10771077
push(maybeWrapForLabel(tryy));
1078-
} else if (Try* tryy;
1078+
} else if (Try * tryy;
10791079
(tryy = scope.getCatch()) || (tryy = scope.getCatchAll())) {
10801080
auto index = scope.getIndex();
10811081
setCatchBody(tryy, *expr, index);

src/wasm/wasm-stack.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ void BinaryInstWriter::visitAtomicRMW(AtomicRMW* curr) {
540540
curr->bytes,
541541
curr->offset,
542542
curr->memory,
543-
MemoryOrder::SeqCst,
543+
curr->order,
544544
/*isRMW=*/true);
545545
}
546546

0 commit comments

Comments
 (0)