mod span;
mod utils;
pub use self::span::{EngineFunc, EngineFuncSpan, EngineFuncSpanIter};
use self::utils::SmallByteSlice;
#[cfg(feature = "validate")]
use super::ValidatingFuncTranslator;
use super::{FuncToValidate, FuncTranslationDriver, FuncTranslator, TranslationError};
use crate::{
Config,
Error,
TrapCode,
core::{Fuel, FuelCostsProvider, hint},
engine::{ResumableOutOfFuelError, utils::unreachable_unchecked},
errors::FuelError,
module::{FuncIdx, ModuleHeader},
};
use alloc::boxed::Box;
use core::{
cell::UnsafeCell,
fmt,
iter,
mem::{ManuallyDrop, MaybeUninit},
pin::Pin,
ptr::{self, NonNull},
slice,
sync::atomic::{AtomicU8, AtomicUsize, Ordering},
};
use spin::Mutex;
#[cfg(feature = "validate")]
use wasmparser::ValidatorResources;
use wasmparser::WasmFeatures;
#[cfg(doc)]
use crate::ir::Op;
const LEN_BUCKET0_LOG2: usize = 5;
const MAX_FUNCS: usize = 100_000_000;
const LEN_BUCKET0: u64 = 1 << LEN_BUCKET0_LOG2;
const MAX_BUCKETS: usize = Funcs::required_buckets_for_len(MAX_FUNCS);
#[derive(Debug)]
pub struct CodeMap {
funcs: Funcs,
alloc_lock: Mutex<()>,
features: WasmFeatures,
}
impl CodeMap {
pub fn new(config: &Config) -> Self {
Self {
funcs: Funcs::default(),
alloc_lock: Mutex::new(()),
features: config.wasm_features(),
}
}
pub fn alloc_funcs(&self, amount: usize) -> EngineFuncSpan {
let _guard = self.alloc_lock.lock();
match self.funcs.alloc_funcs(amount) {
Ok(span) => span,
Err(err) => panic!("failed to alloc funcs: {err}"),
}
}
pub fn init_func_as_compiled(&self, func: EngineFunc, entity: CompiledFuncEntry) {
let func = match self.funcs.get(func) {
Some(func) => func,
None => panic!("missing function entry at: {func:?}"),
};
func.init_compiled(entity);
}
pub fn init_func_as_uncompiled(
&self,
func: EngineFunc,
func_idx: FuncIdx,
bytes: &[u8],
module: &ModuleHeader,
func_to_validate: Option<FuncToValidate>,
) {
let func = match self.funcs.get(func) {
Some(func) => func,
None => panic!("missing function entry at: {func:?}"),
};
func.init_uncompiled(UncompiledFuncEntry::new(
func_idx,
bytes,
module.clone(),
func_to_validate,
));
}
#[inline]
pub fn view(&self) -> CodeView<'_> {
let len_funcs = self.funcs.len_funcs.load(Ordering::Acquire);
CodeView {
code_map: self,
len_funcs,
}
}
}
#[derive(Copy, Clone)]
pub struct CodeView<'a> {
code_map: &'a CodeMap,
len_funcs: usize,
}
impl fmt::Debug for CodeView<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CodeView")
.field("len_funcs", &self.len_funcs)
.finish_non_exhaustive()
}
}
impl<'a> CodeView<'a> {
#[inline]
pub fn refresh(&mut self) {
self.len_funcs = self.code_map.funcs.len_funcs.load(Ordering::Acquire);
}
#[inline]
pub fn entry(&self, func: EngineFunc) -> Option<&'a FuncEntry> {
unsafe { self.code_map.funcs.get_within(func, self.len_funcs) }
}
#[track_caller]
#[inline]
pub fn get_or_compile(
&self,
fuel: Option<&mut Fuel>,
func: EngineFunc,
) -> Result<Option<CompiledFuncRef<'a>>, Error> {
let Some(entry) = self.entry(func) else {
return Ok(None);
};
let compiled = entry.get_or_compile(fuel, &self.code_map.features)?;
Ok(Some(compiled))
}
pub fn features(&self) -> &WasmFeatures {
&self.code_map.features
}
}
pub struct Funcs {
buckets: UnsafeCell<[Option<RawFuncsBucket>; MAX_BUCKETS]>,
len_funcs: AtomicUsize,
}
pub struct Buckets<'a> {
buckets: &'a [Option<RawFuncsBucket>],
n: usize,
}
impl<'a> Buckets<'a> {
fn new(funcs: &'a Funcs) -> Self {
let len_funcs = funcs.len_funcs.load(Ordering::Acquire);
let len_buckets = Funcs::required_buckets_for_len(len_funcs);
let base: *const Option<RawFuncsBucket> = funcs.buckets.get().cast();
let buckets = unsafe { slice::from_raw_parts(base, len_buckets) };
Self { buckets, n: 0 }
}
}
impl<'a> Iterator for Buckets<'a> {
type Item = FuncsBucketRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
let bucket = self.buckets.get(self.n)?;
let raw = bucket.as_ref().copied()?;
let len_bucket = Funcs::size_of_bucket_at(self.n);
let bucket = unsafe { FuncsBucketRef::from_raw_parts(raw, len_bucket) };
self.n += 1;
Some(bucket)
}
}
pub struct DebugBuckets<'a>(&'a Funcs);
impl fmt::Debug for DebugBuckets<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_list();
for bucket in self.0.buckets() {
dbg.entry(&bucket);
}
dbg.finish()
}
}
impl fmt::Debug for Funcs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let buckets = DebugBuckets(self);
f.debug_struct("Funcs")
.field("buckets", &buckets)
.field("len_funcs", &self.len_funcs)
.finish()
}
}
#[test]
fn size_of_funcs_type() {
const EXPECTED_SIZE: usize = (MAX_BUCKETS * core::mem::size_of::<*mut FuncEntry>())
+ core::mem::size_of::<AtomicUsize>();
assert_eq!(core::mem::size_of::<Funcs>(), EXPECTED_SIZE);
}
impl Default for Funcs {
fn default() -> Self {
Self {
buckets: UnsafeCell::new([const { None }; MAX_BUCKETS]),
len_funcs: AtomicUsize::new(0),
}
}
}
impl Funcs {
fn alloc_funcs(&self, n: usize) -> Result<EngineFuncSpan, Error> {
let start = self.len_funcs.load(Ordering::Relaxed);
let end = start
.checked_add(n)
.filter(|&end| end <= MAX_FUNCS)
.unwrap(); let current_buckets = Self::required_buckets_for_len(start);
let needed = Self::required_buckets_for_len(end);
let base = self.buckets.get().cast::<Option<RawFuncsBucket>>();
for n in current_buckets..needed {
let bucket = FuncsBucket::new(Self::size_of_bucket_at(n));
let (raw, _len) = bucket.into_raw_parts();
unsafe { base.add(n).write(Some(raw)) };
}
self.len_funcs.store(end, Ordering::Release);
Ok(EngineFuncSpan::new(
EngineFunc::from(start as u32),
EngineFunc::from(end as u32),
))
}
#[inline]
pub fn get(&self, func: EngineFunc) -> Option<&FuncEntry> {
let len_funcs = self.len_funcs.load(Ordering::Acquire);
unsafe { self.get_within(func, len_funcs) }
}
#[inline]
pub unsafe fn get_within(&self, func: EngineFunc, len_funcs: usize) -> Option<&FuncEntry> {
use crate::core::hint::unlikely;
if unlikely(u32::from(func) as usize >= len_funcs) {
return None;
}
let (bucket, slot) = Self::locate(func);
let base = self.buckets.get().cast::<Option<RawFuncsBucket>>();
let Some(raw) = (unsafe { *base.add(bucket) }) else {
unsafe { unreachable_unchecked!() } };
let bucket_ref =
unsafe { FuncsBucketRef::from_raw_parts(raw, Self::size_of_bucket_at(bucket)) };
bucket_ref.get(slot)
}
#[inline]
fn locate(func: EngineFunc) -> (usize, usize) {
let index = u32::from(func);
let j = u64::from(index) + LEN_BUCKET0;
let msb = 63 - j.leading_zeros() as usize; let bucket = msb - LEN_BUCKET0_LOG2;
let slot = (j - (1u64 << msb)) as usize;
(bucket, slot)
}
#[inline]
const fn required_buckets_for_len(len: usize) -> usize {
if len == 0 {
return 0;
}
let j = (len as u64 - 1) + LEN_BUCKET0;
let msb = 63 - j.leading_zeros() as usize;
(msb - LEN_BUCKET0_LOG2) + 1
}
#[inline]
fn size_of_bucket_at(n: usize) -> usize {
1usize << (LEN_BUCKET0_LOG2 + n)
}
fn buckets(&self) -> Buckets<'_> {
Buckets::new(self)
}
}
impl Drop for Funcs {
fn drop(&mut self) {
let buckets = self.buckets.get_mut();
for (n, raw_bucket) in buckets.iter_mut().enumerate() {
let Some(raw_bucket) = raw_bucket.take() else {
break;
};
let len = Self::size_of_bucket_at(n);
let bucket = unsafe { FuncsBucket::from_raw_parts(raw_bucket, len) };
drop(bucket)
}
}
}
#[derive(Copy, Clone)]
pub struct RawFuncsBucket {
funcs: NonNull<FuncEntry>,
}
pub struct FuncsBucket {
funcs: Box<[FuncEntry]>,
}
impl FuncsBucket {
#[inline]
pub fn new(size: usize) -> Self {
Self {
funcs: iter::repeat_with(FuncEntry::uninit).take(size).collect(),
}
}
pub fn into_raw_parts(self) -> (RawFuncsBucket, usize) {
let len = self.funcs.len();
let funcs = unsafe { NonNull::new_unchecked(Box::into_raw(self.funcs) as *mut FuncEntry) };
(RawFuncsBucket { funcs }, len)
}
#[inline]
pub unsafe fn from_raw_parts(raw: RawFuncsBucket, len: usize) -> FuncsBucket {
let funcs = ptr::slice_from_raw_parts_mut(raw.funcs.as_ptr(), len);
FuncsBucket {
funcs: unsafe { Box::from_raw(funcs) },
}
}
}
#[derive(Debug)]
pub struct FuncsBucketRef<'a> {
funcs: &'a [FuncEntry],
}
impl<'a> FuncsBucketRef<'a> {
#[inline]
pub unsafe fn from_raw_parts(raw: RawFuncsBucket, len: usize) -> FuncsBucketRef<'a> {
let funcs = unsafe { slice::from_raw_parts(raw.funcs.as_ptr(), len) };
Self { funcs }
}
#[inline]
pub fn get(&self, n: usize) -> Option<&'a FuncEntry> {
self.funcs.get(n)
}
}
#[repr(C)]
pub struct FuncEntry {
data: UnsafeCell<FuncEntryData>,
state: AtomicU8,
}
impl Drop for FuncEntry {
fn drop(&mut self) {
match self.state.load(Ordering::Acquire) {
| state::UNINIT | state::COMPILING | state::FAILED_TO_COMPILE => {}
| state::UNCOMPILED => {
let data = self.data.get_mut();
unsafe { ManuallyDrop::drop(&mut data.uncompiled) }
}
| state::COMPILED => {
let data = self.data.get_mut();
unsafe { ManuallyDrop::drop(&mut data.compiled) }
}
_ => unreachable!(),
}
}
}
impl fmt::Debug for FuncEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = unsafe { self.data_mut() };
match self.state.load(Ordering::Acquire) {
state::UNINIT => f.debug_struct("FuncEntry::Uninit").finish(),
state::COMPILING => f.debug_struct("FuncEntry::Compiling").finish(),
state::FAILED_TO_COMPILE => f.debug_struct("FuncEntry::FailedToCompile").finish(),
state::COMPILED => f
.debug_struct("FuncEntry::Compiled")
.field("state", unsafe { &state.compiled })
.finish(),
state::UNCOMPILED => f
.debug_struct("FuncEntry::Uncompiled")
.field("state", unsafe { &state.uncompiled })
.finish(),
_ => unreachable!(),
}
}
}
impl FuncEntry {
#[inline]
fn uninit() -> Self {
Self {
data: UnsafeCell::new(FuncEntryData { undefined: () }),
state: AtomicU8::new(state::UNINIT),
}
}
fn is_initialized(&self) -> bool {
self.state.load(Ordering::Relaxed) != state::UNINIT
}
fn init_compiled(&self, compiled: CompiledFuncEntry) {
assert!(!self.is_initialized(), "func has already been initialized");
unsafe { self.set_compiled(compiled) }
}
fn init_uncompiled(&self, uncompiled: UncompiledFuncEntry) {
assert!(!self.is_initialized(), "func has already been initialized");
unsafe { self.set_uncompiled(uncompiled) }
}
unsafe fn set_compiled(&self, compiled: CompiledFuncEntry) {
let data = unsafe { self.data_mut() };
data.compiled = ManuallyDrop::new(compiled);
self.state.store(state::COMPILED, Ordering::Release);
}
unsafe fn set_uncompiled(&self, uncompiled: UncompiledFuncEntry) {
let data = unsafe { self.data_mut() };
data.uncompiled = ManuallyDrop::new(uncompiled);
self.state.store(state::UNCOMPILED, Ordering::Release);
}
unsafe fn take_uncompiled(&self) -> UncompiledFuncEntry {
let data = unsafe { self.data_mut() };
let uncompiled = unsafe { ManuallyDrop::take(&mut data.uncompiled) };
data.undefined = ();
uncompiled
}
#[allow(clippy::mut_from_ref)] unsafe fn data_mut(&self) -> &mut FuncEntryData {
unsafe { &mut *self.data.get() }
}
#[inline(always)]
pub fn get_or_compile(
&self,
fuel: Option<&mut Fuel>,
features: &WasmFeatures,
) -> Result<CompiledFuncRef<'_>, Error> {
if self.state.load(Ordering::Acquire) == state::COMPILED {
return Ok(unsafe { self.assume_compiled() });
}
self.compile(fuel, features)
}
#[cold]
#[inline(never)]
fn compile(
&self,
fuel: Option<&mut Fuel>,
features: &WasmFeatures,
) -> Result<CompiledFuncRef<'_>, Error> {
use core::hint::spin_loop;
'outer: loop {
match self.state.load(Ordering::Acquire) {
state::COMPILED => break 'outer,
state::COMPILING => {
spin_loop();
continue 'outer;
}
state::FAILED_TO_COMPILE => {
hint::cold();
return Err(Error::from(TranslationError::LazyCompilationFailed));
}
state::UNCOMPILED => {
if self
.state
.compare_exchange(
state::UNCOMPILED,
state::COMPILING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
spin_loop();
continue 'outer;
}
let mut uncompiled = unsafe { self.take_uncompiled() };
match uncompiled.compile(fuel, features) {
Ok(compiled) => {
unsafe { self.set_compiled(compiled) };
break 'outer;
}
Err(error) if matches!(error.as_trap_code(), Some(TrapCode::OutOfFuel)) => {
hint::cold();
unsafe { self.set_uncompiled(uncompiled) };
return Err(error);
}
Err(error) => {
hint::cold();
self.state
.store(state::FAILED_TO_COMPILE, Ordering::Release);
return Err(error);
}
};
}
_ => unsafe { unreachable_unchecked!() },
}
}
Ok(unsafe { self.assume_compiled() })
}
#[inline]
pub unsafe fn assume_compiled(&self) -> CompiledFuncRef<'_> {
let func = unsafe { &*self.data.get() };
let compiled = unsafe { &*func.compiled };
CompiledFuncRef::from(compiled)
}
}
union FuncEntryData {
undefined: (),
uncompiled: ManuallyDrop<UncompiledFuncEntry>,
compiled: ManuallyDrop<CompiledFuncEntry>,
}
unsafe impl Send for FuncEntry {}
unsafe impl Sync for FuncEntry {}
unsafe impl Send for Funcs {}
unsafe impl Sync for Funcs {}
unsafe impl Send for RawFuncsBucket {}
unsafe impl Sync for RawFuncsBucket {}
mod state {
pub const UNINIT: u8 = 0;
pub const UNCOMPILED: u8 = 1;
pub const COMPILING: u8 = 2;
pub const FAILED_TO_COMPILE: u8 = 3;
pub const COMPILED: u8 = 4;
}
#[cfg(feature = "validate")]
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct TypeIndex(u32);
struct UncompiledFuncEntry {
func_index: FuncIdx,
bytes: SmallByteSlice,
module: ModuleHeader,
#[cfg(feature = "validate")]
validation: Option<(TypeIndex, ValidatorResources)>,
}
impl UncompiledFuncEntry {
pub fn new(
func_index: FuncIdx,
bytes: &[u8],
module: ModuleHeader,
#[cfg_attr(not(feature = "validate"), expect(unused_variables))] func_to_validate: impl Into<
Option<FuncToValidate>,
>,
) -> Self {
#[cfg(feature = "validate")]
let validation = func_to_validate.into().map(|func_to_validate| {
assert_eq!(
func_to_validate.index,
func_index.into_u32(),
"Wasmi function index ({}) does not match with Wasm validation function index ({})",
func_to_validate.index,
func_index.into_u32(),
);
(TypeIndex(func_to_validate.ty), func_to_validate.resources)
});
let bytes = bytes.into();
Self {
func_index,
bytes,
module,
#[cfg(feature = "validate")]
validation,
}
}
#[cfg_attr(not(feature = "validate"), allow(unused_variables))]
fn compile(
&mut self,
fuel: Option<&mut Fuel>,
features: &WasmFeatures,
) -> Result<CompiledFuncEntry, Error> {
const COMPILE_FUEL_PER_BYTE: u64 = 7;
const VALIDATE_FUEL_PER_BYTE: u64 = 2;
const VALIDATE_AND_COMPILE_FUEL_PER_BYTE: u64 =
VALIDATE_FUEL_PER_BYTE + COMPILE_FUEL_PER_BYTE;
let func_idx = self.func_index;
let wasm_bytes = self.bytes.as_slice();
let needs_validation = {
#[cfg(feature = "validate")]
{
self.validation.is_some()
}
#[cfg(not(feature = "validate"))]
{
false
}
};
let compilation_fuel = |_costs: &FuelCostsProvider| {
let len_bytes = wasm_bytes.len() as u64;
let fuel_per_byte = match needs_validation {
false => COMPILE_FUEL_PER_BYTE,
true => VALIDATE_AND_COMPILE_FUEL_PER_BYTE,
};
len_bytes.saturating_mul(fuel_per_byte)
};
if let Some(fuel) = fuel {
match fuel.consume_fuel(compilation_fuel) {
Err(FuelError::OutOfFuel { required_fuel }) => {
return Err(Error::from(ResumableOutOfFuelError::new(required_fuel)));
}
Ok(_) | Err(FuelError::FuelMeteringDisabled) => {}
}
}
let module = self.module.clone();
let Some(engine) = module.engine().upgrade() else {
panic!(
"cannot compile function lazily since engine does no longer exist: {:?}",
module.engine()
)
};
let mut result = MaybeUninit::uninit();
let validation = {
#[cfg(feature = "validate")]
{
self.validation.take()
}
#[cfg(not(feature = "validate"))]
{
<Option<FuncToValidate>>::None
}
};
match validation {
#[cfg(feature = "validate")]
Some((type_index, resources)) => {
let allocs = engine.get_allocs();
let translator = FuncTranslator::new(func_idx, module, allocs.0)?;
let func_to_validate = FuncToValidate {
resources,
index: func_idx.into_u32(),
ty: type_index.0,
features: *features,
};
let validator = func_to_validate.into_validator(allocs.1);
let translator = ValidatingFuncTranslator::new(validator, translator)?;
let allocs = FuncTranslationDriver::new(0, wasm_bytes, translator)?.translate(
|compiled_func| {
result.write(compiled_func);
},
)?;
engine.recycle_allocs(allocs.translation, allocs.validation);
}
None => {
let allocs = engine.get_translation_allocs();
let translator = FuncTranslator::new(func_idx, module, allocs)?;
let allocs = FuncTranslationDriver::new(0, wasm_bytes, translator)?.translate(
|compiled_func| {
result.write(compiled_func);
},
)?;
engine.recycle_translation_allocs(allocs);
}
};
Ok(unsafe { result.assume_init() })
}
}
impl fmt::Debug for UncompiledFuncEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("UncompiledFuncEntry");
dbg.field("func_idx", &self.func_index)
.field("bytes", &self.bytes)
.field("module", &self.module);
#[cfg(feature = "validate")]
dbg.field("validate", &self.validation.is_some());
dbg.finish()
}
}
#[derive(Debug)]
pub struct CompiledFuncEntry {
ops: Pin<Box<[u8]>>,
len_local_slots: u16,
len_stack_slots: u16,
}
impl CompiledFuncEntry {
pub fn new(len_local_slots: u16, len_stack_slots: u16, ops: &[u8]) -> Self {
debug_assert!(len_local_slots <= len_stack_slots);
let ops: Pin<Box<[u8]>> = Pin::new(ops.into());
debug_assert!(
!ops.is_empty(),
"compiled functions must have at least one instruction"
);
debug_assert!(
ops.len() <= i32::MAX as usize,
"compiled function has too many operators: {}",
ops.len(),
);
Self {
ops,
len_local_slots,
len_stack_slots,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct CompiledFuncRef<'a> {
ops: Pin<&'a [u8]>,
len_local_slots: u16,
len_stack_slots: u16,
}
impl<'a> From<&'a CompiledFuncEntry> for CompiledFuncRef<'a> {
#[inline]
fn from(func: &'a CompiledFuncEntry) -> Self {
Self {
ops: func.ops.as_ref(),
len_local_slots: func.len_local_slots,
len_stack_slots: func.len_stack_slots,
}
}
}
impl<'a> CompiledFuncRef<'a> {
#[inline]
pub fn ops(&self) -> &'a [u8] {
self.ops.get_ref()
}
#[inline]
pub fn len_local_slots(&self) -> u16 {
self.len_local_slots
}
#[inline]
pub fn len_stack_slots(&self) -> u16 {
self.len_stack_slots
}
}