use super::{Reset, ReusableAllocations};
use crate::{
Engine,
Error,
core::FuelCostsProvider,
engine::{
TranslationError,
executor::op_code_to_handler,
translator::{
comparator::UpdateBranchOffset,
func::{
LabelRef,
LabelRegistry,
labels::{Label, ResolvedLabelUser},
},
},
},
ir::{self, BlockFuel, BranchOffset, Encode as _, Op, OpCode},
};
use alloc::vec::Vec;
use core::{cmp, fmt, marker::PhantomData, mem};
type FuelUsed = u64;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct BytePos(usize);
impl From<usize> for BytePos {
fn from(index: usize) -> Self {
Self(index)
}
}
impl From<BytePos> for usize {
fn from(pos: BytePos) -> Self {
pos.0
}
}
pub struct Pos<T> {
value: BytePos,
marker: PhantomData<fn() -> T>,
}
impl<T> From<BytePos> for Pos<T> {
fn from(value: BytePos) -> Self {
Self {
value,
marker: PhantomData,
}
}
}
impl<T> From<Pos<T>> for BytePos {
fn from(pos: Pos<T>) -> Self {
pos.value
}
}
impl<T> Copy for Pos<T> {}
impl<T> Clone for Pos<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> PartialEq for Pos<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> Eq for Pos<T> {}
impl<T> PartialOrd for Pos<T> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Pos<T> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<T> fmt::Debug for Pos<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pos")
.field("value", &self.value)
.field("marker", &self.marker)
.finish()
}
}
#[derive(Debug, Default)]
pub struct EncodedOps {
buffer: Vec<u8>,
temp: Option<ReportingPos>,
}
#[derive(Debug)]
enum ReportingPos {
BranchOffset(Pos<BranchOffset>),
BlockFuel(Pos<BlockFuel>),
}
impl Reset for EncodedOps {
fn reset(&mut self) {
self.buffer.clear();
}
}
impl EncodedOps {
#[must_use]
fn next_pos(&self) -> BytePos {
BytePos::from(self.buffer.len())
}
#[must_use]
fn take_reporting_pos(&mut self) -> Option<ReportingPos> {
self.temp.take()
}
}
impl ir::Encoder for EncodedOps {
type Pos = BytePos;
type Error = TranslationError;
fn write_bytes(&mut self, bytes: &[u8]) -> Result<Self::Pos, Self::Error> {
let pos = self.buffer.len();
if self.buffer.try_reserve(bytes.len()).is_err() {
return Err(TranslationError::OutOfSystemMemory);
}
self.buffer.extend(bytes);
Ok(BytePos::from(pos))
}
fn encode_op_code(&mut self, code: OpCode) -> Result<Self::Pos, Self::Error> {
encode_op_code(self, code)
}
fn branch_offset(
&mut self,
pos: Self::Pos,
_branch_offset: BranchOffset,
) -> Result<(), Self::Error> {
debug_assert!(self.temp.is_none());
self.temp = Some(ReportingPos::BranchOffset(pos.into()));
Ok(())
}
fn block_fuel(
&mut self,
pos: Self::Pos,
_block_fuel: ir::BlockFuel,
) -> Result<(), Self::Error> {
debug_assert!(self.temp.is_none());
self.temp = Some(ReportingPos::BlockFuel(pos.into()));
Ok(())
}
}
#[derive(Debug, Default)]
pub struct OpEncoder {
staged: Option<StagedOp>,
fuel_costs: Option<FuelCostsProvider>,
ops: EncodedOps,
labels: LabelRegistry,
}
#[derive(Debug, Copy, Clone)]
pub struct StagedOp {
op: Op,
fuel: Option<(Pos<BlockFuel>, FuelUsed)>,
}
impl StagedOp {
pub fn new(op: Op, fuel: Option<(Pos<BlockFuel>, FuelUsed)>) -> Self {
Self { op, fuel }
}
pub fn replace(&mut self, op: Op) -> Op {
mem::replace(&mut self.op, op)
}
}
impl ReusableAllocations for OpEncoder {
type Allocations = OpEncoderAllocations;
fn into_allocations(self) -> Self::Allocations {
Self::Allocations {
ops: self.ops,
labels: self.labels,
}
}
}
#[derive(Debug, Default)]
pub struct OpEncoderAllocations {
ops: EncodedOps,
labels: LabelRegistry,
}
impl Reset for OpEncoderAllocations {
fn reset(&mut self) {
self.ops.reset();
self.labels.reset();
}
}
impl OpEncoder {
pub fn new(engine: &Engine, alloc: OpEncoderAllocations) -> Self {
let config = engine.config();
let fuel_costs = config
.get_consume_fuel()
.then(|| config.fuel_costs())
.cloned();
Self {
staged: None,
fuel_costs,
ops: alloc.ops,
labels: alloc.labels,
}
}
pub fn new_label(&mut self) -> LabelRef {
self.labels.new_label()
}
pub fn pin_label(&mut self, lref: LabelRef) -> Result<(), Error> {
self.try_encode_staged()?;
let next_pos = Pos::from(self.ops.next_pos());
self.labels.pin_label(lref, next_pos);
Ok(())
}
pub fn pin_label_if_unpinned(&mut self, lref: LabelRef) -> Result<(), Error> {
self.try_encode_staged()?;
let next_pos = Pos::from(self.ops.next_pos());
self.labels.pin_label_if_unpinned(lref, next_pos);
Ok(())
}
fn try_resolve_label(&mut self, lref: LabelRef) -> Result<BranchOffset, Error> {
assert!(self.staged.is_none());
let src = self.ops.next_pos();
let offset = match self.labels.get_label(lref) {
Label::Pinned(dst) => trace_branch_offset(src, dst)?,
Label::Unpinned => BranchOffset::uninit(),
};
Ok(offset)
}
pub fn peek_staged(&self) -> Option<Op> {
self.staged.map(|staged| staged.op)
}
pub fn stage(
&mut self,
new_staged: Op,
fuel_op: Option<Pos<BlockFuel>>,
fuel_selector: impl FuelCostsSelector,
) -> Result<Pos<Op>, Error> {
let fuel = match (fuel_op, &self.fuel_costs) {
(None, None) => None,
(Some(fuel_op), Some(fuel_costs)) => Some((fuel_op, fuel_selector.select(fuel_costs))),
_ => unreachable!(),
};
let new_staged = StagedOp::new(new_staged, fuel);
if let Some(old_staged) = self.staged.replace(new_staged) {
self.encode_staged(old_staged)?;
}
Ok(Pos::from(self.ops.next_pos()))
}
pub fn try_encode_staged(&mut self) -> Result<(), Error> {
if let Some(staged) = self.staged.take() {
self.encode_staged(staged)?;
}
debug_assert!(self.staged.is_none());
Ok(())
}
fn encode_staged(&mut self, staged: StagedOp) -> Result<Pos<Op>, Error> {
if let Some((fuel_pos, fuel_used)) = staged.fuel {
self.bump_fuel_consumption_by(Some(fuel_pos), fuel_used)?;
}
let pos = self.encode_impl(staged.op)?;
debug_assert!(self.ops.temp.is_none());
Ok(pos)
}
pub fn drop_staged(&mut self) -> (Option<Pos<BlockFuel>>, FuelUsed) {
let Some(staged) = self.staged.take() else {
panic!("could not drop staged `Op` since there was none")
};
debug_assert!(self.staged.is_none());
let fuel_pos = staged.fuel.map(|(pos, _)| pos);
let fuel_used = staged.fuel.map(|(_, used)| used).unwrap_or(0);
(fuel_pos, fuel_used)
}
pub fn replace_staged(&mut self, new_staged: Op) -> Result<Pos<Op>, Error> {
let Some(staged) = self.staged.as_mut() else {
panic!("expected a staged `Op` but found `None`")
};
staged.replace(new_staged);
Ok(Pos::from(self.ops.next_pos()))
}
pub fn encode<T: ir::Encode>(
&mut self,
op: T,
fuel_pos: Option<Pos<BlockFuel>>,
fuel_selector: impl FuelCostsSelector,
) -> Result<Pos<T>, Error> {
self.try_encode_staged()?;
self.bump_fuel_consumption(fuel_pos, fuel_selector)?;
let pos = self.encode_impl(op)?;
debug_assert!(self.ops.take_reporting_pos().is_none());
debug_assert!(self.staged.is_none());
Ok(pos)
}
pub fn encode_consume_fuel(&mut self) -> Result<Option<Pos<BlockFuel>>, Error> {
let Some(fuel_costs) = &self.fuel_costs else {
return Ok(None);
};
let consumed_fuel = BlockFuel::from(fuel_costs.base());
self.try_encode_staged()?;
Op::consume_fuel(consumed_fuel).encode(&mut self.ops)?;
let Some(ReportingPos::BlockFuel(pos)) = self.ops.take_reporting_pos() else {
unreachable!("expected encoded `BlockFuel` entry but found none")
};
debug_assert!(self.staged.is_none());
Ok(Some(pos))
}
pub fn encode_branch<T>(
&mut self,
dst: LabelRef,
make_branch: impl FnOnce(BranchOffset) -> T,
fuel_pos: Option<Pos<BlockFuel>>,
fuel_selector: impl FuelCostsSelector,
) -> Result<(Pos<T>, Pos<BranchOffset>), Error>
where
T: ir::Encode + UpdateBranchOffset,
{
self.try_encode_staged()?;
self.bump_fuel_consumption(fuel_pos, fuel_selector)?;
let offset = self.try_resolve_label(dst)?;
let item = make_branch(offset);
let pos_item = self.encode_impl(item)?;
let pos_offset = match self.ops.take_reporting_pos() {
Some(ReportingPos::BranchOffset(pos)) => pos,
_ => panic!("missing encoded position for `BranchOffset`"),
};
if !self.labels.is_pinned(dst) {
self.labels
.new_user(dst, BytePos::from(pos_item), pos_offset);
}
debug_assert!(self.staged.is_none());
Ok((pos_item, pos_offset))
}
fn encode_impl<T>(&mut self, op: T) -> Result<Pos<T>, Error>
where
T: ir::Encode,
{
let pos = self.ops.next_pos();
op.encode(&mut self.ops)?;
Ok(Pos::from(pos))
}
fn bump_fuel_consumption(
&mut self,
fuel_pos: Option<Pos<BlockFuel>>,
fuel_selector: impl FuelCostsSelector,
) -> Result<(), Error> {
debug_assert_eq!(fuel_pos.is_some(), self.fuel_costs.is_some());
let fuel_used = self
.fuel_costs
.as_ref()
.map(|costs| fuel_selector.select(costs))
.unwrap_or(0);
if fuel_used == 0 {
return Ok(());
}
self.bump_fuel_consumption_by(fuel_pos, fuel_used)
}
fn bump_fuel_consumption_by(
&mut self,
fuel_pos: Option<Pos<BlockFuel>>,
delta: FuelUsed,
) -> Result<(), Error> {
debug_assert_eq!(fuel_pos.is_some(), self.fuel_costs.is_some());
let fuel_pos = match fuel_pos {
None => return Ok(()),
Some(fuel_pos) => fuel_pos,
};
self.ops
.update_encoded(fuel_pos, |mut fuel| -> Option<BlockFuel> {
fuel.bump_by(delta).ok()?;
Some(fuel)
});
Ok(())
}
pub fn encoded_ops(&self) -> &[u8] {
debug_assert!(self.staged.is_none());
&self.ops.buffer[..]
}
pub fn update_branch_offsets(&mut self) -> Result<(), Error> {
for user in self.labels.resolved_users() {
let ResolvedLabelUser { src, dst, pos } = user;
let offset = trace_branch_offset(src, dst)?;
self.ops.update_branch_offset(pos, offset)?;
}
Ok(())
}
}
struct UpdateEncodedError<T> {
kind: UpdateEncodedErrorKind,
marker: PhantomData<fn() -> T>,
}
impl<T> From<UpdateEncodedErrorKind> for UpdateEncodedError<T> {
fn from(kind: UpdateEncodedErrorKind) -> Self {
Self {
kind,
marker: PhantomData,
}
}
}
impl<T> Clone for UpdateEncodedError<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for UpdateEncodedError<T> {}
impl<T> fmt::Debug for UpdateEncodedError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UpdateEncodedError")
.field("kind", &self.kind)
.field("marker", &self.marker)
.finish()
}
}
impl<T> fmt::Display for UpdateEncodedError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self.kind {
UpdateEncodedErrorKind::BufferOutOfBounds => "buffer out of bounds",
UpdateEncodedErrorKind::FailedToDecode => "failed to decode",
UpdateEncodedErrorKind::FailedToEncode => "failed to encode",
UpdateEncodedErrorKind::FailedToUpdateEncoded => "failed to update encoded",
};
let type_name = core::any::type_name::<T>();
write!(f, "{message}: {type_name}")
}
}
#[derive(Debug, Copy, Clone)]
enum UpdateEncodedErrorKind {
BufferOutOfBounds,
FailedToDecode,
FailedToEncode,
FailedToUpdateEncoded,
}
impl EncodedOps {
pub fn update_branch_offset(
&mut self,
pos: Pos<BranchOffset>,
offset: BranchOffset,
) -> Result<(), Error> {
self.update_encoded(pos, |_| Some(offset));
Ok(())
}
fn update_encoded<T>(&mut self, pos: Pos<T>, f: impl FnOnce(T) -> Option<T>)
where
T: ir::Encode + ir::Decode,
{
if let Err(error) = self
.update_encoded_or_err(pos, f)
.map_err(<UpdateEncodedError<T>>::from)
{
panic!("`OpEncoder::update_encoded` unexpectedly failed: {error}")
}
}
fn update_encoded_or_err<T>(
&mut self,
pos: Pos<T>,
f: impl FnOnce(T) -> Option<T>,
) -> Result<(), UpdateEncodedErrorKind>
where
T: ir::Decode + ir::Encode,
{
let at = usize::from(BytePos::from(pos));
let Some(buffer) = self.buffer.get_mut(at..) else {
return Err(UpdateEncodedErrorKind::BufferOutOfBounds);
};
let Ok(decoded) = T::decode(&mut &buffer[..]) else {
return Err(UpdateEncodedErrorKind::FailedToDecode);
};
let Some(updated) = f(decoded) else {
return Err(UpdateEncodedErrorKind::FailedToUpdateEncoded);
};
if updated.encode(&mut SliceEncoder::from(buffer)).is_err() {
return Err(UpdateEncodedErrorKind::FailedToEncode);
}
Ok(())
}
}
pub struct SliceEncoder<'a> {
bytes: &'a mut [u8],
}
#[derive(Debug, Copy, Clone)]
pub struct SliceEncoderError;
impl<'a> From<&'a mut [u8]> for SliceEncoder<'a> {
fn from(bytes: &'a mut [u8]) -> Self {
Self { bytes }
}
}
impl<'a> ir::Encoder for SliceEncoder<'a> {
type Pos = ();
type Error = SliceEncoderError;
fn write_bytes(&mut self, bytes: &[u8]) -> Result<Self::Pos, Self::Error> {
let Some(buffer) = self.bytes.get_mut(..bytes.len()) else {
return Err(SliceEncoderError);
};
buffer.copy_from_slice(bytes);
Ok(())
}
fn encode_op_code(&mut self, code: OpCode) -> Result<Self::Pos, Self::Error> {
encode_op_code(self, code)
}
fn branch_offset(
&mut self,
_pos: Self::Pos,
_branch_offset: BranchOffset,
) -> Result<(), Self::Error> {
Ok(())
}
fn block_fuel(&mut self, _pos: Self::Pos, _block_fuel: BlockFuel) -> Result<(), Self::Error> {
Ok(())
}
}
pub trait FuelCostsSelector {
fn select(self, costs: &FuelCostsProvider) -> FuelUsed;
}
impl<T> FuelCostsSelector for T
where
T: FnOnce(&FuelCostsProvider) -> FuelUsed,
{
fn select(self, costs: &FuelCostsProvider) -> FuelUsed {
self(costs)
}
}
impl FuelCostsSelector for BlockFuel {
fn select(self, _costs: &FuelCostsProvider) -> FuelUsed {
FuelUsed::from(self)
}
}
impl FuelCostsSelector for FuelUsed {
fn select(self, _costs: &FuelCostsProvider) -> FuelUsed {
self
}
}
fn encode_op_code<E: ir::Encoder>(encoder: &mut E, code: OpCode) -> Result<E::Pos, E::Error> {
match cfg!(feature = "indirect-dispatch") {
true => {
u16::from(code).encode(encoder)
}
false => {
(op_code_to_handler(code) as usize).encode(encoder)
}
}
}
fn trace_branch_offset(src: BytePos, dst: Pos<Op>) -> Result<BranchOffset, Error> {
fn trace_offset_or_none(src: BytePos, dst: BytePos) -> Option<BranchOffset> {
let src = isize::try_from(usize::from(src)).ok()?;
let dst = isize::try_from(usize::from(dst)).ok()?;
let offset = dst.checked_sub(src)?;
i32::try_from(offset).map(BranchOffset::from).ok()
}
let Some(offset) = trace_offset_or_none(src, BytePos::from(dst)) else {
return Err(Error::from(TranslationError::BranchOffsetOutOfBounds));
};
Ok(offset)
}