wasmtime: Modify WasmTy and WasmRet for future {extern,func}ref support

This commit is contained in:
Nick Fitzgerald
2020-07-07 09:48:17 -07:00
parent bef1b87be0
commit 44e6fae29c

View File

@@ -193,17 +193,26 @@ macro_rules! getters {
unsafe extern "C" fn( unsafe extern "C" fn(
*mut VMContext, *mut VMContext,
*mut VMContext, *mut VMContext,
$($args,)* $( $args::Abi, )*
) -> R, ) -> R::Abi,
>(anyfunc.as_ref().func_ptr.as_ptr()); >(anyfunc.as_ref().func_ptr.as_ptr());
let mut ret = None; let mut ret = None;
$(let $args = $args.into_abi();)*
let weak_store = instance.store.weak();
let weak_store = WeakStore(&weak_store);
$( let $args = $args.into_abi_for_arg(weak_store); )*
invoke_wasm_and_catch_traps(anyfunc.as_ref().vmctx, &instance.store, || { invoke_wasm_and_catch_traps(anyfunc.as_ref().vmctx, &instance.store, || {
ret = Some(fnptr(anyfunc.as_ref().vmctx, ptr::null_mut(), $($args,)*)); ret = Some(fnptr(
anyfunc.as_ref().vmctx,
ptr::null_mut(),
$( $args, )*
));
})?; })?;
Ok(ret.unwrap()) Ok(R::from_abi(ret.unwrap(), weak_store))
} }
}) })
} }
@@ -783,6 +792,12 @@ pub(crate) fn invoke_wasm_and_catch_traps(
} }
} }
// Public (but hidden) wrapper around a `Weak<StoreInner>` so that we can use it
// in public (but hidden) trait methods.
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct WeakStore<'a>(&'a Weak<StoreInner>);
/// A trait implemented for types which can be arguments to closures passed to /// A trait implemented for types which can be arguments to closures passed to
/// [`Func::wrap`] and friends. /// [`Func::wrap`] and friends.
/// ///
@@ -791,160 +806,40 @@ pub(crate) fn invoke_wasm_and_catch_traps(
/// stable over time. /// stable over time.
/// ///
/// For more information see [`Func::wrap`] /// For more information see [`Func::wrap`]
pub unsafe trait WasmTy: Copy { pub unsafe trait WasmTy {
// The raw ABI representation of this type inside Wasm.
#[doc(hidden)]
type Abi: Copy;
// Convert this value into its ABI representation, when passing a value into
// Wasm as an argument.
#[doc(hidden)]
fn into_abi_for_arg<'a>(self, store: WeakStore<'a>) -> Self::Abi;
// Convert from the raw ABI representation back into `Self`, when receiving
// a value from Wasm.
//
// Safety: The abi value *must* have be valid for this type (e.g. for
// `externref`, it must be a valid raw `VMExternRef` pointer, not some
// random, dangling pointer).
#[doc(hidden)]
unsafe fn from_abi<'a>(abi: Self::Abi, store: WeakStore<'a>) -> Self;
// Add this type to the given vec of expected valtypes.
#[doc(hidden)] #[doc(hidden)]
fn push(dst: &mut Vec<ValType>); fn push(dst: &mut Vec<ValType>);
// Does the next valtype(s) match this type?
#[doc(hidden)] #[doc(hidden)]
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>; fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
// Load this type's raw ABI representation from an args array.
#[doc(hidden)] #[doc(hidden)]
unsafe fn load(ptr: &mut *const u128) -> Self; unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi;
// Store this type's raw ABI representation into an args array.
#[doc(hidden)] #[doc(hidden)]
unsafe fn store(abi: Self, ptr: *mut u128); unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128);
}
unsafe impl WasmTy for () {
fn push(_dst: &mut Vec<ValType>) {}
fn matches(_tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
Ok(())
}
#[inline]
unsafe fn load(_ptr: &mut *const u128) -> Self {}
#[inline]
unsafe fn store(_abi: Self, _ptr: *mut u128) {}
}
unsafe impl WasmTy for i32 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::I32);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::I32),
"Type mismatch, expected i32, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = **ptr as Self;
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi as u128;
}
}
unsafe impl WasmTy for u32 {
fn push(dst: &mut Vec<ValType>) {
<i32 as WasmTy>::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<i32 as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
<i32 as WasmTy>::load(ptr) as Self
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
<i32 as WasmTy>::store(abi as i32, ptr)
}
}
unsafe impl WasmTy for i64 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::I64);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::I64),
"Type mismatch, expected i64, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = **ptr as Self;
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi as u128;
}
}
unsafe impl WasmTy for u64 {
fn push(dst: &mut Vec<ValType>) {
<i64 as WasmTy>::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<i64 as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
<i64 as WasmTy>::load(ptr) as Self
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
<i64 as WasmTy>::store(abi as i64, ptr)
}
}
unsafe impl WasmTy for f32 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::F32);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::F32),
"Type mismatch, expected f32, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = f32::from_bits(**ptr as u32);
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi.to_bits() as u128;
}
}
unsafe impl WasmTy for f64 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::F64);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::F64),
"Type mismatch, expected f64, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = f64::from_bits(**ptr as u64);
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi.to_bits() as u128;
}
} }
/// A trait implemented for types which can be returned from closures passed to /// A trait implemented for types which can be returned from closures passed to
@@ -956,64 +851,362 @@ unsafe impl WasmTy for f64 {
/// ///
/// For more information see [`Func::wrap`] /// For more information see [`Func::wrap`]
pub unsafe trait WasmRet { pub unsafe trait WasmRet {
// Same as `WasmTy::Abi`.
#[doc(hidden)] #[doc(hidden)]
type Abi; type Abi: Copy;
// Similar to `WasmTy::into_abi_for_arg` but used when host code is
// returning a value into Wasm, rather than host code passing an argument to
// a Wasm call. Unlike `into_abi_for_arg`, implementors of this method can
// raise traps, which means that callers must ensure that
// `invoke_wasm_and_catch_traps` is on the stack, and therefore this method
// is unsafe.
#[doc(hidden)]
unsafe fn into_abi_for_ret<'a>(self, store: WeakStore<'a>) -> Self::Abi;
// Same as `WasmTy::from_abi`.
#[doc(hidden)]
unsafe fn from_abi<'a>(abi: Self::Abi, store: WeakStore<'a>) -> Self;
// Same as `WasmTy::push`.
#[doc(hidden)] #[doc(hidden)]
fn push(dst: &mut Vec<ValType>); fn push(dst: &mut Vec<ValType>);
// Same as `WasmTy::matches`.
#[doc(hidden)] #[doc(hidden)]
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>; fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
// Same as `WasmTy::load_from_args`.
#[doc(hidden)] #[doc(hidden)]
fn into_abi(self) -> Self::Abi; unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi;
// Same as `WasmTy::store_to_args`.
#[doc(hidden)] #[doc(hidden)]
unsafe fn store(abi: Self::Abi, ptr: *mut u128); unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128);
} }
unsafe impl<T: WasmTy> WasmRet for T { unsafe impl WasmTy for () {
type Abi = T; type Abi = Self;
fn push(dst: &mut Vec<ValType>) {
T::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> { #[inline]
T::matches(tys) fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {}
#[inline]
unsafe fn from_abi<'a>(_abi: Self::Abi, _store: WeakStore<'a>) -> Self {}
fn push(_dst: &mut Vec<ValType>) {}
fn matches(_tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
Ok(())
} }
#[inline] #[inline]
fn into_abi(self) -> Self::Abi { unsafe fn load_from_args(_ptr: &mut *const u128) -> Self::Abi {}
#[inline]
unsafe fn store_to_args(_abi: Self::Abi, _ptr: *mut u128) {}
}
unsafe impl WasmTy for i32 {
type Abi = Self;
#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self self
} }
#[inline] #[inline]
unsafe fn store(abi: Self::Abi, ptr: *mut u128) { unsafe fn from_abi<'a>(abi: Self::Abi, _store: WeakStore<'a>) -> Self {
T::store(abi, ptr); abi
}
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::I32);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::I32),
"Type mismatch, expected i32, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
let ret = **ptr as Self;
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
*ptr = abi as u128;
} }
} }
unsafe impl<T: WasmTy> WasmRet for Result<T, Trap> { unsafe impl WasmTy for u32 {
type Abi = T; type Abi = <i32 as WasmTy>::Abi;
#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self as i32
}
#[inline]
unsafe fn from_abi<'a>(abi: Self::Abi, _store: WeakStore<'a>) -> Self {
abi as Self
}
fn push(dst: &mut Vec<ValType>) { fn push(dst: &mut Vec<ValType>) {
T::push(dst) <i32 as WasmTy>::push(dst)
} }
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> { fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
T::matches(tys) <i32 as WasmTy>::matches(tys)
} }
#[inline] #[inline]
fn into_abi(self) -> Self::Abi { unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
<i32 as WasmTy>::load_from_args(ptr)
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
<i32 as WasmTy>::store_to_args(abi, ptr)
}
}
unsafe impl WasmTy for i64 {
type Abi = Self;
#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
}
#[inline]
unsafe fn from_abi<'a>(abi: Self::Abi, _store: WeakStore<'a>) -> Self {
abi
}
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::I64);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::I64),
"Type mismatch, expected i64, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
let ret = **ptr as Self;
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
*ptr = abi as u128;
}
}
unsafe impl WasmTy for u64 {
type Abi = <i64 as WasmTy>::Abi;
#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self as i64
}
#[inline]
unsafe fn from_abi<'a>(abi: Self::Abi, _store: WeakStore<'a>) -> Self {
abi as Self
}
fn push(dst: &mut Vec<ValType>) {
<i64 as WasmTy>::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<i64 as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
<i64 as WasmTy>::load_from_args(ptr)
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
<i64 as WasmTy>::store_to_args(abi, ptr)
}
}
unsafe impl WasmTy for f32 {
type Abi = Self;
#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
}
#[inline]
unsafe fn from_abi<'a>(abi: Self::Abi, _store: WeakStore<'a>) -> Self {
abi
}
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::F32);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::F32),
"Type mismatch, expected f32, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
let ret = f32::from_bits(**ptr as u32);
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
*ptr = abi.to_bits() as u128;
}
}
unsafe impl WasmTy for f64 {
type Abi = Self;
#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
}
#[inline]
unsafe fn from_abi<'a>(abi: Self::Abi, _store: WeakStore<'a>) -> Self {
abi
}
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::F64);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::F64),
"Type mismatch, expected f64, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
let ret = f64::from_bits(**ptr as u64);
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
*ptr = abi.to_bits() as u128;
}
}
unsafe impl<T> WasmRet for T
where
T: WasmTy,
{
type Abi = <T as WasmTy>::Abi;
#[inline]
unsafe fn into_abi_for_ret<'a>(self, store: WeakStore<'a>) -> Self::Abi {
<Self as WasmTy>::into_abi_for_arg(self, store)
}
#[inline]
unsafe fn from_abi<'a>(abi: Self::Abi, store: WeakStore<'a>) -> Self {
<Self as WasmTy>::from_abi(abi, store)
}
#[inline]
fn push(dst: &mut Vec<ValType>) {
<Self as WasmTy>::push(dst)
}
#[inline]
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<Self as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
<Self as WasmTy>::load_from_args(ptr)
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
<Self as WasmTy>::store_to_args(abi, ptr)
}
}
unsafe impl<T> WasmRet for Result<T, Trap>
where
T: WasmTy,
{
type Abi = <T as WasmTy>::Abi;
#[inline]
unsafe fn into_abi_for_ret<'a>(self, store: WeakStore<'a>) -> Self::Abi {
match self { match self {
Ok(val) => return T::into_abi(val), Ok(val) => return <T as WasmTy>::into_abi_for_arg(val, store),
Err(trap) => handle_trap(trap), Err(trap) => handle_trap(trap),
} }
fn handle_trap(trap: Trap) -> ! { unsafe fn handle_trap(trap: Trap) -> ! {
unsafe { raise_user_trap(trap.into()) } raise_user_trap(trap.into())
} }
} }
#[inline] #[inline]
unsafe fn store(abi: Self::Abi, ptr: *mut u128) { unsafe fn from_abi<'a>(abi: Self::Abi, store: WeakStore<'a>) -> Self {
T::store(abi, ptr); Ok(<T as WasmTy>::from_abi(abi, store))
}
fn push(dst: &mut Vec<ValType>) {
<T as WasmTy>::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<T as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi {
<T as WasmTy>::load_from_args(ptr)
}
#[inline]
unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) {
<T as WasmTy>::store_to_args(abi, ptr);
} }
} }
@@ -1141,40 +1334,53 @@ macro_rules! impl_into_func {
R: WasmRet, R: WasmRet,
{ {
fn into_func(self, store: &Store) -> Func { fn into_func(self, store: &Store) -> Func {
// Note that this shim's ABI must match that expected by /// This shim is called by Wasm code, constructs a `Caller`,
// cranelift, since cranelift is generating raw function calls /// calls the wrapped host function, and returns the translated
// directly to this function. /// result back to Wasm.
unsafe extern "C" fn shim<F, $($args,)* R>( ///
/// Note that this shim's ABI must *exactly* match that expected
/// by Cranelift, since Cranelift is generating raw function
/// calls directly to this function.
unsafe extern "C" fn wasm_to_host_shim<F, $($args,)* R>(
vmctx: *mut VMContext, vmctx: *mut VMContext,
caller_vmctx: *mut VMContext, caller_vmctx: *mut VMContext,
$($args: $args,)* $( $args: $args::Abi, )*
) -> R::Abi ) -> R::Abi
where where
F: Fn(Caller<'_>, $($args),*) -> R + 'static, F: Fn(Caller<'_>, $( $args ),*) -> R + 'static,
$($args: WasmTy,)* $( $args: WasmTy, )*
R: WasmRet, R: WasmRet,
{ {
let ret = {
let state = (*vmctx).host_state(); let state = (*vmctx).host_state();
// Double-check ourselves in debug mode, but we control // Double-check ourselves in debug mode, but we control
// the `Any` here so an unsafe downcast should also // the `Any` here so an unsafe downcast should also
// work. // work.
debug_assert!(state.is::<(F, Weak<StoreInner>)>()); debug_assert!(state.is::<(F, Weak<StoreInner>)>());
let (func, store) = &*(state as *const _ as *const (F, Weak<StoreInner>)); let (func, store) = &*(state as *const _ as *const (F, Weak<StoreInner>));
let weak_store = WeakStore(store);
let ret = {
panic::catch_unwind(AssertUnwindSafe(|| { panic::catch_unwind(AssertUnwindSafe(|| {
func( func(
Caller { store, caller_vmctx }, Caller { store, caller_vmctx },
$($args,)* $( $args::from_abi($args, weak_store), )*
) )
})) }))
}; };
match ret { match ret {
Ok(ret) => ret.into_abi(), Ok(ret) => ret.into_abi_for_ret(weak_store),
Err(panic) => wasmtime_runtime::resume_panic(panic), Err(panic) => wasmtime_runtime::resume_panic(panic),
} }
} }
unsafe extern "C" fn trampoline<$($args,)* R>( /// This trampoline allows host code to indirectly call the
/// wrapped function (e.g. via `Func::call` on a `funcref` that
/// happens to reference our wrapped function).
///
/// It reads the arguments out of the incoming `args` array,
/// calls the given function pointer, and then stores the result
/// back into the `args` array.
unsafe extern "C" fn host_trampoline<$($args,)* R>(
callee_vmctx: *mut VMContext, callee_vmctx: *mut VMContext,
caller_vmctx: *mut VMContext, caller_vmctx: *mut VMContext,
ptr: *const VMFunctionBody, ptr: *const VMFunctionBody,
@@ -1189,14 +1395,14 @@ macro_rules! impl_into_func {
unsafe extern "C" fn( unsafe extern "C" fn(
*mut VMContext, *mut VMContext,
*mut VMContext, *mut VMContext,
$($args,)* $( $args::Abi, )*
) -> R::Abi, ) -> R::Abi,
>(ptr); >(ptr);
let mut _next = args as *const u128; let mut _next = args as *const u128;
$(let $args = $args::load(&mut _next);)* $( let $args = $args::load_from_args(&mut _next); )*
let ret = ptr(callee_vmctx, caller_vmctx, $($args),*); let ret = ptr(callee_vmctx, caller_vmctx, $( $args ),*);
R::store(ret, args); R::store_to_args(ret, args);
} }
let mut _args = Vec::new(); let mut _args = Vec::new();
@@ -1204,13 +1410,14 @@ macro_rules! impl_into_func {
let mut ret = Vec::new(); let mut ret = Vec::new();
R::push(&mut ret); R::push(&mut ret);
let ty = FuncType::new(_args.into(), ret.into()); let ty = FuncType::new(_args.into(), ret.into());
let store_weak = store.weak(); let store_weak = store.weak();
let trampoline = trampoline::<$($args,)* R>; let trampoline = host_trampoline::<$($args,)* R>;
let (instance, export) = unsafe { let (instance, export) = unsafe {
crate::trampoline::generate_raw_func_export( crate::trampoline::generate_raw_func_export(
&ty, &ty,
std::slice::from_raw_parts_mut( std::slice::from_raw_parts_mut(
shim::<F, $($args,)* R> as *mut _, wasm_to_host_shim::<F, $($args,)* R> as *mut _,
0, 0,
), ),
trampoline, trampoline,
@@ -1219,6 +1426,7 @@ macro_rules! impl_into_func {
) )
.expect("failed to generate export") .expect("failed to generate export")
}; };
Func { Func {
instance, instance,
export, export,