Files
wasmtime/cranelift/codegen/src/isa/x64/inst/args.rs
2020-07-30 14:16:12 -07:00

852 lines
23 KiB
Rust

//! Instruction operand sub-components (aka "parts"): definitions and printing.
use std::fmt;
use std::string::{String, ToString};
use regalloc::{RealRegUniverse, Reg, RegClass, RegUsageCollector, RegUsageMapper};
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::machinst::*;
use super::{
regs::{self, show_ireg_sized},
EmitState,
};
/// A possible addressing mode (amode) that can be used in instructions.
/// These denote a 64-bit value only.
#[derive(Clone)]
pub enum Amode {
/// Immediate sign-extended and a Register.
ImmReg { simm32: u32, base: Reg },
/// sign-extend-32-to-64(Immediate) + Register1 + (Register2 << Shift)
ImmRegRegShift {
simm32: u32,
base: Reg,
index: Reg,
shift: u8, /* 0 .. 3 only */
},
/// sign-extend-32-to-64(Immediate) + RIP (instruction pointer).
/// To wit: not supported in 32-bits mode.
RipRelative { target: BranchTarget },
}
impl Amode {
pub(crate) fn imm_reg(simm32: u32, base: Reg) -> Self {
debug_assert!(base.get_class() == RegClass::I64);
Self::ImmReg { simm32, base }
}
pub(crate) fn imm_reg_reg_shift(simm32: u32, base: Reg, index: Reg, shift: u8) -> Self {
debug_assert!(base.get_class() == RegClass::I64);
debug_assert!(index.get_class() == RegClass::I64);
debug_assert!(shift <= 3);
Self::ImmRegRegShift {
simm32,
base,
index,
shift,
}
}
pub(crate) fn rip_relative(target: BranchTarget) -> Self {
Self::RipRelative { target }
}
/// Add the regs mentioned by `self` to `collector`.
pub(crate) fn get_regs_as_uses(&self, collector: &mut RegUsageCollector) {
match self {
Amode::ImmReg { base, .. } => {
collector.add_use(*base);
}
Amode::ImmRegRegShift { base, index, .. } => {
collector.add_use(*base);
collector.add_use(*index);
}
Amode::RipRelative { .. } => {
// RIP isn't involved in regalloc.
}
}
}
}
impl ShowWithRRU for Amode {
fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String {
match self {
Amode::ImmReg { simm32, base } => {
format!("{}({})", *simm32 as i32, base.show_rru(mb_rru))
}
Amode::ImmRegRegShift {
simm32,
base,
index,
shift,
} => format!(
"{}({},{},{})",
*simm32 as i32,
base.show_rru(mb_rru),
index.show_rru(mb_rru),
1 << shift
),
Amode::RipRelative { ref target } => format!(
"{}(%rip)",
match target {
BranchTarget::Label(label) => format!("label{}", label.get()),
BranchTarget::ResolvedOffset(offset) => offset.to_string(),
}
),
}
}
}
/// A Memory Address. These denote a 64-bit value only.
/// Used for usual addressing modes as well as addressing modes used during compilation, when the
/// moving SP offset is not known.
#[derive(Clone)]
pub enum SyntheticAmode {
/// A real amode.
Real(Amode),
/// A (virtual) offset to the "nominal SP" value, which will be recomputed as we push and pop
/// within the function.
NominalSPOffset { simm32: u32 },
}
impl SyntheticAmode {
pub(crate) fn nominal_sp_offset(simm32: u32) -> Self {
SyntheticAmode::NominalSPOffset { simm32 }
}
/// Add the regs mentioned by `self` to `collector`.
pub(crate) fn get_regs_as_uses(&self, collector: &mut RegUsageCollector) {
match self {
SyntheticAmode::Real(addr) => addr.get_regs_as_uses(collector),
SyntheticAmode::NominalSPOffset { .. } => {
// Nothing to do; the base is SP and isn't involved in regalloc.
}
}
}
pub(crate) fn map_uses<RUM: RegUsageMapper>(&mut self, map: &RUM) {
match self {
SyntheticAmode::Real(addr) => addr.map_uses(map),
SyntheticAmode::NominalSPOffset { .. } => {
// Nothing to do.
}
}
}
pub(crate) fn finalize(&self, state: &mut EmitState) -> Amode {
match self {
SyntheticAmode::Real(addr) => addr.clone(),
SyntheticAmode::NominalSPOffset { simm32 } => {
let off = *simm32 as i64 + state.virtual_sp_offset;
// TODO will require a sequence of add etc.
assert!(
off <= u32::max_value() as i64,
"amode finalize: add sequence NYI"
);
Amode::imm_reg(off as u32, regs::rsp())
}
}
}
}
impl Into<SyntheticAmode> for Amode {
fn into(self) -> SyntheticAmode {
SyntheticAmode::Real(self)
}
}
impl ShowWithRRU for SyntheticAmode {
fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String {
match self {
SyntheticAmode::Real(addr) => addr.show_rru(mb_rru),
SyntheticAmode::NominalSPOffset { simm32 } => {
format!("rsp({} + virtual offset)", *simm32 as i32)
}
}
}
}
/// An operand which is either an integer Register, a value in Memory or an Immediate. This can
/// denote an 8, 16, 32 or 64 bit value. For the Immediate form, in the 8- and 16-bit case, only
/// the lower 8 or 16 bits of `simm32` is relevant. In the 64-bit case, the value denoted by
/// `simm32` is its sign-extension out to 64 bits.
#[derive(Clone)]
pub enum RegMemImm {
Reg { reg: Reg },
Mem { addr: SyntheticAmode },
Imm { simm32: u32 },
}
impl RegMemImm {
pub(crate) fn reg(reg: Reg) -> Self {
debug_assert!(reg.get_class() == RegClass::I64);
Self::Reg { reg }
}
pub(crate) fn mem(addr: impl Into<SyntheticAmode>) -> Self {
Self::Mem { addr: addr.into() }
}
pub(crate) fn imm(simm32: u32) -> Self {
Self::Imm { simm32 }
}
/// Asserts that in register mode, the reg class is the one that's expected.
pub(crate) fn assert_regclass_is(&self, expected_reg_class: RegClass) {
if let Self::Reg { reg } = self {
debug_assert_eq!(reg.get_class(), expected_reg_class);
}
}
/// Add the regs mentioned by `self` to `collector`.
pub(crate) fn get_regs_as_uses(&self, collector: &mut RegUsageCollector) {
match self {
Self::Reg { reg } => collector.add_use(*reg),
Self::Mem { addr } => addr.get_regs_as_uses(collector),
Self::Imm { .. } => {}
}
}
}
impl ShowWithRRU for RegMemImm {
fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String {
self.show_rru_sized(mb_rru, 8)
}
fn show_rru_sized(&self, mb_rru: Option<&RealRegUniverse>, size: u8) -> String {
match self {
Self::Reg { reg } => show_ireg_sized(*reg, mb_rru, size),
Self::Mem { addr } => addr.show_rru(mb_rru),
Self::Imm { simm32 } => format!("${}", *simm32 as i32),
}
}
}
/// An operand which is either an integer Register or a value in Memory. This can denote an 8, 16,
/// 32, 64, or 128 bit value.
#[derive(Clone)]
pub enum RegMem {
Reg { reg: Reg },
Mem { addr: SyntheticAmode },
}
impl RegMem {
pub(crate) fn reg(reg: Reg) -> Self {
debug_assert!(reg.get_class() == RegClass::I64 || reg.get_class() == RegClass::V128);
Self::Reg { reg }
}
pub(crate) fn mem(addr: impl Into<SyntheticAmode>) -> Self {
Self::Mem { addr: addr.into() }
}
/// Asserts that in register mode, the reg class is the one that's expected.
pub(crate) fn assert_regclass_is(&self, expected_reg_class: RegClass) {
if let Self::Reg { reg } = self {
debug_assert_eq!(reg.get_class(), expected_reg_class);
}
}
/// Add the regs mentioned by `self` to `collector`.
pub(crate) fn get_regs_as_uses(&self, collector: &mut RegUsageCollector) {
match self {
RegMem::Reg { reg } => collector.add_use(*reg),
RegMem::Mem { addr, .. } => addr.get_regs_as_uses(collector),
}
}
}
impl ShowWithRRU for RegMem {
fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String {
self.show_rru_sized(mb_rru, 8)
}
fn show_rru_sized(&self, mb_rru: Option<&RealRegUniverse>, size: u8) -> String {
match self {
RegMem::Reg { reg } => show_ireg_sized(*reg, mb_rru, size),
RegMem::Mem { addr, .. } => addr.show_rru(mb_rru),
}
}
}
/// Some basic ALU operations. TODO: maybe add Adc, Sbb.
#[derive(Clone, PartialEq)]
pub enum AluRmiROpcode {
Add,
Sub,
And,
Or,
Xor,
/// The signless, non-extending (N x N -> N, for N in {32,64}) variant.
Mul,
}
impl fmt::Debug for AluRmiROpcode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
AluRmiROpcode::Add => "add",
AluRmiROpcode::Sub => "sub",
AluRmiROpcode::And => "and",
AluRmiROpcode::Or => "or",
AluRmiROpcode::Xor => "xor",
AluRmiROpcode::Mul => "imul",
};
write!(fmt, "{}", name)
}
}
impl fmt::Display for AluRmiROpcode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[derive(Clone, PartialEq)]
pub enum UnaryRmROpcode {
/// Bit-scan reverse.
Bsr,
/// Bit-scan forward.
Bsf,
}
impl fmt::Debug for UnaryRmROpcode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
UnaryRmROpcode::Bsr => write!(fmt, "bsr"),
UnaryRmROpcode::Bsf => write!(fmt, "bsf"),
}
}
}
impl fmt::Display for UnaryRmROpcode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
pub(crate) enum InstructionSet {
SSE,
SSE2,
SSE41,
}
/// Some SSE operations requiring 2 operands r/m and r.
#[derive(Clone, Copy, PartialEq)]
pub enum SseOpcode {
Addss,
Addsd,
Andps,
Andpd,
Andnps,
Andnpd,
Comiss,
Comisd,
Cmpss,
Cmpsd,
Cvtsd2ss,
Cvtsd2si,
Cvtsi2ss,
Cvtsi2sd,
Cvtss2si,
Cvtss2sd,
Cvttss2si,
Cvttsd2si,
Divss,
Divsd,
Insertps,
Maxss,
Maxsd,
Minss,
Minsd,
Movaps,
Movapd,
Movd,
Movq,
Movss,
Movsd,
Movups,
Movupd,
Mulps,
Mulpd,
Mulss,
Mulsd,
Orps,
Orpd,
Rcpss,
Roundss,
Roundsd,
Rsqrtss,
Sqrtss,
Sqrtsd,
Subss,
Subsd,
Ucomiss,
Ucomisd,
Xorps,
Xorpd,
}
impl SseOpcode {
/// Which `InstructionSet` is the first supporting this opcode?
pub(crate) fn available_from(&self) -> InstructionSet {
use InstructionSet::*;
match self {
SseOpcode::Addss
| SseOpcode::Andps
| SseOpcode::Andnps
| SseOpcode::Cvtsi2ss
| SseOpcode::Cvtss2si
| SseOpcode::Cvttss2si
| SseOpcode::Divss
| SseOpcode::Maxss
| SseOpcode::Minss
| SseOpcode::Movaps
| SseOpcode::Movss
| SseOpcode::Movups
| SseOpcode::Mulps
| SseOpcode::Mulss
| SseOpcode::Orps
| SseOpcode::Rcpss
| SseOpcode::Rsqrtss
| SseOpcode::Subss
| SseOpcode::Ucomiss
| SseOpcode::Sqrtss
| SseOpcode::Comiss
| SseOpcode::Cmpss
| SseOpcode::Xorps => SSE,
SseOpcode::Addsd
| SseOpcode::Andpd
| SseOpcode::Andnpd
| SseOpcode::Cvtsd2ss
| SseOpcode::Cvtsd2si
| SseOpcode::Cvtsi2sd
| SseOpcode::Cvtss2sd
| SseOpcode::Cvttsd2si
| SseOpcode::Divsd
| SseOpcode::Maxsd
| SseOpcode::Minsd
| SseOpcode::Movapd
| SseOpcode::Movd
| SseOpcode::Movq
| SseOpcode::Movsd
| SseOpcode::Movupd
| SseOpcode::Mulpd
| SseOpcode::Mulsd
| SseOpcode::Orpd
| SseOpcode::Sqrtsd
| SseOpcode::Subsd
| SseOpcode::Ucomisd
| SseOpcode::Comisd
| SseOpcode::Cmpsd
| SseOpcode::Xorpd => SSE2,
SseOpcode::Insertps | SseOpcode::Roundss | SseOpcode::Roundsd => SSE41,
}
}
/// Returns the src operand size for an instruction.
pub(crate) fn src_size(&self) -> u8 {
match self {
SseOpcode::Movd => 4,
_ => 8,
}
}
}
impl fmt::Debug for SseOpcode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
SseOpcode::Addss => "addss",
SseOpcode::Addsd => "addsd",
SseOpcode::Andpd => "andpd",
SseOpcode::Andps => "andps",
SseOpcode::Andnps => "andnps",
SseOpcode::Andnpd => "andnpd",
SseOpcode::Comiss => "comiss",
SseOpcode::Comisd => "comisd",
SseOpcode::Cvtsd2ss => "cvtsd2ss",
SseOpcode::Cvtsd2si => "cvtsd2si",
SseOpcode::Cvtsi2ss => "cvtsi2ss",
SseOpcode::Cvtsi2sd => "cvtsi2sd",
SseOpcode::Cvtss2si => "cvtss2si",
SseOpcode::Cvtss2sd => "cvtss2sd",
SseOpcode::Cvttss2si => "cvttss2si",
SseOpcode::Cvttsd2si => "cvttsd2si",
SseOpcode::Divss => "divss",
SseOpcode::Divsd => "divsd",
SseOpcode::Maxss => "maxss",
SseOpcode::Maxsd => "maxsd",
SseOpcode::Minss => "minss",
SseOpcode::Minsd => "minsd",
SseOpcode::Movaps => "movaps",
SseOpcode::Movapd => "movapd",
SseOpcode::Movd => "movd",
SseOpcode::Movq => "movq",
SseOpcode::Movss => "movss",
SseOpcode::Movsd => "movsd",
SseOpcode::Movups => "movups",
SseOpcode::Movupd => "movupd",
SseOpcode::Mulps => "mulps",
SseOpcode::Mulpd => "mulpd",
SseOpcode::Mulss => "mulss",
SseOpcode::Mulsd => "mulsd",
SseOpcode::Orpd => "orpd",
SseOpcode::Orps => "orps",
SseOpcode::Rcpss => "rcpss",
SseOpcode::Roundss => "roundss",
SseOpcode::Roundsd => "roundsd",
SseOpcode::Rsqrtss => "rsqrtss",
SseOpcode::Sqrtss => "sqrtss",
SseOpcode::Sqrtsd => "sqrtsd",
SseOpcode::Subss => "subss",
SseOpcode::Subsd => "subsd",
SseOpcode::Ucomiss => "ucomiss",
SseOpcode::Ucomisd => "ucomisd",
SseOpcode::Cmpss => "cmpss",
SseOpcode::Cmpsd => "cmpsd",
SseOpcode::Insertps => "insertps",
SseOpcode::Xorps => "xorps",
SseOpcode::Xorpd => "xorpd",
};
write!(fmt, "{}", name)
}
}
impl fmt::Display for SseOpcode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
/// These indicate ways of extending (widening) a value, using the Intel
/// naming: B(yte) = u8, W(ord) = u16, L(ong)word = u32, Q(uad)word = u64
#[derive(Clone, PartialEq)]
pub enum ExtMode {
/// Byte -> Longword.
BL,
/// Byte -> Quadword.
BQ,
/// Word -> Longword.
WL,
/// Word -> Quadword.
WQ,
/// Longword -> Quadword.
LQ,
}
impl ExtMode {
pub(crate) fn src_size(&self) -> u8 {
match self {
ExtMode::BL | ExtMode::BQ => 1,
ExtMode::WL | ExtMode::WQ => 2,
ExtMode::LQ => 4,
}
}
pub(crate) fn dst_size(&self) -> u8 {
match self {
ExtMode::BL | ExtMode::WL => 4,
ExtMode::BQ | ExtMode::WQ | ExtMode::LQ => 8,
}
}
}
impl fmt::Debug for ExtMode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
ExtMode::BL => "bl",
ExtMode::BQ => "bq",
ExtMode::WL => "wl",
ExtMode::WQ => "wq",
ExtMode::LQ => "lq",
};
write!(fmt, "{}", name)
}
}
impl fmt::Display for ExtMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
/// These indicate the form of a scalar shift/rotate: left, signed right, unsigned right.
#[derive(Clone)]
pub enum ShiftKind {
ShiftLeft,
/// Inserts zeros in the most significant bits.
ShiftRightLogical,
/// Replicates the sign bit in the most significant bits.
ShiftRightArithmetic,
RotateLeft,
RotateRight,
}
impl fmt::Debug for ShiftKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
ShiftKind::ShiftLeft => "shl",
ShiftKind::ShiftRightLogical => "shr",
ShiftKind::ShiftRightArithmetic => "sar",
ShiftKind::RotateLeft => "rol",
ShiftKind::RotateRight => "ror",
};
write!(fmt, "{}", name)
}
}
impl fmt::Display for ShiftKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
/// What kind of division or remainer instruction this is?
#[derive(Clone)]
pub enum DivOrRemKind {
SignedDiv,
UnsignedDiv,
SignedRem,
UnsignedRem,
}
impl DivOrRemKind {
pub(crate) fn is_signed(&self) -> bool {
match self {
DivOrRemKind::SignedDiv | DivOrRemKind::SignedRem => true,
_ => false,
}
}
pub(crate) fn is_div(&self) -> bool {
match self {
DivOrRemKind::SignedDiv | DivOrRemKind::UnsignedDiv => true,
_ => false,
}
}
}
/// These indicate condition code tests. Not all are represented since not all are useful in
/// compiler-generated code.
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum CC {
/// overflow
O = 0,
/// no overflow
NO = 1,
/// < unsigned
B = 2,
/// >= unsigned
NB = 3,
/// zero
Z = 4,
/// not-zero
NZ = 5,
/// <= unsigned
BE = 6,
/// > unsigned
NBE = 7,
/// negative
S = 8,
/// not-negative
NS = 9,
/// < signed
L = 12,
/// >= signed
NL = 13,
/// <= signed
LE = 14,
/// > signed
NLE = 15,
/// parity
P = 10,
/// not parity
NP = 11,
}
impl CC {
pub(crate) fn from_intcc(intcc: IntCC) -> Self {
match intcc {
IntCC::Equal => CC::Z,
IntCC::NotEqual => CC::NZ,
IntCC::SignedGreaterThanOrEqual => CC::NL,
IntCC::SignedGreaterThan => CC::NLE,
IntCC::SignedLessThanOrEqual => CC::LE,
IntCC::SignedLessThan => CC::L,
IntCC::UnsignedGreaterThanOrEqual => CC::NB,
IntCC::UnsignedGreaterThan => CC::NBE,
IntCC::UnsignedLessThanOrEqual => CC::BE,
IntCC::UnsignedLessThan => CC::B,
IntCC::Overflow => CC::O,
IntCC::NotOverflow => CC::NO,
}
}
pub(crate) fn invert(&self) -> Self {
match self {
CC::O => CC::NO,
CC::NO => CC::O,
CC::B => CC::NB,
CC::NB => CC::B,
CC::Z => CC::NZ,
CC::NZ => CC::Z,
CC::BE => CC::NBE,
CC::NBE => CC::BE,
CC::S => CC::NS,
CC::NS => CC::S,
CC::L => CC::NL,
CC::NL => CC::L,
CC::LE => CC::NLE,
CC::NLE => CC::LE,
CC::P => CC::NP,
CC::NP => CC::P,
}
}
pub(crate) fn from_floatcc(floatcc: FloatCC) -> Self {
match floatcc {
FloatCC::Ordered => CC::NP,
FloatCC::Unordered => CC::P,
// Alias for NE
FloatCC::NotEqual | FloatCC::OrderedNotEqual => CC::NZ,
// Alias for E
FloatCC::UnorderedOrEqual => CC::Z,
// Alias for A
FloatCC::GreaterThan => CC::NBE,
// Alias for AE
FloatCC::GreaterThanOrEqual => CC::NB,
FloatCC::UnorderedOrLessThan => CC::B,
FloatCC::UnorderedOrLessThanOrEqual => CC::BE,
FloatCC::Equal
| FloatCC::LessThan
| FloatCC::LessThanOrEqual
| FloatCC::UnorderedOrGreaterThan
| FloatCC::UnorderedOrGreaterThanOrEqual => {
panic!("No single condition code to guarantee ordered. Treat as special case.")
}
}
}
pub(crate) fn get_enc(self) -> u8 {
self as u8
}
}
impl fmt::Debug for CC {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
CC::O => "o",
CC::NO => "no",
CC::B => "b",
CC::NB => "nb",
CC::Z => "z",
CC::NZ => "nz",
CC::BE => "be",
CC::NBE => "nbe",
CC::S => "s",
CC::NS => "ns",
CC::L => "l",
CC::NL => "nl",
CC::LE => "le",
CC::NLE => "nle",
CC::P => "p",
CC::NP => "np",
};
write!(fmt, "{}", name)
}
}
impl fmt::Display for CC {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
/// A branch target. Either unresolved (basic-block index) or resolved (offset
/// from end of current instruction).
#[derive(Clone, Copy, Debug)]
pub enum BranchTarget {
/// An unresolved reference to a MachLabel.
Label(MachLabel),
/// A resolved reference to another instruction, in bytes.
ResolvedOffset(isize),
}
impl ShowWithRRU for BranchTarget {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
match self {
BranchTarget::Label(l) => format!("{:?}", l),
BranchTarget::ResolvedOffset(offs) => format!("(offset {})", offs),
}
}
}
impl BranchTarget {
/// Get the label.
pub fn as_label(&self) -> Option<MachLabel> {
match self {
&BranchTarget::Label(l) => Some(l),
_ => None,
}
}
/// Get the offset as a signed 32 bit byte offset. This returns the
/// offset in bytes between the first byte of the source and the first
/// byte of the target. It does not take into account the Intel-specific
/// rule that a branch offset is encoded as relative to the start of the
/// following instruction. That is a problem for the emitter to deal
/// with. If a label, returns zero.
pub fn as_offset32_or_zero(&self) -> i32 {
match self {
&BranchTarget::ResolvedOffset(off) => {
// Leave a bit of slack so that the emitter is guaranteed to
// be able to add the length of the jump instruction encoding
// to this value and still have a value in signed-32 range.
assert!(off >= -0x7FFF_FF00 && off <= 0x7FFF_FF00);
off as i32
}
_ => 0,
}
}
}
/// An operand's size in bits.
#[derive(Clone, Copy, PartialEq)]
pub enum OperandSize {
Size32,
Size64,
}
impl OperandSize {
pub(crate) fn to_bytes(&self) -> u8 {
match self {
Self::Size32 => 4,
Self::Size64 => 8,
}
}
pub(crate) fn to_bits(&self) -> u8 {
match self {
Self::Size32 => 32,
Self::Size64 => 64,
}
}
}