use crate::codegen::ptr_type_from_ptr_size;
use crate::isa::{reg::Reg, CallingConvention};
use crate::masm::{OperandSize, SPOffset};
use smallvec::SmallVec;
use std::collections::HashSet;
use std::ops::{Add, BitAnd, Not, Sub};
use wasmtime_environ::{WasmFuncType, WasmHeapType, WasmRefType, WasmType};
pub(crate) mod local;
pub(crate) use local::*;
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub(super) enum ParamsOrReturns {
Params,
Returns,
}
pub(crate) trait ABI {
fn stack_align() -> u8;
fn call_stack_align() -> u8;
fn arg_base_offset() -> u8;
fn ret_addr_offset() -> u8;
fn sig(wasm_sig: &WasmFuncType, call_conv: &CallingConvention) -> ABISig;
fn sig_from(params: &[WasmType], returns: &[WasmType], call_conv: &CallingConvention)
-> ABISig;
fn abi_results(returns: &[WasmType], call_conv: &CallingConvention) -> ABIResults;
fn word_bits() -> u32;
fn word_bytes() -> u32 {
Self::word_bits() / 8
}
fn scratch_reg() -> Reg;
fn float_scratch_reg() -> Reg;
fn scratch_for(ty: &WasmType) -> Reg {
match ty {
WasmType::I32
| WasmType::I64
| WasmType::Ref(WasmRefType {
heap_type: WasmHeapType::Func,
..
}) => Self::scratch_reg(),
WasmType::F32 | WasmType::F64 => Self::float_scratch_reg(),
_ => unimplemented!(),
}
}
fn fp_reg() -> Reg;
fn sp_reg() -> Reg;
fn vmctx_reg() -> Reg;
fn callee_saved_regs(call_conv: &CallingConvention) -> SmallVec<[(Reg, OperandSize); 18]>;
fn stack_slot_size() -> u32;
fn sizeof(ty: &WasmType) -> u32;
}
#[derive(Clone, Debug)]
pub enum ABIOperand {
Reg {
ty: WasmType,
reg: Reg,
size: u32,
},
Stack {
ty: WasmType,
offset: u32,
size: u32,
},
}
impl ABIOperand {
pub fn reg(reg: Reg, ty: WasmType, size: u32) -> Self {
Self::Reg { reg, ty, size }
}
pub fn stack_offset(offset: u32, ty: WasmType, 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!(),
}
}
pub fn get_reg(&self) -> Option<Reg> {
match *self {
ABIOperand::Reg { reg, .. } => Some(reg),
_ => None,
}
}
pub fn ty(&self) -> WasmType {
match *self {
ABIOperand::Reg { ty, .. } | ABIOperand::Stack { ty, .. } => ty,
}
}
}
#[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),
}
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!(),
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ABIResults {
operands: ABIOperands,
}
#[derive(Debug, Clone)]
pub(crate) struct ABIResultsData {
pub results: ABIResults,
pub ret_area: Option<RetArea>,
}
impl ABIResultsData {
pub fn wrap(results: ABIResults) -> Self {
Self {
results,
ret_area: None,
}
}
pub fn unwrap_ret_area(&self) -> &RetArea {
self.ret_area.as_ref().unwrap()
}
}
impl ABIResults {
pub fn from<F>(returns: &[WasmType], call_conv: &CallingConvention, mut map: F) -> Self
where
F: FnMut(&WasmType, u32) -> (ABIOperand, u32),
{
if returns.len() == 0 {
return Self::default();
}
type FoldTuple = (SmallVec<[ABIOperand; 6]>, HashSet<Reg>, u32);
let fold_impl = |(mut operands, mut regs, stack_bytes): FoldTuple, arg| {
let (operand, bytes) = map(arg, stack_bytes);
if operand.is_reg() {
regs.insert(operand.unwrap_reg());
}
operands.push(operand);
(operands, regs, bytes)
};
let (mut operands, regs, bytes): FoldTuple = if call_conv.is_default() {
returns
.iter()
.rev()
.fold((SmallVec::new(), HashSet::with_capacity(1), 0), fold_impl)
} else {
returns
.iter()
.fold((SmallVec::new(), HashSet::with_capacity(1), 0), fold_impl)
};
if call_conv.is_default() {
operands.reverse();
}
Self::new(ABIOperands {
inner: operands,
regs,
bytes,
})
}
pub fn new(operands: ABIOperands) -> Self {
Self { operands }
}
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()
}
#[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 has_stack_results(&self) -> bool {
self.operands.bytes > 0
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ABIParams {
operands: ABIOperands,
has_retptr: bool,
}
impl ABIParams {
pub fn from<F, A: ABI>(
params: &[WasmType],
initial_bytes: u32,
needs_stack_results: bool,
mut map: F,
) -> Self
where
F: FnMut(&WasmType, u32) -> (ABIOperand, u32),
{
if params.len() == 0 && !needs_stack_results {
return Self::with_bytes(initial_bytes);
}
let regiser_capacity = params.len().min(6);
let (mut operands, mut regs, mut stack_bytes): (
SmallVec<[ABIOperand; 6]>,
HashSet<Reg>,
u32,
) = params.iter().fold(
(
SmallVec::new(),
HashSet::with_capacity(regiser_capacity),
initial_bytes,
),
|(mut operands, mut regs, stack_bytes), arg| {
let (operand, bytes) = map(arg, stack_bytes);
if operand.is_reg() {
regs.insert(operand.unwrap_reg());
}
operands.push(operand);
(operands, regs, bytes)
},
);
let ptr_type = ptr_type_from_ptr_size(<A as ABI>::word_bytes() as u8);
if needs_stack_results {
let (operand, bytes) = map(&ptr_type, stack_bytes);
if operand.is_reg() {
regs.insert(operand.unwrap_reg());
}
operands.push(operand);
stack_bytes = bytes;
}
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
}
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, Default)]
pub(crate) struct ABISig {
pub params: ABIParams,
pub results: ABIResults,
pub regs: HashSet<Reg>,
}
impl ABISig {
pub fn new(params: ABIParams, results: ABIResults) -> Self {
let regs = params
.operands
.regs
.union(&results.operands.regs)
.copied()
.collect();
Self {
params,
results,
regs,
}
}
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.has_stack_results()
}
}
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
}