use core::num::NonZeroU32;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(transparent)]
pub struct Label(NonZeroU32);
impl Label {
#[inline]
pub fn raw(self) -> u32 {
self.0.get() - 1
}
#[inline]
pub fn from_raw(value: u32) -> Self {
Label(NonZeroU32::new(value + 1).unwrap())
}
}
impl core::fmt::Display for Label {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
fmt.write_fmt(core::format_args!("<{}>", self.0))
}
}
#[derive(Copy, Clone)]
pub struct Instruction<T> {
pub(crate) instruction: T,
pub(crate) bytes: InstBuf,
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
pub(crate) fixup: Option<(Label, FixupKind)>,
}
impl<T> core::fmt::Debug for Instruction<T>
where
T: core::fmt::Debug,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
self.instruction.fmt(fmt)
}
}
impl<T> core::fmt::Display for Instruction<T>
where
T: core::fmt::Display,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
self.instruction.fmt(fmt)
}
}
impl<T> Instruction<T> {
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
self.bytes.len()
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub(crate) struct FixupKind(pub u32);
impl FixupKind {
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
#[inline]
pub const fn offset(self) -> u32 {
(self.0 >> 24) & 0b11
}
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
#[inline]
pub const fn length(self) -> u32 {
self.0 >> 28
}
#[inline]
pub const fn new_1(opcode: u32, length: u32) -> Self {
FixupKind((1 << 24) | (length << 28) | opcode)
}
#[inline]
pub const fn new_2(opcode: [u32; 2], length: u32) -> Self {
let opcode = opcode[0] | (opcode[1] << 8);
FixupKind((2 << 24) | (length << 28) | opcode)
}
#[inline]
pub const fn new_3(opcode: [u32; 3], length: u32) -> Self {
let opcode = opcode[0] | (opcode[1] << 8) | (opcode[2] << 16);
FixupKind((3 << 24) | (length << 28) | opcode)
}
}
const MAXIMUM_INSTRUCTION_SIZE: usize = 16;
#[derive(Copy, Clone)]
pub struct InstBuf {
out: u128,
length: u32,
}
#[allow(clippy::new_without_default)]
impl InstBuf {
#[inline]
pub fn new() -> Self {
Self { out: 0, length: 0 }
}
#[inline]
pub fn len(&self) -> usize {
(self.length >> 3) as usize
}
#[inline]
pub fn append(&mut self, byte: u8) {
self.out |= u128::from(byte).wrapping_shl(self.length);
self.length += 8;
}
#[inline]
pub fn append_packed_bytes(&mut self, value: u32, length: u32) {
self.out |= u128::from(value).wrapping_shl(self.length);
self.length += length;
}
#[cfg(feature = "alloc")]
#[inline]
unsafe fn encode_into_raw(self, output: *mut u8) {
core::ptr::write_unaligned(output.cast::<u64>(), u64::from_le(self.out as u64));
core::ptr::write_unaligned(output.add(8).cast::<u64>(), u64::from_le((self.out >> 64) as u64));
}
#[cfg(feature = "alloc")]
#[allow(clippy::debug_assert_with_mut_call)]
#[inline]
pub unsafe fn encode_into_vec_unsafe(self, output: &mut Vec<u8>) {
debug_assert!(output.spare_capacity_mut().len() >= MAXIMUM_INSTRUCTION_SIZE);
self.encode_into_raw(output.spare_capacity_mut().as_mut_ptr().cast());
let new_length = output.len() + (self.length as usize >> 3);
output.set_len(new_length);
}
#[cfg(feature = "alloc")]
#[cold]
#[inline(never)]
fn reserve_impl(output: &mut Vec<u8>, length: usize) {
output.reserve(length);
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn reserve_const<const INSTRUCTIONS: usize>(output: &mut Vec<u8>) {
Self::reserve(output, INSTRUCTIONS);
}
#[cfg(feature = "alloc")]
#[inline(always)]
pub fn reserve(output: &mut Vec<u8>, count: usize) {
let count = count.checked_mul(MAXIMUM_INSTRUCTION_SIZE).unwrap();
if output.spare_capacity_mut().len() < count {
Self::reserve_impl(output, count);
if output.spare_capacity_mut().len() < count {
unsafe {
core::hint::unreachable_unchecked();
}
}
}
}
#[inline]
pub fn from_array<const N: usize>(array: [u8; N]) -> Self {
if N > MAXIMUM_INSTRUCTION_SIZE {
panic!();
}
let mut out = Self::new();
for value in array {
out.append(value);
}
out
}
#[cfg(feature = "alloc")]
pub fn to_vec(self) -> Vec<u8> {
let mut vec = Vec::with_capacity(MAXIMUM_INSTRUCTION_SIZE);
unsafe {
self.encode_into_vec_unsafe(&mut vec);
}
vec
}
}
#[cfg(feature = "alloc")]
#[test]
fn test_inst_buf() {
assert_eq!(InstBuf::from_array([0x01]).to_vec(), [0x01]);
assert_eq!(InstBuf::from_array([0x01, 0x02]).to_vec(), [0x01, 0x02]);
assert_eq!(InstBuf::from_array([0x01, 0x02, 0x03]).to_vec(), [0x01, 0x02, 0x03]);
assert_eq!(InstBuf::from_array([0x01, 0x02, 0x03, 0x04]).to_vec(), [0x01, 0x02, 0x03, 0x04]);
assert_eq!(
InstBuf::from_array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]).to_vec(),
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
);
assert_eq!(
InstBuf::from_array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]).to_vec(),
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]
);
assert_eq!(
InstBuf::from_array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A]).to_vec(),
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A]
);
let mut buf = InstBuf::from_array([0x01]);
assert_eq!(buf.to_vec(), [0x01]);
buf.append_packed_bytes(0x05040302, 32);
assert_eq!(buf.to_vec(), [0x01, 0x02, 0x03, 0x04, 0x05]);
buf.append_packed_bytes(0x09080706, 32);
assert_eq!(buf.to_vec(), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]);
let mut buf = InstBuf::from_array([0x01]);
assert_eq!(buf.to_vec(), [0x01]);
buf.append_packed_bytes(0x0302, 16);
assert_eq!(buf.to_vec(), [0x01, 0x02, 0x03]);
buf.append_packed_bytes(0x0504, 16);
assert_eq!(buf.to_vec(), [0x01, 0x02, 0x03, 0x04, 0x05]);
buf.append_packed_bytes(0x0706, 16);
assert_eq!(buf.to_vec(), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
buf.append_packed_bytes(0x0908, 16);
assert_eq!(buf.to_vec(), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]);
}