Skip to content

Commit

Permalink
winch: Overhaul the internal ABI (bytecodealliance#7974)
Browse files Browse the repository at this point in the history
* winch: Overhaul the internal ABI

This change overhauls Winch's ABI. This means that as part of this change,  the default ABI now closely resembles Cranelift's ABI, particularly on the treatment of the VMContext. This change also fixes many wrong assumptions about trampolines, which are tied to how the previous ABI operated.

The main motivation behind this change is:

* To make it easier to integrate Winch-generated functions with Wasmtime
* Fix fuzz bugs related to imports
* Solidify the implementation regarding the usage of a pinned register to hold the VMContext value throughout the lifetime of a function.


The previous implementation had the following characteristics, and wrong assumptions):

* Assumed that nternal functions don't receive a caller or callee VMContexts as parameters.
* Worked correctly in the following scenarios:
    * `Wasm -> Native`: since we can explicitly load the caller and callee `VMContext`, because we're
      calling a native import.
    * `(Native, Array) -> Wasm`: because the native signatures define a tuple of  `VMContext` as arguments.

* It didn't work in the following scenario:
   * `Wasm->Wasm`: When calling imports from another WebAssembly instance (via
      direct call or `call_indirect`. The previous implementation wrongly assumes
      that there should be a trampoline in this case, but there isn't. The code
      was generated by the same compiler, so the same ABI should be used in
      both functions, but it doesn't. 


This change introduces the following changes, which fix the previous assumptions and bugs:

* All internal functions declare a two extra pointer-sized parameters, which will hold the callee and caller `VMContext`s
* Use a pinned register that will be considered live through the lifetime of the function instead of pinning it at the trampoline level. The pinning explicitlly happens when entering the function body and no other assumptions are made from there on.
* Introduce the concept of special `ContextArgs` for function calls. This enum holds metadata about which context arguments are needed depending on the callee. The previous implementation of introducing register values at arbitrary locations in the value stack conflicts with the stack ordering principle which states that older values must *always* precede newer values. So we can't insert a register, because if a spill happens the order of the values will be wrong. 


Finally, given that this change also enables the `imports.wast` test suite, it also includes a fix to `global.{get, set}` instructions which didn't account entirely for imported globals. 


Resolved conflicts
Update Winch filetests

* Fix typos

* Use `get_wasm_local` and `get_frame_local` instead of `get_local` and `get_local_unchecked`

* Introduce `MAX_CONTEXT_ARGS` and use it in the trampoline to skip context arguments.
  • Loading branch information
saulecabrera authored Feb 21, 2024
1 parent 93f17e3 commit 0e98a8d
Show file tree
Hide file tree
Showing 698 changed files with 9,324 additions and 7,477 deletions.
1 change: 0 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
"elem",
"select",
"unreached_invalid",
"imports",
"linking",
]
.contains(&testname);
Expand Down
119 changes: 104 additions & 15 deletions winch/codegen/src/abi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
//!
//! # Default ABI
//! The Default ABI
//!
//! Winch uses a default internal ABI, for all internal functions.
//! This allows us to push the complexity of system ABI compliance to
//! the trampolines (not yet implemented). The default ABI treats all
//! allocatable registers as caller saved, which means that (i) all
//! register values in the Wasm value stack (which are normally
//! referred to as "live"), must be saved onto the machine stack (ii)
//! function prologues and epilogues don't store/restore other
//! registers more than the non-allocatable ones (e.g. rsp/rbp in
//! x86_64).
//! Winch uses a default ABI, for all internal functions. This allows
//! us to push the complexity of system ABI compliance to the trampolines. The
//! default ABI treats all allocatable registers as caller saved, which means
//! that (i) all register values in the Wasm value stack (which are normally
//! referred to as "live"), must be saved onto the machine stack (ii) function
//! prologues and epilogues don't store/restore other registers more than the
//! non-allocatable ones (e.g. rsp/rbp in x86_64).
//!
//! The calling convention in the default ABI, uses registers to a certain fixed
//! count for arguments and return values, and then the stack is used for all
//! additional arguments and return values. Aside from the parameters declared
//! in each WebAssembly function, Winch's ABI declares two extra parameters, to
//! hold the callee and caller `VMContext` pointers. A well-known `LocalSlot` is
//! reserved for the callee VMContext pointer and also a particular pinned
//! register is used to hold the value of the callee `VMContext`, which is
//! available throughout the lifetime of the function.
//!
//! The calling convention in the default ABI, uses registers to a
//! certain fixed count for arguments and return values, and then the
//! stack is used for all additional arguments.
//!
//! Generally the stack layout looks like:
//! +-------------------------------+
Expand All @@ -28,10 +32,10 @@
//! | SP |
//! +-------------------------------+----> SP @ Function prologue
//! | |
//! +-------------------------------+----> VMContext slot
//! | |
//! | |
//! | Stack slots |
//! | + `VMContext` slot |
//! | + dynamic space |
//! | |
//! | |
Expand Down Expand Up @@ -60,6 +64,84 @@ pub(super) enum ParamsOrReturns {
Returns,
}

