blob: 0b98b98579ce3e0c60ffef56b3bfea81e70fbcd6 [file] [log] [blame]
import itertools
import math
from collections.abc import Iterator
from dataclasses import dataclass
from enum import Enum
from relaxed_atomic_execution_tests import acqrel_execution_tests
# Workaround for python <3.10, escape characters can't appear in f-strings.
# Although we require 3.10 in some places, the formatter complains without this.
newline = "\n"
backslash = "\\"
FUNC_NAME = "$test-all-ops"
def indent(s):
return "\n".join(f" {line}" if line else "" for line in s.split("\n"))
class ValueType(Enum):
i32 = 32
i64 = 64
class Ordering(Enum):
seqcst = 0
acqrel = 1
@dataclass
class Template:
op: str
value_type: object
args: int
should_drop: bool
bin: bytes = b""
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"),
Template(op="i32.atomic.load16_u", value_type=ValueType.i64, args=1, should_drop=True, bin=b"\xfe\x13"),
Template(op="i64.atomic.load8_u", value_type=ValueType.i64, args=1, should_drop=True, bin=b"\xfe\x14"),
Template(op="i64.atomic.load16_u", value_type=ValueType.i64, args=1, should_drop=True, bin=b"\xfe\x15"),
Template(op="i64.atomic.load32_u", value_type=ValueType.i64, args=1, should_drop=True, bin=b"\xfe\x16"),
Template(op="i32.atomic.store", value_type=ValueType.i32, args=2, should_drop=False, bin=b"\xfe\x17"),
Template(op="i64.atomic.store", value_type=ValueType.i64, args=2, should_drop=False, bin=b"\xfe\x18"),
Template(op="i32.atomic.store8", value_type=ValueType.i32, args=2, should_drop=False, bin=b"\xfe\x19"),
Template(op="i32.atomic.store16", value_type=ValueType.i32, args=2, should_drop=False, bin=b"\xfe\x1a"),
Template(op="i64.atomic.store8", value_type=ValueType.i64, args=2, should_drop=False, bin=b"\xfe\x1b"),
Template(op="i64.atomic.store16", value_type=ValueType.i64, args=2, should_drop=False, bin=b"\xfe\x1c"),
Template(op="i64.atomic.store32", value_type=ValueType.i64, args=2, should_drop=False, bin=b"\xfe\x1d"),
Template(op="i32.atomic.rmw.add", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x1e"),
Template(op="i64.atomic.rmw.add", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x1f"),
Template(op="i32.atomic.rmw8.add_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x20"),
Template(op="i32.atomic.rmw16.add_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x21"),
Template(op="i64.atomic.rmw8.add_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x22"),
Template(op="i64.atomic.rmw16.add_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x23"),
Template(op="i64.atomic.rmw32.add_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x24"),
Template(op="i32.atomic.rmw.sub", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x25"),
Template(op="i64.atomic.rmw.sub", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x26"),
Template(op="i32.atomic.rmw8.sub_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x27"),
Template(op="i32.atomic.rmw16.sub_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x28"),
Template(op="i64.atomic.rmw8.sub_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x29"),
Template(op="i64.atomic.rmw16.sub_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x2a"),
Template(op="i64.atomic.rmw32.sub_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x2b"),
Template(op="i32.atomic.rmw.and", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x2c"),
Template(op="i64.atomic.rmw.and", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x2d"),
Template(op="i32.atomic.rmw8.and_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x2e"),
Template(op="i32.atomic.rmw16.and_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x2f"),
Template(op="i64.atomic.rmw8.and_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x30"),
Template(op="i64.atomic.rmw16.and_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x31"),
Template(op="i64.atomic.rmw32.and_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x32"),
Template(op="i32.atomic.rmw.or", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x33"),
Template(op="i64.atomic.rmw.or", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x34"),
Template(op="i32.atomic.rmw8.or_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x35"),
Template(op="i32.atomic.rmw16.or_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x36"),
Template(op="i64.atomic.rmw8.or_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x37"),
Template(op="i64.atomic.rmw16.or_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x38"),
Template(op="i64.atomic.rmw32.or_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x39"),
Template(op="i32.atomic.rmw.xor", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x3a"),
Template(op="i64.atomic.rmw.xor", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x3b"),
Template(op="i32.atomic.rmw8.xor_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x3c"),
Template(op="i32.atomic.rmw16.xor_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x3d"),
Template(op="i64.atomic.rmw8.xor_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x3e"),
Template(op="i64.atomic.rmw16.xor_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x3f"),
Template(op="i64.atomic.rmw32.xor_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x40"),
Template(op="i32.atomic.rmw.xchg", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x41"),
Template(op="i64.atomic.rmw.xchg", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x42"),
Template(op="i32.atomic.rmw8.xchg_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x43"),
Template(op="i32.atomic.rmw16.xchg_u", value_type=ValueType.i32, args=2, should_drop=True, bin=b"\xfe\x44"),
Template(op="i64.atomic.rmw8.xchg_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x45"),
Template(op="i64.atomic.rmw16.xchg_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x46"),
Template(op="i64.atomic.rmw32.xchg_u", value_type=ValueType.i64, args=2, should_drop=True, bin=b"\xfe\x47"),
Template(op="i32.atomic.rmw.cmpxchg", value_type=ValueType.i32, args=3, should_drop=True, bin=b"\xfe\x48"),
Template(op="i64.atomic.rmw.cmpxchg", value_type=ValueType.i64, args=3, should_drop=True, bin=b"\xfe\x49"),
Template(op="i32.atomic.rmw8.cmpxchg_u", value_type=ValueType.i32, args=3, should_drop=True, bin=b"\xfe\x4a"),
Template(op="i32.atomic.rmw16.cmpxchg_u", value_type=ValueType.i32, args=3, should_drop=True, bin=b"\xfe\x4b"),
Template(op="i64.atomic.rmw8.cmpxchg_u", value_type=ValueType.i64, args=3, should_drop=True, bin=b"\xfe\x4c"),
Template(op="i64.atomic.rmw16.cmpxchg_u", value_type=ValueType.i64, args=3, should_drop=True, bin=b"\xfe\x4d"),
Template(op="i64.atomic.rmw32.cmpxchg_u", value_type=ValueType.i64, args=3, should_drop=True, bin=b"\xfe\x4e"),
]
def all_combinations() -> Iterator[(Template, (int, ValueType), Ordering)]:
"""Yield tuples covering all possible combinations of atomic memory operations.
(template, (idx, memory_ptr_type), ordering)
where idx is a memory index or None representing an implicit 0 index
and memory_ptr_type is i32 or i64 based on the memory being indexed
and ordering is an `Ordering` enum or None representing an implicit seqcst 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])
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))"""
memargs = []
if mem_idx is not None:
memargs.append(str(mem_idx))
if ordering is not None:
memargs.append(ordering.name)
memarg_str = " ".join(memargs) + " " if memargs else ""
# The first argument (the memory location) must match the memory that we're indexing. Other arguments match the op (e.g. i32 for i32.atomic.load).
args = [f"({mem_ptr_type.name}.const 0)"] + [f"({template.value_type.name}.const 42)" for _ in range(template.args - 1)]
op_str = "(" + "".join([template.op, " ", memarg_str, " ".join(args)]) + ")"
if not template.should_drop:
return op_str
return f"(drop {op_str})"
def func():
"""Return a func exercising all ops in `templates` e.g.
(func $test-all-ops
(drop (i32.atomic.load (i32.const 42)))
(drop (i32.atomic.load acqrel (i32.const 42)))
...
)
"""
return f''';; Memory index must come before memory ordering if present.
;; Both immediates are optional; an ommitted memory ordering will be treated as seqcst.
(func $test-all-ops
{indent(newline.join(statement(template, mem_idx, mem_ptr_type, ordering) for template, (mem_idx, mem_ptr_type), ordering in all_combinations()))}
)'''
def text_test():
"""Return a (module ...) that exercises all ops in `templates`."""
return f'''(module
(memory i32 1 1)
(memory i64 1 1)
{indent(func())}
)'''
def invalid_text_test():
return '''(assert_invalid (module
(memory 1 1 shared)
(func $i32load (drop (i32.load acqrel (i32.const 51))))
) "Can't set memory ordering for non-atomic i32.load")'''
def bin_to_str(bin: bytes) -> str:
"""Return binary formatted for .wast format e.g. \00\61\73\6d\01\00\00\00"""
return ''.join(f'{backslash}{byte:02x}' for byte in bin)
# (i64.const 0)
I64CONST = b"\x42\x00"
# (i32.const 0)
I32CONST = b"\x41\x00"
def bin_statement_lines(template: Template, mem_idx: int, mem_ptr_type: ValueType, ordering: Ordering) -> Iterator[(bytes, str)]:
"""Yield (b, comment) where `b` is a part of the statement using `template`, and `comment` explains that part, e.g.
(b"\xfe\x11", "i64.atomic.load")
The entire iterator represents a complete expression using the `template`. e.g.
(drop (i32.atomic.load (i32.const 42)))
"""
arg_one_bin = I64CONST if mem_ptr_type == ValueType.i64 else I32CONST
yield arg_one_bin, f"({mem_ptr_type.name}.const 0)"
for _ in range(template.args - 1):
const = I64CONST if template.value_type == ValueType.i64 else I32CONST
yield const, f"({template.value_type.name}.const 51)"
yield template.bin, template.op
has_ordering = ordering is not None
has_mem_idx = mem_idx is not None
raw_alignment = int(math.log2(mem_ptr_type.value // 8))
alignment = raw_alignment | (has_ordering << 5) | (has_mem_idx << 6)
comment = f"Alignment of {raw_alignment}" \
f'{" with bit 5 set indicating that an ordering immediate follows" if has_ordering else ""}' \
f'{" and" if has_ordering and has_mem_idx else ""}' \
f'{" with bit 6 set indicating that a memory index immediate follows" if has_mem_idx else ""}'
yield int.to_bytes(alignment), comment
if has_mem_idx:
yield int.to_bytes(mem_idx), "memory index"
if has_ordering:
ordering_num = ordering.value
if "rmw" in template.op:
ordering_num |= ordering_num << 4
yield int.to_bytes(ordering_num), f"{ordering.name} memory ordering"
yield b"\x00", "offset"
if template.should_drop:
yield b"\x1a", "drop"
def bin_statement(template: Template, mem_idx: int, mem_ptr_type: ValueType, ordering: Ordering) -> (bytes, str):
"""Return (b, s) where `b` is the binary exercising an instruction, e.g.
(drop (i32.atomic.load (i32.const 42)))
and `s` is a str containing the binary along with comments explaining it, e.g.
"\41\33" ;; (i32.const 51)
"\fe\10" ;; i32.atomic.load
"\42" ;; Alignment of 2 with bit 6 set indicating that a memory index immediate follows
"\00" ;; memory index
"\00" ;; offset
"\1a" ;; drop
"""
bins = []
strs = []
for bin, comment in bin_statement_lines(template, mem_idx, mem_ptr_type, ordering):
bins.append(bin)
strs.append(f'"{bin_to_str(bin)}" ;; {comment}')
return b"".join(bins), "\n".join(strs)
def to_unsigned_leb(num: int) -> bytearray:
ret = bytearray()
if num == 0:
ret.append(0)
return ret
rem = num
while (num := rem) > 0:
rem = num >> 7
ret.append((num & 0x7F) | (bool(rem) << 7))
return ret
def binary_func_body():
statement_strs = []
statement_bins = []
for template, (mem_idx, mem_ptr_type), ordering in all_combinations():
bin, s = bin_statement(template, mem_idx, mem_ptr_type, ordering)
statement_bins.append(bin)
statement_strs.append(s)
# plus two with \0b function end byte and local decl count (0)
func_body_size = sum(map(len, statement_bins)) + 2
section_size = (
func_body_size + # function body bytes
len(to_unsigned_leb(func_body_size)) + # LEB encoding of function body size
1) # Function count byte (0)
code_section_strs = \
[rf'"\0a{bin_to_str(to_unsigned_leb(section_size))}\01" ;; Code section' "\n"
rf'"{bin_to_str(to_unsigned_leb(func_body_size))}\00" ;; func {FUNC_NAME}'] + \
list(map(indent, statement_strs)) + \
[r' "\0b" ;; end']
return "\n\n".join(code_section_strs) + "\n"
def module_binary(str: str):
return f'''(module binary
{indent(str)})'''
def binary_test():
header = fr'''"\00asm\01\00\00\00" ;; Wasm header
"\01\04\01" ;; Type section
"\60\00\00" ;; {FUNC_NAME} type
"\03\02\01\00" ;; Function section
"\05\07\02" ;; Memory section
"\01\01\01" ;; (memory i32 1 1)
"\05\01\01" ;; (memory i64 1 1)'''
return module_binary(header + "\n" + binary_func_body())
def main():
print(";; Generated by scripts/test/generate_atomic_spec_test.py. Do not edit manually.")
print()
print(text_test())
print()
print(invalid_text_test())
print()
print(binary_test())
print()
print(acqrel_execution_tests)
if __name__ == "__main__":
main()