Skip to content

Commit

Permalink
winch: Implement new trampolines
Browse files Browse the repository at this point in the history
This change is a follow-up to
bytecodealliance#6262, in which the new
trampolines, described [here](https://github.com/bytecodealliance/rfcs/blob/main/accepted/tail-calls.md#new-trampolines-and-vmcallercheckedanyfunc-changes),
were introduced to Wasmtime.

This change, focuses on the `array-to-wasm`,
`native-to-wasm` and `wasm-to-native` trampolines to restore Winch's
working state prior to the introduction of the new trampolines. It's
worth noting that the new approach for trampolines make it easier to support
the `TypedFunc` API in Winch. Prior to the introduction of the new
trampolines, it was not obvious how to approach it.

This change also introduces a pinned register that will hold the
`VMContext` pointer, which is loaded in the `*-to-wasm`  trampolines;
the `VMContext`  register is a pre-requisite to this change to support
the `wasm-to-native` trampolines.

Lastly, with the introduction of the `VMContext` register and the
`wasm-to-native` trampolines, this change also introduces support for
calling function imports, which is a variation of the already existing
calls to locally defined functions.

prtest:full
  • Loading branch information
saulecabrera committed May 15, 2023
1 parent 1cbca5a commit 633fc06
Show file tree
Hide file tree
Showing 23 changed files with 697 additions and 242 deletions.
57 changes: 50 additions & 7 deletions crates/winch/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use wasmtime_environ::{
CompileError, DefinedFuncIndex, FilePos, FuncIndex, FunctionBodyData, FunctionLoc,
ModuleTranslation, ModuleTypes, PrimaryMap, Tunables, WasmFunctionInfo,
};
use winch_codegen::TargetIsa;
use winch_codegen::{TargetIsa, TrampolineKind};
use winch_environ::FuncEnv;

pub(crate) struct Compiler {
Expand Down Expand Up @@ -92,8 +92,22 @@ impl wasmtime_environ::Compiler for Compiler {
types: &ModuleTypes,
index: DefinedFuncIndex,
) -> Result<Box<dyn Any + Send>, CompileError> {
let _ = (translation, types, index);
todo!()
let index = translation.module.func_index(index);
let sig = translation.module.functions[index].signature;
let ty = &types[sig];
let wasm_ty = wasmparser::FuncType::new(
ty.params().iter().copied().map(Into::into),
ty.returns().iter().copied().map(Into::into),
);
let env = FuncEnv::new(&translation.module, translation.get_types(), &self.isa);
let buffer = self
.isa
.compile_trampoline(&wasm_ty, index.as_u32(), &env, TrampolineKind::ArrayToWasm)
.map_err(|e| CompileError::Codegen(format!("{:?}", e)))?;
let compiled_function =
CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());

Ok(Box::new(compiled_function))
}

fn compile_native_to_wasm_trampoline(
Expand All @@ -102,17 +116,46 @@ impl wasmtime_environ::Compiler for Compiler {
types: &ModuleTypes,
index: DefinedFuncIndex,
) -> Result<Box<dyn Any + Send>, CompileError> {
let _ = (translation, types, index);
todo!()
let index = translation.module.func_index(index);
let sig = translation.module.functions[index].signature;
let ty = &types[sig];
let wasm_ty = wasmparser::FuncType::new(
ty.params().iter().copied().map(Into::into),
ty.returns().iter().copied().map(Into::into),
);

let env = FuncEnv::new(&translation.module, translation.get_types(), &self.isa);
let buffer = self
.isa
.compile_trampoline(&wasm_ty, index.as_u32(), &env, TrampolineKind::NativeToWasm)
.map_err(|e| CompileError::Codegen(format!("{:?}", e)))?;

let compiled_function =
CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());

Ok(Box::new(compiled_function))
}

fn compile_wasm_to_native_trampoline(
&self,
translation: &ModuleTranslation<'_>,
wasm_func_ty: &wasmtime_environ::WasmFuncType,
) -> Result<Box<dyn Any + Send>, CompileError> {
let _ = (translation, wasm_func_ty);
todo!()
let wasm_ty = wasmparser::FuncType::new(
wasm_func_ty.params().iter().copied().map(Into::into),
wasm_func_ty.returns().iter().copied().map(Into::into),
);

let env = FuncEnv::new(&translation.module, translation.get_types(), &self.isa);
let buffer = self
.isa
.compile_trampoline(&wasm_ty, 1u32, &env, TrampolineKind::WasmToNative)
.map_err(|e| CompileError::Codegen(format!("{:?}", e)))?;

let compiled_function =
CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment());

Ok(Box::new(compiled_function))
}

