use crate::codegen::ptr_type_from_ptr_size;
use crate::isa::{CallingConvention, reg::Reg};
use crate::masm::SPOffset;
use anyhow::Result;
use smallvec::SmallVec;
use std::collections::HashSet;
use std::ops::{Add, BitAnd, Not, Sub};
use wasmtime_environ::{WasmFuncType, WasmValType};
pub(crate) mod local;
pub(crate) use local::*;
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub(super) enum ParamsOrReturns {
Params,
Returns,
}
macro_rules! vmctx {
($m:ident) => {
<$m::ABI as $crate::abi::ABI>::vmctx_reg()
};
}
macro_rules! scratch {
($m:ident) => {
<$m::ABI as $crate::abi::ABI>::scratch_for(&wasmtime_environ::WasmValType::I64)
};
($m:ident, $wasm_type:expr) => {
<$m::ABI as $crate::abi::ABI>::scratch_for($wasm_type)
};
}
pub(crate) use scratch;
pub(crate) use vmctx;
pub(crate) fn wasm_sig<A: ABI>(ty: &WasmFuncType) -> Result<ABISig> {
let mut params: SmallVec<[WasmValType; 6]> = SmallVec::new();
params.extend_from_slice(&vmctx_types::<A>());
params.extend_from_slice(ty.params());
A::sig_from(¶ms, ty.returns(), &CallingConvention::Default)
}
pub(crate) fn vmctx_types<A: ABI>() -> [WasmValType; 2] {
[A::ptr_type(), A::ptr_type()]
}
pub(crate) trait ABI {
fn stack_align() -> u8;
fn call_stack_align() -> u8;
fn arg_base_offset() -> u8;
fn initial_frame_size() -> u8;
#[cfg(test)]
fn sig(wasm_sig: &WasmFuncType, call_conv: &CallingConvention) -> Result<ABISig> {
Self::sig_from(wasm_sig.params(), wasm_sig.returns(), call_conv)
}
fn sig_from(
params: &[WasmValType],
returns: &[WasmValType],
call_conv: &CallingConvention,
) -> Result<ABISig>;
fn abi_results(returns: &[WasmValType], call_conv: &CallingConvention) -> Result<ABIResults>;
fn word_bits() -> u8;
fn word_bytes() -> u8 {
Self::word_bits() / 8
}
fn scratch_for(ty: &WasmValType) -> Reg;
fn vmctx_reg() -> Reg;
fn stack_slot_size() -> u8;
fn sizeof(ty: &WasmValType) -> u8;
fn ptr_type() -> WasmValType {
WasmValType::I64
}
}
#[derive(Clone, Debug)]
pub enum ABIOperand {
Reg {
ty: WasmValType,
reg: Reg,
size: u32,
},
Stack {
ty: WasmValType,
offset: u32,
size: u32,
},
}
impl ABIOperand {
pub fn reg(reg: Reg, ty: WasmValType, size: u32) -> Self {
Self::Reg { reg, ty, size }
}
pub fn stack_offset(offset: u32, ty: WasmValType, size: u32) -> Self {
Self::Stack { ty, offset, size }
}
pub fn is_reg(&self) -> bool {
match *self {
ABIOperand::Reg { .. } => true,
_ => false,
}
}
pub fn unwrap_reg(&self) -> Reg {
match self {
ABIOperand::Reg { reg, .. } => *reg,
_ => unreachable!(),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ABIOperands {
pub inner: SmallVec<[ABIOperand; 6]>,
pub regs: HashSet<Reg>,
pub bytes: u32,
}
impl Default for ABIOperands {
fn default() -> Self {
Self {
inner: Default::default(),
regs: HashSet::with_capacity(0),
bytes: 0,
}
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum RetArea {
SP(SPOffset),
Slot(LocalSlot),
Uninit,
}
impl Default for RetArea {
fn default() -> Self {
Self::Uninit
}
}
impl RetArea {
pub fn sp(offs: SPOffset) -> Self {
Self::SP(offs)
}
pub fn slot(local: LocalSlot) -> Self {
Self::Slot(local)
}
pub fn unwrap_sp(&self) -> SPOffset {
match self {
Self::SP(offs) => *offs,
_ => unreachable!(),
}
}
pub fn is_sp(&self) -> bool {
match self {
Self::SP(_) => true,
_ => false,
}
}
pub fn is_uninit(&self) -> bool {
match self {
Self::Uninit => true,
_ => false,
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ABIResults {
operands: ABIOperands,
ret_area: Option<RetArea>,
}
impl ABIResults {
pub fn from<F>(
returns: &[WasmValType],
call_conv: &CallingConvention,
mut map: F,
) -> Result<Self>
where
F: FnMut(&WasmValType, u32) -> Result<(ABIOperand, u32)>,
{
if returns.len() == 0 {
return Ok(Self::default());
}
type FoldTuple = (SmallVec<[ABIOperand; 6]>, HashSet<Reg>, u32);
type FoldTupleResult = Result<FoldTuple>;
let fold_impl =
|(mut operands, mut regs, stack_bytes): FoldTuple, arg| -> FoldTupleResult {
let (operand, bytes) = map(arg, stack_bytes)?;
if operand.is_reg() {
regs.insert(operand.unwrap_reg());
}
operands.push(operand);
Ok((operands, regs, bytes))
};
let (mut operands, regs, bytes) = if call_conv.is_default() {
returns
.iter()
.rev()
.try_fold((SmallVec::new(), HashSet::with_capacity(1), 0), fold_impl)?
} else {
returns
.iter()
.try_fold((SmallVec::new(), HashSet::with_capacity(1), 0), fold_impl)?
};
if call_conv.is_default() {
operands.reverse();
}
Ok(Self::new(ABIOperands {
inner: operands,
regs,
bytes,
}))
}
pub fn new(operands: ABIOperands) -> Self {
let ret_area = (operands.bytes > 0).then(|| RetArea::default());
Self { operands, ret_area }
}
pub fn regs(&self) -> &HashSet<Reg> {
&self.operands.regs
}
pub fn operands(&self) -> &[ABIOperand] {
&self.operands.inner
}
pub fn len(&self) -> usize {
self.operands.inner.len()
}
pub fn stack_operands_len(&self) -> usize {
self.operands().len() - self.regs().len()
}
#[cfg(test)]
pub fn get(&self, n: usize) -> Option<&ABIOperand> {
self.operands.inner.get(n)
}
pub fn unwrap_singleton(&self) -> &ABIOperand {
debug_assert_eq!(self.len(), 1);
&self.operands.inner[0]
}
pub fn size(&self) -> u32 {
self.operands.bytes
}
pub fn on_stack(&self) -> bool {
self.operands.bytes > 0
}
pub fn set_ret_area(&mut self, area: RetArea) {
debug_assert!(self.on_stack());
debug_assert!(!area.is_uninit());
self.ret_area = Some(area);
}
pub fn ret_area(&self) -> Option<&RetArea> {
self.ret_area.as_ref()
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ABIParams {
operands: ABIOperands,
has_retptr: bool,
}
impl ABIParams {
pub fn from<F, A: ABI>(
params: &[WasmValType],
initial_bytes: u32,
needs_stack_results: bool,
mut map: F,
) -> Result<Self>
where
F: FnMut(&WasmValType, u32) -> Result<(ABIOperand, u32)>,
{
if params.len() == 0 && !needs_stack_results {
return Ok(Self::with_bytes(initial_bytes));
}
let register_capacity = params.len().min(6);
let mut operands = SmallVec::new();
let mut regs = HashSet::with_capacity(register_capacity);
let mut stack_bytes = initial_bytes;
let ptr_type = ptr_type_from_ptr_size(<A as ABI>::word_bytes());
let stack_results = if needs_stack_results {
let (operand, bytes) = map(&ptr_type, stack_bytes)?;
if operand.is_reg() {
regs.insert(operand.unwrap_reg());
}
stack_bytes = bytes;
Some(operand)
} else {
None
};
for arg in params.iter() {
let (operand, bytes) = map(arg, stack_bytes)?;
if operand.is_reg() {
regs.insert(operand.unwrap_reg());
}
operands.push(operand);
stack_bytes = bytes;
}
if let Some(operand) = stack_results {
operands.push(operand);
}
Ok(Self {
operands: ABIOperands {
inner: operands,
regs,
bytes: stack_bytes,
},
has_retptr: needs_stack_results,
})
}
pub fn with_bytes(bytes: u32) -> Self {
let mut params = Self::default();
params.operands.bytes = bytes;
params
}
#[cfg(test)]
pub fn get(&self, n: usize) -> Option<&ABIOperand> {
self.operands.inner.get(n)
}
pub fn operands(&self) -> &[ABIOperand] {
&self.operands.inner
}
pub fn len(&self) -> usize {
self.operands.inner.len()
}
pub fn len_without_retptr(&self) -> usize {
if self.has_retptr {
self.len() - 1
} else {
self.len()
}
}
pub fn has_retptr(&self) -> bool {
self.has_retptr
}
pub fn unwrap_results_area_operand(&self) -> &ABIOperand {
debug_assert!(self.has_retptr);
self.operands.inner.last().unwrap()
}
}
#[derive(Debug, Clone)]
pub(crate) struct ABISig {
pub params: ABIParams,
pub results: ABIResults,
pub regs: HashSet<Reg>,
pub call_conv: CallingConvention,
}
impl Default for ABISig {
fn default() -> Self {
Self {
params: Default::default(),
results: Default::default(),
regs: Default::default(),
call_conv: CallingConvention::Default,
}
}
}
impl ABISig {
pub fn new(cc: CallingConvention, params: ABIParams, results: ABIResults) -> Self {
let regs = params
.operands
.regs
.union(&results.operands.regs)
.copied()
.collect();
Self {
params,
results,
regs,
call_conv: cc,
}
}
pub fn params(&self) -> &[ABIOperand] {
self.params.operands()
}
pub fn results(&self) -> &[ABIOperand] {
self.results.operands()
}
pub fn params_without_retptr(&self) -> &[ABIOperand] {
if self.params.has_retptr() {
&self.params()[0..(self.params.len() - 1)]
} else {
self.params()
}
}
pub fn params_stack_size(&self) -> u32 {
self.params.operands.bytes
}
pub fn results_stack_size(&self) -> u32 {
self.results.operands.bytes
}
pub fn has_stack_results(&self) -> bool {
self.results.on_stack()
}
}
pub(crate) fn align_to<N>(value: N, alignment: N) -> N
where
N: Not<Output = N>
+ BitAnd<N, Output = N>
+ Add<N, Output = N>
+ Sub<N, Output = N>
+ From<u8>
+ Copy,
{
let alignment_mask = alignment - 1.into();
(value + alignment_mask) & !alignment_mask
}
pub(crate) fn calculate_frame_adjustment(frame_size: u32, addend: u32, alignment: u32) -> u32 {
let total = frame_size + addend;
(alignment - (total % alignment)) % alignment
}