Skip to content

WIP: replace register-alloc with purely stack-based translation #1512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 178 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
178 commits
Select commit Hold shift + click to select a range
5a2e18e
create stack2 module for stack based translation
Robbepop Apr 28, 2025
4e53784
add docs
Robbepop May 10, 2025
957dd26
rename ValueStack -> Stack
Robbepop May 10, 2025
476638f
add missing docs to OperandIdx
Robbepop May 10, 2025
6af6841
add Default impl to Stack
Robbepop May 10, 2025
8590eb2
add StackPhase to Stack
Robbepop May 10, 2025
aed2c55
apply rustfmt
Robbepop May 11, 2025
1834056
add LocalIdx type to locals.rs submodule
Robbepop May 12, 2025
c0df291
add OperandIdx field to each Operand enum variant
Robbepop May 12, 2025
9a06725
add conversion from StackOperand -> Operand
Robbepop May 12, 2025
60d2c6e
add stub Stack base API
Robbepop May 12, 2025
e32c985
add RegisterSpace API
Robbepop May 12, 2025
b1c58a3
fix some warnings
Robbepop May 12, 2025
2db20c4
silence warnings for now
Robbepop May 12, 2025
74baf6c
add docs to Stack's stub API
Robbepop May 12, 2025
afd06f7
add Stack::peek API
Robbepop May 12, 2025
4507da8
add some inline annotations
Robbepop May 12, 2025
fce2c49
add Stack::alloc_const stub API
Robbepop May 12, 2025
7736d19
update locals docs
Robbepop May 12, 2025
10cd546
add ConstRegistry impl
Robbepop May 12, 2025
086aa84
apply rustfmt
Robbepop May 12, 2025
342f1fe
implement most of the LocalsRegistry
Robbepop May 12, 2025
524fbb0
fix broken doc link
Robbepop May 12, 2025
e611bb1
swap consts and locals fields
Robbepop May 12, 2025
29434ae
add Stack::reset
Robbepop May 12, 2025
c25b706
use NonZero instead of NonZeroUsize
Robbepop May 14, 2025
a750ea2
improve LocalsRegistry and add tests
Robbepop May 14, 2025
6ce307f
use u32 instead of usize in LocalsRegistry::register
Robbepop May 15, 2025
d354f31
refactor StackPhase
Robbepop May 15, 2025
1ecee13
implement Stack::pop_some
Robbepop May 15, 2025
687e277
implement all stubs of new Stack
Robbepop May 19, 2025
88ebb27
remove unnecessary #[must_use] attributes
Robbepop May 20, 2025
cdc06fb
optimize LocalsRegistry perf and memory usage
Robbepop May 20, 2025
b42965d
add missing wrapping_sub
Robbepop May 20, 2025
20e4684
enable unused_variables warning again
Robbepop May 20, 2025
4406ff0
move Operand definitions into its own submodule
Robbepop May 20, 2025
65d9d69
change imports
Robbepop May 20, 2025
6e8ac7b
rename max_stack_height -> max_height, add getter, update docs
Robbepop May 20, 2025
4e4528b
fix broken doc links
Robbepop May 20, 2025
fb1c089
remove StackPhase
Robbepop May 20, 2025
9fe939b
remove Stack::finish_translation method
Robbepop May 20, 2025
34a3624
update #Error docs
Robbepop May 20, 2025
230ed2e
add TempOperand::instr getter
Robbepop May 20, 2025
d2d6f30
use &self for Operand getters
Robbepop May 20, 2025
5e6bc67
make LocalsRegistry::ty panic instead of returning Option
Robbepop May 20, 2025
0db6e62
PreservedLocalsIter: use StackOperand slice instead of Stack
Robbepop May 21, 2025
3f2f449
use replace_first_operand in Stack::preserve_locals
Robbepop May 21, 2025
ad79e0b
restructure into new translator2 sub-module
Robbepop May 23, 2025
9f1614c
expect (silence) more warnings
Robbepop May 23, 2025
024f8eb
update docs
Robbepop May 23, 2025
47cd1b1
add Reset trait and impls
Robbepop May 24, 2025
2583483
refactor Stack internals
Robbepop May 24, 2025
569fb02
add ControlStack to Stack internals
Robbepop May 24, 2025
5f960eb
carve out StackLayout from Stack
Robbepop May 26, 2025
70ba4d1
add stack and layout fields to FuncTranslator
Robbepop May 26, 2025
1d5de36
rename lifetime
Robbepop May 26, 2025
785bae7
use usize instead of u32 for ControlFrame height
Robbepop May 26, 2025
c81788b
return direct references in ControlStack::get[_mut]
Robbepop May 26, 2025
4e6aca6
add control stack API to Stack
Robbepop May 26, 2025
aba7d47
apply rustfmt
Robbepop May 26, 2025
f0e1f5d
remove unused ConstsRegistry from Stack
Robbepop May 26, 2025
8524526
refactor Stack to separate control and value stacks
Robbepop May 27, 2025
bccb499
move StackLayout to its own module
Robbepop May 27, 2025
1fdd104
make Stack::peek more powerful
Robbepop May 27, 2025
06cb607
use `usize` for `depth` params
Robbepop May 27, 2025
1b6242b
apply clippy suggestions
Robbepop May 27, 2025
8bf14e2
implement FuncTranslator::register_locals
Robbepop May 27, 2025
2789773
add is_fuel_metering_enabled convenience func
Robbepop May 27, 2025
4155ded
impl finish_translate_locals as no-op
Robbepop May 27, 2025
6061923
fix doc links
Robbepop May 27, 2025
9e6391a
no longer return Option from Stack::pop{2,3}
Robbepop May 27, 2025
d6c1175
change StackOperand doc links to Operand
Robbepop May 27, 2025
b502a88
cleanup panic doc
Robbepop May 27, 2025
7fac62e
add LabelRegistry to new FuncTranslator
Robbepop May 28, 2025
99bcc84
add basic instruction encoder
Robbepop May 30, 2025
2c0d7a4
add Reset for FuncTranslatorAllocations
Robbepop May 30, 2025
e0cd2b1
initialize function body enclosing block
Robbepop May 31, 2025
c0f5a15
add bail_unreachable utility macro
Robbepop May 31, 2025
c586806
refactor initialization
Robbepop May 31, 2025
7681f08
implement WasmTranslator::finish for new translator
Robbepop Jun 3, 2025
b10a6d2
post-rebase cleanup
Robbepop Jun 5, 2025
f97ec77
move stack-based FuncTranslator into func2 submodule
Robbepop Jun 5, 2025
a0b0013
add experimental-translator feature to wasmi crate
Robbepop Jun 5, 2025
9beaf55
add StackAllocations and fix Stack init bug
Robbepop Jun 5, 2025
4069881
apply rustfmt
Robbepop Jun 5, 2025
d7a91cc
add Stack::push_func_block
Robbepop Jun 5, 2025
0debd7f
remove invalid debug assert
Robbepop Jun 5, 2025
9ebe550
add Layout::operand_to_reg utility method
Robbepop Jun 5, 2025
9e17492
implement some visit methods
Robbepop Jun 5, 2025
307f9fc
apply clippy suggestion
Robbepop Jun 6, 2025
40de714
refactor ControlFrame
Robbepop Jun 6, 2025
bec5a7f
implemented visit_end as stub
Robbepop Jun 6, 2025
924151a
remove unused ControlFrame::is_branched_to method
Robbepop Jun 6, 2025
db3254e
differentiate between func enclosing block in translation
Robbepop Jun 7, 2025
e9277c0
initial impl for translate_end_func
Robbepop Jun 7, 2025
2e5d0c0
implement translate_end_func method
Robbepop Jun 7, 2025
b1ea167
unsilence unused_variables warning and fix occurrences
Robbepop Jun 7, 2025
eeff947
simplify translate_end_func when in unreachable state
Robbepop Jun 7, 2025
f9d2d3f
unsilence and fix occurrences of unused_imports
Robbepop Jun 7, 2025
b18a152
add Stack::peek_control
Robbepop Jun 8, 2025
2b4eaa3
add InstrEncoder::{get_mut, bump_fuel_consumption} methods
Robbepop Jun 8, 2025
96614dc
refactor tranlate_end_func
Robbepop Jun 8, 2025
9971cd0
add ControlFrame::len_branch_params
Robbepop Jun 9, 2025
546c0fe
refactor and simplify translate_end
Robbepop Jun 9, 2025
feed87b
format the code in a more readable way
Robbepop Jun 9, 2025
cb32d6a
no longer pop operands in translate_return
Robbepop Jun 9, 2025
705b547
trunc operands in translate_end_block
Robbepop Jun 9, 2025
d8c3996
fix Stack::trunc method
Robbepop Jun 9, 2025
21d55ed
no longer manipulate stack in copy_operand[s]_to_temp methods
Robbepop Jun 9, 2025
b0bf174
move trivial match arm to top
Robbepop Jun 9, 2025
c7d8104
refactor FuncTranslator::is_fuel_metering_enabled
Robbepop Jun 9, 2025
7bdb581
return Operand in Stack::operator_to_temp
Robbepop Jun 9, 2025
64545d9
refactor new translator
Robbepop Jun 9, 2025
981f9b7
move into_allocations method higher in file
Robbepop Jun 9, 2025
d562ed9
fix intra doc link
Robbepop Jun 9, 2025
f31a315
add experimental-translator crate feature to wasmi_wast crate
Robbepop Jun 9, 2025
8558dc1
properly update reachability in translate_end_block
Robbepop Jun 9, 2025
11d5943
implement translate_end_loop
Robbepop Jun 9, 2025
bad1283
add ReusableAllocations trait
Robbepop Jun 11, 2025
533d12e
add InstrEncoderAllocations
Robbepop Jun 11, 2025
91ee870
add fuel_costs to InstrEncoder
Robbepop Jun 11, 2025
715f38f
make full use of the new ReusableAllocations trait
Robbepop Jun 11, 2025
9174841
add InstrEncoder::push_consume_fuel_instr
Robbepop Jun 11, 2025
5ea3345
add Stack::consume_fuel_instr getter
Robbepop Jun 11, 2025
a4beb56
apply rustfmt
Robbepop Jun 11, 2025
d627411
add fuel metering support to InstrEncoder APIs
Robbepop Jun 11, 2025
0f016ed
make InstrEncoder aware if fuel metering is enabled
Robbepop Jun 11, 2025
1db7f22
add FuncTranslator::pin_label utility method
Robbepop Jun 11, 2025
5956335
refactor Stack::push_loop
Robbepop Jun 11, 2025
b2fb6bf
improve and update docs
Robbepop Jun 11, 2025
cc41968
remove debug println
Robbepop Jun 11, 2025
cef1ee5
add StackOperand::ty getter method
Robbepop Jun 11, 2025
7d730c9
refactor and fix OperandStack::operand_to_temp_at method
Robbepop Jun 11, 2025
2c6064c
implement FuncTranslator::visit_block trait method
Robbepop Jun 11, 2025
17b2679
implement FuncTranslator::visit_loop trait method
Robbepop Jun 11, 2025
571eace
reorder exports
Robbepop Jun 12, 2025
10a13d3
push else operands only for IfReachability::Both
Robbepop Jun 12, 2025
d5ad83e
start implementation of FuncTranslator::visit_if
Robbepop Jun 12, 2025
f4f7e07
convert condition operand to Reg
Robbepop Jun 13, 2025
0c4ce98
fix some internal doc links
Robbepop Jun 14, 2025
71575af
make InstrEncoder::get_mut private
Robbepop Jun 14, 2025
781681e
fix compile error
Robbepop Jun 14, 2025
542a205
silence warnings temporarily for visit_if
Robbepop Jun 15, 2025
579fe43
remove Reset impl for InstrEncoder
Robbepop Jun 15, 2025
ba9497c
add last_instr field to InstrEncoder
Robbepop Jun 15, 2025
1519f9c
add (debug) asserts for is_instruction_parameter
Robbepop Jun 15, 2025
9208950
add AllocConst impl for StackLayout
Robbepop Jun 15, 2025
e4f192c
add InstrEncoder::try_replace_instr method
Robbepop Jun 15, 2025
abdd57b
implement translate_br_if variants + fusion
Robbepop Jun 15, 2025
ff30725
implement visit_if
Robbepop Jun 15, 2025
1f0febc
fix bug in translate_br_if fallback
Robbepop Jun 15, 2025
a14eaaa
reorder exports
Robbepop Jun 16, 2025
d6d09de
fix imports in func2 translator
Robbepop Jun 16, 2025
1012dc9
cfg-guard func and func2 modules
Robbepop Jun 16, 2025
dfa4fbf
silence warnings instead of conditionally compile func module
Robbepop Jun 17, 2025
45ca651
implement translate_br
Robbepop Jun 17, 2025
9051604
refactor Stack::push_else method
Robbepop Jun 17, 2025
7c9256c
implement FuncTranslator::visit_else
Robbepop Jun 17, 2025
024f17d
implement FuncTranslator::visit_nop
Robbepop Jun 17, 2025
8834944
implement FuncTranslator::visit_unreachable
Robbepop Jun 17, 2025
5dffbc7
implement FuncTranslator::end_unreachable
Robbepop Jun 17, 2025
caf163a
add StackHeight utility type
Robbepop Jun 17, 2025
dadb30c
put debug_assert to various translate_end_* methods
Robbepop Jun 20, 2025
a41074a
improve ControlStack::pop docs
Robbepop Jun 20, 2025
330176a
rename UnreachableControlFrame -> ControlFrameKind
Robbepop Jun 20, 2025
fd5b006
pop else operands only when available
Robbepop Jun 20, 2025
d83d9b9
drop else operands when possible
Robbepop Jun 20, 2025
5da91c3
pull let binding into if's then block
Robbepop Jun 20, 2025
b288739
implement translate_end_if
Robbepop Jun 20, 2025
e34ff30
add and use ControlFrameBase trait
Robbepop Jun 20, 2025
1b292c0
use ControlFrameBase to generalize end_if translation
Robbepop Jun 20, 2025
fdb4542
fix translate_end_if label pinning
Robbepop Jun 20, 2025
f144887
implement translate_end_else
Robbepop Jun 20, 2025
0f0e1ae
fix pinning of the else_label
Robbepop Jun 20, 2025
6c76c0c
implement FuncTranslator::visit_return
Robbepop Jun 20, 2025
1929b49
rename translate_return -> encode_return
Robbepop Jun 20, 2025
7959811
make ControlStack::get_mut return ControlFrameMut wrapper
Robbepop Jun 22, 2025
878ab39
add Stack::acquire_target method
Robbepop Jun 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/wasmi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ prefer-btree-collections = [
]
wat = ["dep:wat", "std"]
simd = ["wasmi_core/simd", "wasmi_ir/simd", "wasmparser/simd"]
experimental-translator = []

