use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use crate::entity::{AggId, Block, FuncId, GlobalId, SigId, StackSlot, Value};
use crate::ops::{BinaryOp, CmpOp, ConvOp, Policy, UnaryOp};
use crate::types::{MemType, Type};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct MemFlags {
pub align: u32,
pub volatile: bool,
}
impl MemFlags {
pub const MAX_ALIGN: u32 = 1 << 29;
#[must_use]
pub const fn new(align: u32) -> MemFlags {
MemFlags {
align,
volatile: false,
}
}
#[must_use]
pub const fn volatile(mut self) -> MemFlags {
self.volatile = true;
self
}
}
impl fmt::Display for MemFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "align {}", self.align)?;
if self.volatile {
f.write_str(" volatile")?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum MemOrder {
Relaxed,
Acquire,
Release,
AcqRel,
SeqCst,
}
impl MemOrder {
pub const ALL: [MemOrder; 5] = [
MemOrder::Relaxed,
MemOrder::Acquire,
MemOrder::Release,
MemOrder::AcqRel,
MemOrder::SeqCst,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
MemOrder::Relaxed => "relaxed",
MemOrder::Acquire => "acquire",
MemOrder::Release => "release",
MemOrder::AcqRel => "acq_rel",
MemOrder::SeqCst => "seq_cst",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<MemOrder> {
MemOrder::ALL.iter().copied().find(|o| o.name() == name)
}
#[must_use]
pub const fn strength(self) -> u8 {
match self {
MemOrder::Relaxed => 0,
MemOrder::Acquire | MemOrder::Release => 1,
MemOrder::AcqRel => 2,
MemOrder::SeqCst => 3,
}
}
}
impl fmt::Display for MemOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum RmwOp {
Xchg,
Add,
Sub,
And,
Or,
Xor,
Nand,
Min,
Max,
}
impl RmwOp {
pub const ALL: [RmwOp; 9] = [
RmwOp::Xchg,
RmwOp::Add,
RmwOp::Sub,
RmwOp::And,
RmwOp::Or,
RmwOp::Xor,
RmwOp::Nand,
RmwOp::Min,
RmwOp::Max,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
RmwOp::Xchg => "xchg",
RmwOp::Add => "add",
RmwOp::Sub => "sub",
RmwOp::And => "and",
RmwOp::Or => "or",
RmwOp::Xor => "xor",
RmwOp::Nand => "nand",
RmwOp::Min => "min",
RmwOp::Max => "max",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<RmwOp> {
RmwOp::ALL.iter().copied().find(|o| o.name() == name)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Attrs(u16);
impl Attrs {
pub const NONE: Attrs = Attrs(0);
pub const NOUNWIND: Attrs = Attrs(1);
pub const NORETURN: Attrs = Attrs(1 << 1);
pub const NOGC: Attrs = Attrs(1 << 2);
pub const READONLY: Attrs = Attrs(1 << 3);
pub const READNONE: Attrs = Attrs(1 << 4);
pub const COLD: Attrs = Attrs(1 << 5);
pub const NOINLINE: Attrs = Attrs(1 << 6);
pub const INLINE: Attrs = Attrs(1 << 7);
const NAMES: [(Attrs, &'static str); 8] = [
(Attrs::NOUNWIND, "nounwind"),
(Attrs::NORETURN, "noreturn"),
(Attrs::NOGC, "nogc"),
(Attrs::READONLY, "readonly"),
(Attrs::READNONE, "readnone"),
(Attrs::COLD, "cold"),
(Attrs::NOINLINE, "noinline"),
(Attrs::INLINE, "inline"),
];
#[must_use]
pub const fn contains(self, other: Attrs) -> bool {
self.0 & other.0 == other.0
}
#[must_use]
pub const fn union(self, other: Attrs) -> Attrs {
Attrs(self.0 | other.0)
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub fn from_name(name: &str) -> Option<Attrs> {
Attrs::NAMES
.iter()
.find(|(_, n)| *n == name)
.map(|(a, _)| *a)
}
#[must_use]
pub const fn bits(self) -> u16 {
self.0
}
}
impl core::ops::BitOr for Attrs {
type Output = Attrs;
fn bitor(self, rhs: Attrs) -> Attrs {
self.union(rhs)
}
}
impl core::ops::BitOrAssign for Attrs {
fn bitor_assign(&mut self, rhs: Attrs) {
*self = self.union(rhs);
}
}
impl fmt::Display for Attrs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
return Ok(());
}
let mut sep = "[";
for (attr, name) in Attrs::NAMES {
if self.contains(attr) {
write!(f, "{sep}{name}")?;
sep = ", ";
}
}
f.write_str("]")
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum CallTarget {
Direct(FuncId),
Indirect {
sig: SigId,
callee: Value,
},
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Call {
pub target: CallTarget,
pub args: Vec<Value>,
pub gc: Vec<Value>,
pub attrs: Attrs,
}
impl Call {
#[must_use]
pub fn direct(func: FuncId, args: &[Value]) -> Call {
Call {
target: CallTarget::Direct(func),
args: args.to_vec(),
gc: Vec::new(),
attrs: Attrs::NONE,
}
}
#[must_use]
pub fn indirect(sig: SigId, callee: Value, args: &[Value]) -> Call {
Call {
target: CallTarget::Indirect { sig, callee },
args: args.to_vec(),
gc: Vec::new(),
attrs: Attrs::NONE,
}
}
#[must_use]
pub fn with_gc(mut self, gc: &[Value]) -> Call {
self.gc = gc.to_vec();
self
}
#[must_use]
pub fn with_attrs(mut self, attrs: Attrs) -> Call {
self.attrs = attrs;
self
}
pub(crate) fn for_each_operand(&self, mut f: impl FnMut(Value)) {
if let CallTarget::Indirect { callee, .. } = self.target {
f(callee);
}
self.args.iter().copied().for_each(&mut f);
self.gc.iter().copied().for_each(f);
}
pub(crate) fn map_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
if let CallTarget::Indirect { callee, .. } = &mut self.target {
*callee = f(*callee);
}
for v in self.args.iter_mut().chain(self.gc.iter_mut()) {
*v = f(*v);
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum InstData {
Const {
ty: Type,
bits: u64,
},
Unary {
op: UnaryOp,
policy: Policy,
arg: Value,
},
Binary {
op: BinaryOp,
policy: Policy,
args: [Value; 2],
},
Fma {
args: [Value; 3],
},
Compare {
op: CmpOp,
args: [Value; 2],
},
Convert {
op: ConvOp,
policy: Policy,
to: Type,
arg: Value,
},
Select {
cond: Value,
args: [Value; 2],
},
StackAddr {
slot: StackSlot,
},
GlobalAddr {
global: GlobalId,
},
FuncAddr {
func: FuncId,
},
FieldAddr {
base: Value,
agg: AggId,
field: u32,
},
ElemAddr {
base: Value,
elem: MemType,
index: Value,
},
PtrOffset {
base: Value,
offset: Value,
},
Load {
ty: Type,
addr: Value,
flags: MemFlags,
},
Store {
value: Value,
addr: Value,
flags: MemFlags,
},
AtomicLoad {
ty: Type,
addr: Value,
order: MemOrder,
},
AtomicStore {
value: Value,
addr: Value,
order: MemOrder,
},
AtomicRmw {
op: RmwOp,
addr: Value,
value: Value,
order: MemOrder,
},
CmpXchg {
addr: Value,
expected: Value,
new: Value,
success: MemOrder,
failure: MemOrder,
},
Fence {
order: MemOrder,
},
MemCopy {
dst: Value,
src: Value,
len: Value,
flags: MemFlags,
},
MemMove {
dst: Value,
src: Value,
len: Value,
flags: MemFlags,
},
MemSet {
dst: Value,
byte: Value,
len: Value,
flags: MemFlags,
},
Call(Box<Call>),
Safepoint {
gc: Box<[Value]>,
},
}
impl InstData {
pub fn for_each_operand(&self, mut f: impl FnMut(Value)) {
match self {
InstData::Const { .. }
| InstData::StackAddr { .. }
| InstData::GlobalAddr { .. }
| InstData::FuncAddr { .. }
| InstData::Fence { .. } => {}
InstData::Unary { arg, .. } | InstData::Convert { arg, .. } => f(*arg),
InstData::Binary { args, .. } | InstData::Compare { args, .. } => {
f(args[0]);
f(args[1]);
}
InstData::Fma { args } => args.iter().copied().for_each(f),
InstData::Select { cond, args } => {
f(*cond);
f(args[0]);
f(args[1]);
}
InstData::FieldAddr { base, .. } => f(*base),
InstData::ElemAddr { base, index, .. } => {
f(*base);
f(*index);
}
InstData::PtrOffset { base, offset } => {
f(*base);
f(*offset);
}
InstData::Load { addr, .. } | InstData::AtomicLoad { addr, .. } => f(*addr),
InstData::Store { value, addr, .. } | InstData::AtomicStore { value, addr, .. } => {
f(*value);
f(*addr);
}
InstData::AtomicRmw { addr, value, .. } => {
f(*addr);
f(*value);
}
InstData::CmpXchg {
addr,
expected,
new,
..
} => {
f(*addr);
f(*expected);
f(*new);
}
InstData::MemCopy { dst, src, len, .. } | InstData::MemMove { dst, src, len, .. } => {
f(*dst);
f(*src);
f(*len);
}
InstData::MemSet { dst, byte, len, .. } => {
f(*dst);
f(*byte);
f(*len);
}
InstData::Call(call) => call.for_each_operand(f),
InstData::Safepoint { gc } => gc.iter().copied().for_each(f),
}
}
pub fn map_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
match self {
InstData::Const { .. }
| InstData::StackAddr { .. }
| InstData::GlobalAddr { .. }
| InstData::FuncAddr { .. }
| InstData::Fence { .. } => {}
InstData::Unary { arg, .. } | InstData::Convert { arg, .. } => *arg = f(*arg),
InstData::Binary { args, .. } | InstData::Compare { args, .. } => {
args[0] = f(args[0]);
args[1] = f(args[1]);
}
InstData::Fma { args } => {
for a in args.iter_mut() {
*a = f(*a);
}
}
InstData::Select { cond, args } => {
*cond = f(*cond);
args[0] = f(args[0]);
args[1] = f(args[1]);
}
InstData::FieldAddr { base, .. } => *base = f(*base),
InstData::ElemAddr { base, index, .. } => {
*base = f(*base);
*index = f(*index);
}
InstData::PtrOffset { base, offset } => {
*base = f(*base);
*offset = f(*offset);
}
InstData::Load { addr, .. } | InstData::AtomicLoad { addr, .. } => *addr = f(*addr),
InstData::Store { value, addr, .. } | InstData::AtomicStore { value, addr, .. } => {
*value = f(*value);
*addr = f(*addr);
}
InstData::AtomicRmw { addr, value, .. } => {
*addr = f(*addr);
*value = f(*value);
}
InstData::CmpXchg {
addr,
expected,
new,
..
} => {
*addr = f(*addr);
*expected = f(*expected);
*new = f(*new);
}
InstData::MemCopy { dst, src, len, .. } | InstData::MemMove { dst, src, len, .. } => {
*dst = f(*dst);
*src = f(*src);
*len = f(*len);
}
InstData::MemSet { dst, byte, len, .. } => {
*dst = f(*dst);
*byte = f(*byte);
*len = f(*len);
}
InstData::Call(call) => call.map_operands(f),
InstData::Safepoint { gc } => {
for v in gc.iter_mut() {
*v = f(*v);
}
}
}
}
#[must_use]
pub fn has_side_effects(&self) -> bool {
use crate::ops::{DivZero, Overflow};
let traps = |p: &Policy| {
matches!(p.overflow, Some(Overflow::Trap)) || matches!(p.div_zero, Some(DivZero::Trap))
};
match self {
InstData::Unary { policy, .. }
| InstData::Binary { policy, .. }
| InstData::Convert { policy, .. } => traps(policy) || policy.can_error(),
InstData::Load { flags, .. } => flags.volatile,
InstData::Store { .. }
| InstData::AtomicLoad { .. }
| InstData::AtomicStore { .. }
| InstData::AtomicRmw { .. }
| InstData::CmpXchg { .. }
| InstData::Fence { .. }
| InstData::MemCopy { .. }
| InstData::MemMove { .. }
| InstData::MemSet { .. }
| InstData::Call(_)
| InstData::Safepoint { .. } => true,
InstData::Const { .. }
| InstData::Fma { .. }
| InstData::Compare { .. }
| InstData::Select { .. }
| InstData::StackAddr { .. }
| InstData::GlobalAddr { .. }
| InstData::FuncAddr { .. }
| InstData::FieldAddr { .. }
| InstData::ElemAddr { .. }
| InstData::PtrOffset { .. } => false,
}
}
#[must_use]
pub fn opcode(&self) -> &'static str {
match self {
InstData::Const { .. } => "const",
InstData::Unary { op, .. } => op.name(),
InstData::Binary { op, .. } => op.name(),
InstData::Fma { .. } => "fma",
InstData::Compare { op, .. } => op.name(),
InstData::Convert { op, .. } => op.name(),
InstData::Select { .. } => "select",
InstData::StackAddr { .. } => "stack_addr",
InstData::GlobalAddr { .. } => "global_addr",
InstData::FuncAddr { .. } => "func_addr",
InstData::FieldAddr { .. } => "field_addr",
InstData::ElemAddr { .. } => "elem_addr",
InstData::PtrOffset { .. } => "ptr_offset",
InstData::Load { .. } => "load",
InstData::Store { .. } => "store",
InstData::AtomicLoad { .. } => "atomic_load",
InstData::AtomicStore { .. } => "atomic_store",
InstData::AtomicRmw { .. } => "atomic_rmw",
InstData::CmpXchg { .. } => "cmpxchg",
InstData::Fence { .. } => "fence",
InstData::MemCopy { .. } => "memcpy",
InstData::MemMove { .. } => "memmove",
InstData::MemSet { .. } => "memset",
InstData::Call(call) => match call.target {
CallTarget::Direct(_) => "call",
CallTarget::Indirect { .. } => "call_indirect",
},
InstData::Safepoint { .. } => "safepoint",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum BlockArg {
Value(Value),
Result(u32),
Exception,
ErrorCode,
}
impl From<Value> for BlockArg {
fn from(v: Value) -> Self {
BlockArg::Value(v)
}
}
impl fmt::Display for BlockArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BlockArg::Value(v) => write!(f, "{v}"),
BlockArg::Result(i) => write!(f, "res{i}"),
BlockArg::Exception => f.write_str("exn"),
BlockArg::ErrorCode => f.write_str("err"),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct BlockCall {
pub block: Block,
pub args: Vec<BlockArg>,
}
impl BlockCall {
#[must_use]
pub fn new(block: Block, args: &[Value]) -> BlockCall {
BlockCall {
block,
args: args.iter().copied().map(BlockArg::Value).collect(),
}
}
#[must_use]
pub fn with_args(block: Block, args: &[BlockArg]) -> BlockCall {
BlockCall {
block,
args: args.to_vec(),
}
}
fn for_each_value(&self, mut f: impl FnMut(Value)) {
for arg in &self.args {
if let BlockArg::Value(v) = arg {
f(*v);
}
}
}
fn map_values(&mut self, mut f: impl FnMut(Value) -> Value) {
for arg in &mut self.args {
if let BlockArg::Value(v) = arg {
*v = f(*v);
}
}
}
}
impl fmt::Display for BlockCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.block)?;
if !self.args.is_empty() {
f.write_str("(")?;
for (i, a) in self.args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str(")")?;
}
Ok(())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct SwitchCase {
pub value: u64,
pub dest: BlockCall,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Terminator {
Jump(BlockCall),
Branch {
cond: Value,
then_dest: BlockCall,
else_dest: BlockCall,
},
Switch {
value: Value,
cases: Vec<SwitchCase>,
default: BlockCall,
},
Return {
values: Vec<Value>,
},
Unreachable,
Trap {
code: u32,
},
TailCall(Box<Call>),
Invoke {
call: Box<Call>,
normal: BlockCall,
unwind: BlockCall,
},
Check {
op: Box<InstData>,
normal: BlockCall,
error: BlockCall,
},
Resume {
payload: Value,
},
}
#[derive(Debug)]
pub struct Successors<'a> {
first: [Option<&'a BlockCall>; 2],
cases: core::slice::Iter<'a, SwitchCase>,
}
impl<'a> Iterator for Successors<'a> {
type Item = &'a BlockCall;
fn next(&mut self) -> Option<&'a BlockCall> {
if let Some(c) = self.cases.next() {
return Some(&c.dest);
}
if let Some(c) = self.first[0].take() {
return Some(c);
}
self.first[1].take()
}
}
impl Terminator {
#[must_use]
pub fn successors(&self) -> Successors<'_> {
let none: [Option<&BlockCall>; 2] = [None, None];
match self {
Terminator::Jump(c) => Successors {
first: [Some(c), None],
cases: [].iter(),
},
Terminator::Branch {
then_dest,
else_dest,
..
} => Successors {
first: [Some(then_dest), Some(else_dest)],
cases: [].iter(),
},
Terminator::Switch { cases, default, .. } => Successors {
first: [Some(default), None],
cases: cases.iter(),
},
Terminator::Invoke { normal, unwind, .. } => Successors {
first: [Some(normal), Some(unwind)],
cases: [].iter(),
},
Terminator::Check { normal, error, .. } => Successors {
first: [Some(normal), Some(error)],
cases: [].iter(),
},
Terminator::Return { .. }
| Terminator::Unreachable
| Terminator::Trap { .. }
| Terminator::TailCall(_)
| Terminator::Resume { .. } => Successors {
first: none,
cases: [].iter(),
},
}
}
pub fn for_each_successor_mut(&mut self, mut f: impl FnMut(&mut BlockCall)) {
match self {
Terminator::Jump(c) => f(c),
Terminator::Branch {
then_dest,
else_dest,
..
} => {
f(then_dest);
f(else_dest);
}
Terminator::Switch { cases, default, .. } => {
for c in cases {
f(&mut c.dest);
}
f(default);
}
Terminator::Invoke { normal, unwind, .. } => {
f(normal);
f(unwind);
}
Terminator::Check { normal, error, .. } => {
f(normal);
f(error);
}
Terminator::Return { .. }
| Terminator::Unreachable
| Terminator::Trap { .. }
| Terminator::TailCall(_)
| Terminator::Resume { .. } => {}
}
}
pub fn for_each_operand(&self, mut f: impl FnMut(Value)) {
self.for_each_own_operand(&mut f);
for dest in self.successors() {
dest.for_each_value(&mut f);
}
}
pub(crate) fn for_each_own_operand(&self, mut f: impl FnMut(Value)) {
match self {
Terminator::Jump(_) | Terminator::Unreachable | Terminator::Trap { .. } => {}
Terminator::Branch { cond, .. } => f(*cond),
Terminator::Switch { value, .. } => f(*value),
Terminator::Return { values } => values.iter().copied().for_each(f),
Terminator::TailCall(call) => call.for_each_operand(f),
Terminator::Invoke { call, .. } => call.for_each_operand(f),
Terminator::Check { op, .. } => op.for_each_operand(f),
Terminator::Resume { payload } => f(*payload),
}
}
pub fn map_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
self.map_own_operands(&mut f);
self.for_each_successor_mut(|c| c.map_values(&mut f));
}
pub(crate) fn map_own_operands(&mut self, mut f: impl FnMut(Value) -> Value) {
match self {
Terminator::Jump(_) | Terminator::Unreachable | Terminator::Trap { .. } => {}
Terminator::Branch { cond, .. } => *cond = f(*cond),
Terminator::Switch { value, .. } => *value = f(*value),
Terminator::Return { values } => {
for v in values.iter_mut() {
*v = f(*v);
}
}
Terminator::TailCall(call) | Terminator::Invoke { call, .. } => {
call.map_operands(&mut f);
}
Terminator::Check { op, .. } => op.map_operands(&mut f),
Terminator::Resume { payload } => *payload = f(*payload),
}
}
#[must_use]
pub fn opcode(&self) -> &'static str {
match self {
Terminator::Jump(_) => "jump",
Terminator::Branch { .. } => "br",
Terminator::Switch { .. } => "switch",
Terminator::Return { .. } => "ret",
Terminator::Unreachable => "unreachable",
Terminator::Trap { .. } => "trap",
Terminator::TailCall(call) => match call.target {
CallTarget::Direct(_) => "tail_call",
CallTarget::Indirect { .. } => "tail_call_indirect",
},
Terminator::Invoke { call, .. } => match call.target {
CallTarget::Direct(_) => "invoke",
CallTarget::Indirect { .. } => "invoke_indirect",
},
Terminator::Check { .. } => "check",
Terminator::Resume { .. } => "resume",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn test_switch_successors_list_cases_then_default() {
let t = Terminator::Switch {
value: Value::from_u32(0),
cases: vec![
SwitchCase {
value: 1,
dest: BlockCall::new(Block::from_u32(1), &[]),
},
SwitchCase {
value: 2,
dest: BlockCall::new(Block::from_u32(2), &[]),
},
],
default: BlockCall::new(Block::from_u32(3), &[]),
};
let order: Vec<usize> = t.successors().map(|c| c.block.index()).collect();
assert_eq!(order, [1, 2, 3]);
}
#[test]
fn test_terminator_operands_include_edge_values_only() {
let t = Terminator::Invoke {
call: Box::new(Call::direct(FuncId::from_u32(0), &[Value::from_u32(1)])),
normal: BlockCall::with_args(
Block::from_u32(1),
&[BlockArg::Result(0), BlockArg::Value(Value::from_u32(2))],
),
unwind: BlockCall::with_args(Block::from_u32(2), &[BlockArg::Exception]),
};
let mut seen = Vec::new();
t.for_each_operand(|v| seen.push(v.index()));
assert_eq!(seen, [1, 2]);
}
#[test]
fn test_attrs_display_and_parse() {
let a = Attrs::NOGC | Attrs::NORETURN;
assert_eq!(a.to_string(), "[noreturn, nogc]");
assert_eq!(Attrs::from_name("inline"), Some(Attrs::INLINE));
assert_eq!(Attrs::from_name("bogus"), None);
}
#[test]
fn test_mem_order_names_round_trip() {
for o in MemOrder::ALL {
assert_eq!(MemOrder::from_name(o.name()), Some(o));
}
for o in RmwOp::ALL {
assert_eq!(RmwOp::from_name(o.name()), Some(o));
}
}
#[test]
fn test_inst_data_stays_small() {
assert!(core::mem::size_of::<InstData>() <= 24);
}
#[test]
fn test_block_arg_display() {
assert_eq!(BlockArg::Exception.to_string(), "exn");
assert_eq!(BlockArg::ErrorCode.to_string(), "err");
assert_eq!(BlockArg::Value(Value::from_u32(2)).to_string(), "v2");
}
}