fn append_code(
Expand Down
132 changes: 76 additions & 56 deletions tests/all/winch.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,13 @@
use anyhow::Result;
use wasmtime::*;

#[test]
#[ignore]
fn compiles_with_winch() -> Result<()> {
let mut c = Config::new();

c.strategy(Strategy::Winch);

let engine = Engine::new(&c)?;

// Winch only supports a very basic function signature for now while it's being developed.
let test_mod = r#"
const MODULE: &'static str = r#"
(module
(import "" "" (func $add (param i32 i32) (result i32)))
(func $test (result i32)
(i32.const 42)
)
(export "test" (func $test))
)
"#;
let mut store = Store::new(&engine, ());

let module = Module::new(&engine, test_mod)?;

let instance = Instance::new(&mut store, &module, &[])?;

let f = instance
.get_func(&mut store, "test")
.ok_or(anyhow::anyhow!("test function not found"))?;

let mut returns = vec![Val::null(); 1];

// Winch doesn't support calling typed functions at the moment.
f.call(&mut store, &[], &mut returns)?;

assert_eq!(returns.len(), 1);
assert_eq!(returns[0].unwrap_i32(), 42);

Ok(())
}

#[test]
#[ignore]
fn compiles_with_winch_stack_arguments() -> Result<()> {
let mut c = Config::new();

c.strategy(Strategy::Winch);

let engine = Engine::new(&c)?;

// Winch only supports a very basic function signature for now while it's being developed.
let test_mod = r#"
(module
(func $sum10 (param $arg_1 i32) (param $arg_2 i32) (param $arg_3 i32) (param $arg_4 i32) (param $arg_5 i32) (param $arg_6 i32) (param $arg_7 i32) (param $arg_8 i32) (param $arg_9 i32) (param $arg_10 i32) (result i32)
local.get $arg_1
local.get $arg_2
Expand All @@ -73,30 +28,95 @@ fn compiles_with_winch_stack_arguments() -> Result<()> {
i32.add
local.get $arg_10
i32.add)
(func $call_add (param i32 i32) (result i32)
(local.get 0)
(local.get 1)
(call $add))
(export "42" (func $test))
(export "sum10" (func $sum10))
(export "call_add" (func $call_add))
)
"#;

fn add_fn(store: impl AsContextMut) -> Func {
Func::wrap(store, |a: i32, b: i32| a + b)
}

#[test]
fn array_to_wasm() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MODULE)?;

let module = Module::new(&engine, test_mod)?;
let add_fn = add_fn(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[add_fn.into()])?;

let constant = instance
.get_func(&mut store, "42")
.ok_or(anyhow::anyhow!("test function not found"))?;
let mut returns = vec![Val::null(); 1];
constant.call(&mut store, &[], &mut returns)?;

let instance = Instance::new(&mut store, &module, &[])?;
assert_eq!(returns.len(), 1);
assert_eq!(returns[0].unwrap_i32(), 42);

let f = instance
let sum = instance
.get_func(&mut store, "sum10")
.ok_or(anyhow::anyhow!("sum10 function not found"))?;

let mut returns = vec![Val::null(); 1];

// create a new Val array with ten 1s
let args = vec![Val::I32(1); 10];

// Winch doesn't support calling typed functions at the moment.
f.call(&mut store, &args, &mut returns)?;
sum.call(&mut store, &args, &mut returns)?;

assert_eq!(returns.len(), 1);
assert_eq!(returns[0].unwrap_i32(), 10);

Ok(())
}

#[test]
fn native_to_wasm() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MODULE)?;

let add_fn = add_fn(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[add_fn.into()])?;

let f = instance.get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), i32>(
&mut store, "sum10",
)?;

let args = (1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
let result = f.call(&mut store, args)?;

assert_eq!(result, 10);

Ok(())
}

#[test]
fn wasm_to_native() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MODULE)?;

let add_fn = add_fn(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[add_fn.into()])?;

let f = instance.get_typed_func::<(i32, i32), i32>(&mut store, "call_add")?;

let args = (41, 1);
let result = f.call(&mut store, args)?;

assert_eq!(result, 42);

Ok(())
}
8 changes: 8 additions & 0 deletions winch/codegen/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
//! | |
//! | |
//! | Stack slots |
//! | + `VMContext` slot |
//! | + dynamic space |
//! | |
//! | |
Expand Down Expand Up @@ -78,6 +79,13 @@ pub(crate) trait ABI {
/// Returns the designated scratch register.
fn scratch_reg() -> Reg;

/// Returns the frame pointer register.
fn fp_reg() -> Reg;

/// Returns the pinned register used to hold
/// the `VMContext`.
fn vmctx_reg() -> Reg;

/// Returns the callee-saved registers for the given
/// calling convention.
fn callee_saved_regs(call_conv: &CallingConvention) -> SmallVec<[Reg; 9]>;
Expand Down
Loading

0 comments on commit 633fc06

Please sign in to comment.