# Enables extra checks performed during Wasmi bytecode execution.
#
Expand Down
5 changes: 5 additions & 0 deletions crates/wasmi/src/engine/translator/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum TranslationError {
TooManyFunctionResults,
/// Tried to define a function with too many function parameters.
TooManyFunctionParams,
/// Tried to define a function with too many local variables.
TooManyLocalVariables,
/// The function failed to compiled lazily.
LazyCompilationFailed,
}
Expand Down Expand Up @@ -99,6 +101,9 @@ impl Display for TranslationError {
Self::TooManyFunctionParams => {
write!(f, "encountered function with too many function parameters")
}
Self::TooManyLocalVariables => {
write!(f, "encountered function with too many local variables")
}
Self::LazyCompilationFailed => {
write!(
f,
Expand Down
225 changes: 225 additions & 0 deletions crates/wasmi/src/engine/translator/func2/instrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use super::{Reset, ReusableAllocations};
use crate::{
core::FuelCostsProvider,
engine::translator::utils::{BumpFuelConsumption as _, Instr, IsInstructionParameter as _},
ir::Instruction,
Engine,
Error,
};
use alloc::vec::{self, Vec};

/// Creates and encodes the list of [`Instruction`]s for a function.
#[derive(Debug, Default)]
pub struct InstrEncoder {
/// The list of constructed instructions and their parameters.
instrs: Vec<Instruction>,
/// The fuel costs of instructions.
///
/// This is `Some` if fuel metering is enabled, otherwise `None`.
fuel_costs: Option<FuelCostsProvider>,
/// The last pushed non-parameter [`Instruction`].
last_instr: Option<Instr>,
}

impl ReusableAllocations for InstrEncoder {
type Allocations = InstrEncoderAllocations;

fn into_allocations(self) -> Self::Allocations {
Self::Allocations {
instrs: self.instrs,
}
}
}

/// The reusable heap allocations of the [`InstrEncoder`].
#[derive(Debug, Default)]
pub struct InstrEncoderAllocations {
/// The list of constructed instructions and their parameters.
instrs: Vec<Instruction>,
}

impl Reset for InstrEncoderAllocations {
fn reset(&mut self) {
self.instrs.clear();
}
}

impl InstrEncoder {
/// Creates a new [`InstrEncoder`].
pub fn new(engine: &Engine, alloc: InstrEncoderAllocations) -> Self {
let config = engine.config();
let fuel_costs = config
.get_consume_fuel()
.then(|| config.fuel_costs())
.cloned();
Self {
instrs: alloc.instrs,
fuel_costs,
last_instr: None,
}
}

/// Returns the next [`Instr`].
#[must_use]
pub fn next_instr(&self) -> Instr {
Instr::from_usize(self.instrs.len())
}

/// Pushes an [`Instruction::ConsumeFuel`] instruction to `self`.
///
/// # Note
///
/// The pushes [`Instruction::ConsumeFuel`] is initialized with base fuel costs.
pub fn push_consume_fuel_instr(&mut self) -> Result<Option<Instr>, Error> {
let Some(fuel_costs) = &self.fuel_costs else {
return Ok(None);
};
let base_costs = fuel_costs.base();
let Ok(base_costs) = u32::try_from(base_costs) else {
panic!("out of bounds base fuel costs: {base_costs}");
};
let instr = self.push_instr_impl(Instruction::consume_fuel(base_costs))?;
Ok(Some(instr))
}

/// Pushes a non-parameter [`Instruction`] to the [`InstrEncoder`].
///
/// Returns an [`Instr`] that refers to the pushed [`Instruction`].
pub fn push_instr(
&mut self,
instruction: Instruction,
consume_fuel: Option<Instr>,
f: impl FnOnce(&FuelCostsProvider) -> u64,
) -> Result<Instr, Error> {
self.bump_fuel_consumption(consume_fuel, f)?;
self.push_instr_impl(instruction)
}

/// Pushes a non-parameter [`Instruction`] to the [`InstrEncoder`].
fn push_instr_impl(&mut self, instruction: Instruction) -> Result<Instr, Error> {
debug_assert!(
!instruction.is_instruction_parameter(),
"parameter: {instruction:?}"
);
let instr = self.next_instr();
self.instrs.push(instruction);
self.last_instr = Some(instr);
Ok(instr)
}

/// Replaces `instr` with `new_instr` in `self`.
///
/// - Returns `Ok(true)` if replacement was successful.
/// - Returns `Ok(false)` if replacement was unsuccessful.
///
/// # Panics (Debug)
///
/// If `instr` or `new_instr` are [`Instruction`] parameters.
pub fn try_replace_instr(
&mut self,
instr: Instr,
new_instr: Instruction,
) -> Result<bool, Error> {
debug_assert!(
!new_instr.is_instruction_parameter(),
"parameter: {new_instr:?}"
);
let Some(last_instr) = self.last_instr else {
return Ok(false);
};
let replace = self.get_mut(instr);
debug_assert!(!replace.is_instruction_parameter(), "parameter: {instr:?}");
if instr != last_instr {
return Ok(false);
}
*replace = new_instr;
Ok(true)
}

/// Pushes an [`Instruction`] parameter to the [`InstrEncoder`].
///
/// The parameter is associated to the last pushed [`Instruction`].
pub fn push_param(&mut self, instruction: Instruction) {
debug_assert!(
instruction.is_instruction_parameter(),
"non-parameter: {instruction:?}"
);
self.instrs.push(instruction);
}

/// Returns a shared reference to the [`Instruction`] associated to [`Instr`].
///
/// # Panics
///
/// If `instr` is out of bounds for `self`.
pub fn get(&self, instr: Instr) -> &Instruction {
&self.instrs[instr.into_usize()]
}

/// Returns an exclusive reference to the [`Instruction`] associated to [`Instr`].
///
/// # Panics
///
/// If `instr` is out of bounds for `self`.
fn get_mut(&mut self, instr: Instr) -> &mut Instruction {
&mut self.instrs[instr.into_usize()]
}

/// Bumps consumed fuel for [`Instruction::ConsumeFuel`] of `instr` by `delta`.
///
/// # Errors
///
/// If consumed fuel is out of bounds after this operation.
pub fn bump_fuel_consumption(
&mut self,
consume_fuel: Option<Instr>,
f: impl FnOnce(&FuelCostsProvider) -> u64,
) -> Result<(), Error> {
let (fuel_costs, consume_fuel) = match (&self.fuel_costs, consume_fuel) {
(None, None) => return Ok(()),
(Some(fuel_costs), Some(consume_fuel)) => (fuel_costs, consume_fuel),
_ => {
panic!(
"fuel metering state mismatch: fuel_costs: {:?}, fuel_instr: {:?}",
self.fuel_costs, consume_fuel,
);
}
};
let fuel_consumed = f(fuel_costs);
self.get_mut(consume_fuel)
.bump_fuel_consumption(fuel_consumed)?;
Ok(())
}

/// Returns an iterator yielding all [`Instruction`]s of the [`InstrEncoder`].
///
/// # Note
///
/// The [`InstrEncoder`] will be empty after this operation.
pub fn drain(&mut self) -> InstrEncoderIter {
InstrEncoderIter {
iter: self.instrs.drain(..),
}
}
}

/// Iterator yielding all [`Instruction`]s of the [`InstrEncoder`].
#[derive(Debug)]
pub struct InstrEncoderIter<'a> {
/// The underlying iterator.
iter: vec::Drain<'a, Instruction>,
}

impl<'a> Iterator for InstrEncoderIter<'a> {
type Item = Instruction;

fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}

impl ExactSizeIterator for InstrEncoderIter<'_> {
fn len(&self) -> usize {
self.iter.len()
}
}
142 changes: 142 additions & 0 deletions crates/wasmi/src/engine/translator/func2/layout/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use super::Reset;
use crate::{core::UntypedVal, engine::TranslationError, ir::Reg, Error};
use alloc::{
collections::{btree_map, BTreeMap},
vec::Vec,
};
use core::{iter::Rev, slice::Iter as SliceIter};

/// A pool of deduplicated function local constant values.
///
/// - Those constant values are identified by their associated [`Reg`].
/// - All constant values are also deduplicated so that no duplicates
/// are stored in a [`ConstRegistry`]. This also means that deciding if two
/// [`Reg`] values refer to the equal constant values can be efficiently
/// done by comparing the [`Reg`] indices without resolving to their
/// underlying constant values.
#[derive(Debug, Default)]
pub struct ConstRegistry {
/// Mapping from constant [`UntypedVal`] values to [`Reg`] indices.
const2idx: BTreeMap<UntypedVal, Reg>,
/// Mapping from [`Reg`] indices to constant [`UntypedVal`] values.
idx2const: Vec<UntypedVal>,
/// The [`Reg`] index for the next allocated function local constant value.
next_idx: i16,
}

impl Reset for ConstRegistry {
fn reset(&mut self) {
self.const2idx.clear();
self.idx2const.clear();
self.next_idx = Self::first_index();
}
}

impl ConstRegistry {
/// The maximum index for [`Reg`] referring to function local constant values.
///
/// # Note
///
/// The maximum index is also the one to be assigned to the first allocated
/// function local constant value as indices are counting downwards.
fn first_index() -> i16 {
-1
}

/// The mininmum index for [`Reg`] referring to function local constant values.
///
/// # Note
///
/// This index is not assignable to a function local constant value and acts
/// as a bound to guard against overflowing the range of indices.
fn last_index() -> i16 {
i16::MIN
}

/// Returns the number of allocated function local constant values.
pub fn len_consts(&self) -> u16 {
self.next_idx.abs_diff(Self::first_index())
}

/// Allocates a new constant `value` on the [`ConstRegistry`] and returns its identifier.
///
/// # Note
///
/// If the constant `value` already exists in this [`ConstRegistry`] no new value is
/// allocated and the identifier of the existing constant `value` returned instead.
///
/// # Errors
///
/// If too many constant values have been allocated for this [`ConstRegistry`].
pub fn alloc(&mut self, value: UntypedVal) -> Result<Reg, Error> {
if self.next_idx == Self::last_index() {
return Err(Error::from(TranslationError::TooManyFuncLocalConstValues));
}
match self.const2idx.entry(value) {
btree_map::Entry::Occupied(entry) => Ok(*entry.get()),
btree_map::Entry::Vacant(entry) => {
let register = Reg::from(self.next_idx);
self.next_idx -= 1;
entry.insert(register);
self.idx2const.push(value);
Ok(register)
}
}
}

/// Returns the function local constant [`UntypedVal`] of the [`Reg`] if any.
pub fn get(&self, register: Reg) -> Option<UntypedVal> {
if !register.is_const() {
return None;
}
let index = i16::from(register).wrapping_add(1).unsigned_abs() as usize;
self.idx2const.get(index).copied()
}

/// Returns an iterator yielding all function local constant values of the [`ConstRegistry`].
///
/// # Note
///
/// The function local constant values are yielded in their allocation order.
pub fn iter(&self) -> ConstRegistryIter {
ConstRegistryIter::new(self)
}
}

/// Iterator yielding all allocated function local constant values.
pub struct ConstRegistryIter<'a> {
/// The underlying iterator.
iter: Rev<SliceIter<'a, UntypedVal>>,
}

impl<'a> ConstRegistryIter<'a> {
/// Creates a new [`ConstRegistryIter`] from the given slice of [`UntypedVal`].
pub fn new(consts: &'a ConstRegistry) -> Self {
// Note: we need to revert the iteration since we allocate new
// function local constants in reverse order of their absolute
// vector indices in the function call frame during execution.
Self {
iter: consts.idx2const.as_slice().iter().rev(),
}
}
}

impl Iterator for ConstRegistryIter<'_> {
type Item = UntypedVal;

fn next(&mut self) -> Option<Self::Item> {
self.iter.next().copied()
}
}

impl DoubleEndedIterator for ConstRegistryIter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().copied()
}
}

impl ExactSizeIterator for ConstRegistryIter<'_> {
fn len(&self) -> usize {
self.iter.len()
}
}
Loading
Loading