/// Macro to get the pinned register holding the [VMContext].
macro_rules! vmctx {
($m:ident) => {
<$m::ABI as ABI>::vmctx_reg()
};
}

pub(crate) use vmctx;

/// Constructs an [ABISig] using Winch's ABI.
pub(crate) fn wasm_sig<A: ABI>(ty: &WasmFuncType) -> ABISig {
// 6 is used semi-arbitrarily here, we can modify as we see fit.
let mut params: SmallVec<[WasmValType; 6]> = SmallVec::new();
params.extend_from_slice(&vmctx_types::<A>());
params.extend_from_slice(ty.params());

A::sig_from(&params, ty.returns(), &CallingConvention::Default)
}

/// Returns the callee and caller [VMContext] types.
pub(crate) fn vmctx_types<A: ABI>() -> [WasmValType; 2] {
[A::ptr_type(), A::ptr_type()]
}

/// Returns an [ABISig] for the array calling convention.
/// The signature looks like:
/// ```ignore
/// unsafe extern "C" fn(
/// callee_vmctx: *mut VMOpaqueContext,
/// caller_vmctx: *mut VMOpaqueContext,
/// values_ptr: *mut ValRaw,
/// values_len: usize,
/// )
/// ```
pub(crate) fn array_sig<A: ABI>(call_conv: &CallingConvention) -> ABISig {
let params = [A::ptr_type(), A::ptr_type(), A::ptr_type(), A::ptr_type()];
A::sig_from(&params, &[], call_conv)
}

/// Returns an [ABISig] that follows a variation of the system's
/// calling convention.
/// The main difference between the flavor of the returned signature
/// and the vanilla signature is how multiple values are returned.
/// Multiple returns are handled following Wasmtime's expectations:
/// * A single value is returned via a register according to the calling
/// convention.
/// * More than one values are returned via a return pointer.
/// These variations look like:
///
/// Single return value.
///
/// ```ignore
/// unsafe extern "C" fn(
/// callee_vmctx: *mut VMOpaqueContext,
/// caller_vmctx: *mut VMOpaqueContext,
/// // rest of parameters
/// ) -> // single result
/// ```
///
/// Multiple return values.
///
/// ```ignore
/// unsafe extern "C" fn(
/// callee_vmctx: *mut VMOpaqueContext,
/// caller_vmctx: *mut VMOpaqueContext,
/// // rest of parameters
/// retptr: *mut (), // 2+ results
/// ) -> // first result
/// ```
pub(crate) fn native_sig<A: ABI>(ty: &WasmFuncType, call_conv: &CallingConvention) -> ABISig {
// 6 is used semi-arbitrarily here, we can modify as we see fit.
let mut params: SmallVec<[WasmValType; 6]> = SmallVec::new();
params.extend_from_slice(&vmctx_types::<A>());
params.extend_from_slice(ty.params());

A::sig_from(&params, ty.returns(), call_conv)
}

/// Trait implemented by a specific ISA and used to provide
/// information about alignment, parameter passing, usage of
/// specific registers, etc.
Expand Down Expand Up @@ -140,6 +222,13 @@ pub(crate) trait ABI {

/// Returns the size in bits of the given [`WasmType`].
fn sizeof_bits(ty: &WasmValType) -> u8;

/// The target pointer size represented as [WasmValType].
fn ptr_type() -> WasmValType {
// Defaulting to 64, since we currently only support 64-bit
// architectures.
WasmValType::I64
}
}

/// ABI-specific representation of function argument or result.
Expand Down Expand Up @@ -309,7 +398,7 @@ impl ABIResults {
/// Creates [`ABIResults`] from a slice of `WasmType`.
/// This function maps the given return types to their ABI specific
/// representation. It does so, by iterating over them and applying the
/// given `map` closure. The map closure takes a [WasmType], maps its ABI
/// given `map` closure. The map closure takes a [WasmValType], maps its ABI
/// representation, according to the calling convention. In the case of
/// results, one result is stored in registers and the rest at particular
/// offsets in the stack.
Expand Down
Loading

0 comments on commit 0e98a8d

Please sign in to comment.