Merge branch 'master' into feat-runtime-core-field-offset

This commit is contained in:
Ivan Enderlin
2019-11-13 13:22:41 +01:00
committed by GitHub
13 changed files with 677 additions and 249 deletions

View File

@ -15,7 +15,11 @@ use crate::{
},
vm,
};
use std::{fmt::Debug, slice};
use std::{
fmt::Debug,
ptr::{self, NonNull},
slice,
};
/// Size of the array for internal instance usage
pub const INTERNALS_SIZE: usize = 256;
@ -383,9 +387,9 @@ impl LocalBacking {
vmctx,
),
LocalOrImport::Import(imported_func_index) => {
let vm::ImportedFunc { func, vmctx } =
let vm::ImportedFunc { func, func_ctx } =
imports.vm_functions[imported_func_index];
(func, vmctx)
(func, unsafe { func_ctx.as_ref() }.vmctx.as_ptr())
}
};
@ -416,9 +420,9 @@ impl LocalBacking {
vmctx,
),
LocalOrImport::Import(imported_func_index) => {
let vm::ImportedFunc { func, vmctx } =
let vm::ImportedFunc { func, func_ctx } =
imports.vm_functions[imported_func_index];
(func, vmctx)
(func, unsafe { func_ctx.as_ref() }.vmctx.as_ptr())
}
};
@ -546,6 +550,15 @@ impl ImportBacking {
}
}
impl Drop for ImportBacking {
fn drop(&mut self) {
// Properly drop the `vm::FuncCtx` in `vm::ImportedFunc`.
for (_imported_func_index, imported_func) in (*self.vm_functions).iter_mut() {
let _: Box<vm::FuncCtx> = unsafe { Box::from_raw(imported_func.func_ctx.as_ptr()) };
}
}
}
fn import_functions(
module: &ModuleInner,
imports: &ImportObject,
@ -569,6 +582,7 @@ fn import_functions(
let import =
imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name));
match import {
Some(Export::Function {
func,
@ -578,10 +592,28 @@ fn import_functions(
if *expected_sig == *signature {
functions.push(vm::ImportedFunc {
func: func.inner(),
vmctx: match ctx {
Context::External(ctx) => ctx,
Context::Internal => vmctx,
},
func_ctx: NonNull::new(Box::into_raw(Box::new(vm::FuncCtx {
// ^^^^^^^^ `vm::FuncCtx` is purposely leaked.
// It is dropped by the specific `Drop`
// implementation of `ImportBacking`.
vmctx: NonNull::new(match ctx {
Context::External(vmctx) => vmctx,
Context::ExternalWithEnv(vmctx_, _) => {
if vmctx_.is_null() {
vmctx
} else {
vmctx_
}
}
Context::Internal => vmctx,
})
.expect("`vmctx` must not be null."),
func_env: match ctx {
Context::ExternalWithEnv(_, func_env) => func_env,
_ => None,
},
})))
.unwrap(),
});
} else {
link_errors.push(LinkError::IncorrectImportSignature {
@ -610,8 +642,8 @@ fn import_functions(
None => {
if imports.allow_missing_functions {
functions.push(vm::ImportedFunc {
func: ::std::ptr::null(),
vmctx: ::std::ptr::null_mut(),
func: ptr::null(),
func_ctx: unsafe { NonNull::new_unchecked(ptr::null_mut()) }, // TODO: Non-sense…
});
} else {
link_errors.push(LinkError::ImportNotFound {

View File

@ -6,13 +6,18 @@ use crate::{
module::ModuleInner, table::Table, types::FuncSig, vm,
};
use indexmap::map::Iter as IndexMapIter;
use std::sync::Arc;
use std::{ptr::NonNull, sync::Arc};
/// A kind of Context.
#[derive(Debug, Copy, Clone)]
pub enum Context {
/// External context include a mutable pointer to `Ctx`.
External(*mut vm::Ctx),
/// External context with an environment include a mutable pointer
/// to `Ctx` and an optional non-null pointer to `FuncEnv`.
ExternalWithEnv(*mut vm::Ctx, Option<NonNull<vm::FuncEnv>>),
/// Internal context.
Internal,
}

View File

@ -113,9 +113,13 @@ impl Instance {
let ctx_ptr = match start_index.local_or_import(&instance.module.info) {
LocalOrImport::Local(_) => instance.inner.vmctx,
LocalOrImport::Import(imported_func_index) => {
instance.inner.import_backing.vm_functions[imported_func_index].vmctx
LocalOrImport::Import(imported_func_index) => unsafe {
instance.inner.import_backing.vm_functions[imported_func_index]
.func_ctx
.as_ref()
}
.vmctx
.as_ptr(),
};
let sig_index = *instance
@ -132,7 +136,7 @@ impl Instance {
.expect("wasm trampoline");
let start_func: Func<(), (), Wasm> =
unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, ctx_ptr) };
unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, None, ctx_ptr) };
start_func.call()?;
}
@ -199,9 +203,13 @@ impl Instance {
let ctx = match func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(_) => self.inner.vmctx,
LocalOrImport::Import(imported_func_index) => {
self.inner.import_backing.vm_functions[imported_func_index].vmctx
LocalOrImport::Import(imported_func_index) => unsafe {
self.inner.import_backing.vm_functions[imported_func_index]
.func_ctx
.as_ref()
}
.vmctx
.as_ptr(),
};
let func_wasm_inner = self
@ -210,20 +218,26 @@ impl Instance {
.get_trampoline(&self.module.info, sig_index)
.unwrap();
let func_ptr = match func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(local_func_index) => self
.module
.runnable_module
.get_func(&self.module.info, local_func_index)
.unwrap(),
LocalOrImport::Import(import_func_index) => NonNull::new(
self.inner.import_backing.vm_functions[import_func_index].func as *mut _,
)
.unwrap(),
let (func_ptr, func_env) = match func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(local_func_index) => (
self.module
.runnable_module
.get_func(&self.module.info, local_func_index)
.unwrap(),
None,
),
LocalOrImport::Import(import_func_index) => {
let imported_func = &self.inner.import_backing.vm_functions[import_func_index];
(
NonNull::new(imported_func.func as *mut _).unwrap(),
unsafe { imported_func.func_ctx.as_ref() }.func_env,
)
}
};
let typed_func: Func<Args, Rets, Wasm> =
unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, ctx) };
unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, func_env, ctx) };
Ok(typed_func)
} else {
@ -412,6 +426,7 @@ impl InstanceInner {
ctx: match ctx {
Context::Internal => Context::External(self.vmctx),
ctx @ Context::External(_) => ctx,
ctx @ Context::ExternalWithEnv(_, _) => ctx,
},
signature,
}
@ -454,15 +469,16 @@ impl InstanceInner {
),
LocalOrImport::Import(imported_func_index) => {
let imported_func = &self.import_backing.vm_functions[imported_func_index];
let func_ctx = unsafe { imported_func.func_ctx.as_ref() };
(
imported_func.func as *const _,
Context::External(imported_func.vmctx),
Context::ExternalWithEnv(func_ctx.vmctx.as_ptr(), func_ctx.func_env),
)
}
};
let signature = SigRegistry.lookup_signature_ref(&module.info.signatures[sig_index]);
// let signature = &module.info.signatures[sig_index];
(unsafe { FuncPointer::new(func_ptr) }, ctx, signature)
}
@ -581,9 +597,13 @@ fn call_func_with_index(
let ctx_ptr = match func_index.local_or_import(info) {
LocalOrImport::Local(_) => local_ctx,
LocalOrImport::Import(imported_func_index) => {
import_backing.vm_functions[imported_func_index].vmctx
LocalOrImport::Import(imported_func_index) => unsafe {
import_backing.vm_functions[imported_func_index]
.func_ctx
.as_ref()
}
.vmctx
.as_ptr(),
};
let wasm = runnable

View File

@ -192,7 +192,7 @@ where
Rets: WasmTypeList,
{
/// Conver to function pointer.
fn to_raw(&self) -> NonNull<vm::Func>;
fn to_raw(self) -> (NonNull<vm::Func>, Option<NonNull<vm::FuncEnv>>);
}
/// Represents a TrapEarly type.
@ -230,8 +230,9 @@ where
/// Represents a function that can be used by WebAssembly.
pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> {
inner: Inner,
f: NonNull<vm::Func>,
ctx: *mut vm::Ctx,
func: NonNull<vm::Func>,
func_env: Option<NonNull<vm::FuncEnv>>,
vmctx: *mut vm::Ctx,
_phantom: PhantomData<(&'a (), Args, Rets)>,
}
@ -245,20 +246,22 @@ where
{
pub(crate) unsafe fn from_raw_parts(
inner: Wasm,
f: NonNull<vm::Func>,
ctx: *mut vm::Ctx,
func: NonNull<vm::Func>,
func_env: Option<NonNull<vm::FuncEnv>>,
vmctx: *mut vm::Ctx,
) -> Func<'a, Args, Rets, Wasm> {
Func {
inner,
f,
ctx,
func,
func_env,
vmctx,
_phantom: PhantomData,
}
}
/// Get the underlying func pointer.
pub fn get_vm_func(&self) -> NonNull<vm::Func> {
self.f
self.func
}
}
@ -268,15 +271,18 @@ where
Rets: WasmTypeList,
{
/// Creates a new `Func`.
pub fn new<F, Kind>(f: F) -> Func<'a, Args, Rets, Host>
pub fn new<F, Kind>(func: F) -> Func<'a, Args, Rets, Host>
where
Kind: ExternalFunctionKind,
F: ExternalFunction<Kind, Args, Rets>,
{
let (func, func_env) = func.to_raw();
Func {
inner: Host(()),
f: f.to_raw(),
ctx: ptr::null_mut(),
func,
func_env,
vmctx: ptr::null_mut(),
_phantom: PhantomData,
}
}
@ -414,7 +420,7 @@ where
{
/// Call wasm function and return results.
pub fn call(&self, a: A) -> Result<Rets, RuntimeError> {
unsafe { <A as WasmTypeList>::call(a, self.f, self.inner, self.ctx) }
unsafe { <A as WasmTypeList>::call(a, self.func, self.inner, self.vmctx) }
}
}
@ -506,56 +512,113 @@ macro_rules! impl_traits {
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap,
FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap + 'static,
{
#[allow(non_snake_case)]
fn to_raw(&self) -> NonNull<vm::Func> {
if mem::size_of::<Self>() == 0 {
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x, )* Rets, Trap, FN>(
vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )*
) -> Rets::CStruct
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap,
{
let f: FN = unsafe { mem::transmute_copy(&()) };
fn to_raw(self) -> (NonNull<vm::Func>, Option<NonNull<vm::FuncEnv>>) {
// The `wrap` function is a wrapper around the
// imported function. It manages the argument passed
// to the imported function (in this case, the
// `vmctx` along with the regular WebAssembly
// arguments), and it manages the trapping.
//
// It is also required for the LLVM backend to be
// able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x, )* Rets, Trap, FN>(
vmctx: &vm::Ctx $( , $x: <$x as WasmExternType>::Native )*
) -> Rets::CStruct
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap,
{
// Get the pointer to this `wrap` function.
let self_pointer = wrap::<$( $x, )* Rets, Trap, FN> as *const vm::Func;
let err = match panic::catch_unwind(
panic::AssertUnwindSafe(
|| {
f(vmctx $( , WasmExternType::from_native($x) )* ).report()
}
)
) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
// Get the collection of imported functions.
let vm_imported_functions = unsafe { &(*vmctx.import_backing).vm_functions };
unsafe {
(&*vmctx.module).runnable_module.do_early_trap(err)
}
// Retrieve the `vm::FuncCtx`.
let mut func_ctx: NonNull<vm::FuncCtx> = vm_imported_functions
.iter()
.find_map(|(_, imported_func)| {
if imported_func.func == self_pointer {
Some(imported_func.func_ctx)
} else {
None
}
})
.expect("Import backing is not well-formed, cannot find `func_ctx`.");
let func_ctx = unsafe { func_ctx.as_mut() };
// Extract `vm::Ctx` from `vm::FuncCtx`. The
// pointer is always non-null.
let vmctx = unsafe { func_ctx.vmctx.as_mut() };
// Extract `vm::FuncEnv` from `vm::FuncCtx`.
let func_env = func_ctx.func_env;
let func: &FN = match func_env {
// The imported function is a regular
// function, a closure without a captured
// environment, or a closure with a captured
// environment.
Some(func_env) => unsafe {
let func: NonNull<FN> = func_env.cast();
&*func.as_ptr()
},
// This branch is supposed to be unreachable.
None => unreachable!()
};
// Catch unwind in case of errors.
let err = match panic::catch_unwind(
panic::AssertUnwindSafe(
|| {
func(vmctx $( , WasmExternType::from_native($x) )* ).report()
// ^^^^^ The imported function
// expects `vm::Ctx` as first
// argument; provide it.
}
)
) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
// At this point, there is an error that needs to
// be trapped.
unsafe {
(&*vmctx.module).runnable_module.do_early_trap(err)
}
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap()
} else {
assert_eq!(
mem::size_of::<Self>(),
mem::size_of::<usize>(),
"you cannot use a closure that captures state for `Func`."
);
NonNull::new(unsafe {
mem::transmute_copy::<_, *mut vm::Func>(self)
}).unwrap()
}
// Extract the captured environment of the imported
// function if any.
let func_env: Option<NonNull<vm::FuncEnv>> =
// `FN` is a function pointer, or a closure
// _without_ a captured environment.
if mem::size_of::<Self>() == 0 {
NonNull::new(&self as *const _ as *mut vm::FuncEnv)
}
// `FN` is a closure _with_ a captured
// environment.
else {
NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast)
};
(
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(),
func_env
)
}
}
@ -564,56 +627,110 @@ macro_rules! impl_traits {
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn($( $x, )*) -> Trap,
FN: Fn($( $x, )*) -> Trap + 'static,
{
#[allow(non_snake_case)]
fn to_raw(&self) -> NonNull<vm::Func> {
if mem::size_of::<Self>() == 0 {
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x, )* Rets, Trap, FN>(
vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )*
) -> Rets::CStruct
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn($( $x, )*) -> Trap,
{
let f: FN = unsafe { mem::transmute_copy(&()) };
fn to_raw(self) -> (NonNull<vm::Func>, Option<NonNull<vm::FuncEnv>>) {
// The `wrap` function is a wrapper around the
// imported function. It manages the argument passed
// to the imported function (in this case, only the
// regular WebAssembly arguments), and it manages the
// trapping.
//
// It is also required for the LLVM backend to be
// able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x, )* Rets, Trap, FN>(
vmctx: &vm::Ctx $( , $x: <$x as WasmExternType>::Native )*
) -> Rets::CStruct
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn($( $x, )*) -> Trap,
{
// Get the pointer to this `wrap` function.
let self_pointer = wrap::<$( $x, )* Rets, Trap, FN> as *const vm::Func;
let err = match panic::catch_unwind(
panic::AssertUnwindSafe(
|| {
f($( WasmExternType::from_native($x), )* ).report()
}
)
) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
// Get the collection of imported functions.
let vm_imported_functions = unsafe { &(*vmctx.import_backing).vm_functions };
unsafe {
(&*vmctx.module).runnable_module.do_early_trap(err)
}
// Retrieve the `vm::FuncCtx`.
let mut func_ctx: NonNull<vm::FuncCtx> = vm_imported_functions
.iter()
.find_map(|(_, imported_func)| {
if imported_func.func == self_pointer {
Some(imported_func.func_ctx)
} else {
None
}
})
.expect("Import backing is not well-formed, cannot find `func_ctx`.");
let func_ctx = unsafe { func_ctx.as_mut() };
// Extract `vm::Ctx` from `vm::FuncCtx`. The
// pointer is always non-null.
let vmctx = unsafe { func_ctx.vmctx.as_mut() };
// Extract `vm::FuncEnv` from `vm::FuncCtx`.
let func_env = func_ctx.func_env;
let func: &FN = match func_env {
// The imported function is a regular
// function, a closure without a captured
// environment, or a closure with a captured
// environment.
Some(func_env) => unsafe {
let func: NonNull<FN> = func_env.cast();
&*func.as_ptr()
},
// This branch is supposed to be unreachable.
None => unreachable!()
};
// Catch unwind in case of errors.
let err = match panic::catch_unwind(
panic::AssertUnwindSafe(
|| {
func($( WasmExternType::from_native($x), )* ).report()
}
)
) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
// At this point, there is an error that needs to
// be trapped.
unsafe {
(&*vmctx.module).runnable_module.do_early_trap(err)
}
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap()
} else {
assert_eq!(
mem::size_of::<Self>(),
mem::size_of::<usize>(),
"you cannot use a closure that captures state for `Func`."
);
NonNull::new(unsafe {
mem::transmute_copy::<_, *mut vm::Func>(self)
}).unwrap()
}
// Extract the captured environment of the imported
// function if any.
let func_env: Option<NonNull<vm::FuncEnv>> =
// `FN` is a function pointer, or a closure
// _without_ a captured environment.
if mem::size_of::<Self>() == 0 {
NonNull::new(&self as *const _ as *mut vm::FuncEnv)
}
// `FN` is a closure _with_ a captured
// environment.
else {
NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast)
};
(
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(),
func_env
)
}
}
@ -629,9 +746,9 @@ macro_rules! impl_traits {
unsafe {
<( $( $x ),* ) as WasmTypeList>::call(
( $( $x ),* ),
self.f,
self.func,
self.inner,
self.ctx
self.vmctx
)
}
}
@ -669,8 +786,11 @@ where
Inner: Kind,
{
fn to_export(&self) -> Export {
let func = unsafe { FuncPointer::new(self.f.as_ptr()) };
let ctx = Context::Internal;
let func = unsafe { FuncPointer::new(self.func.as_ptr()) };
let ctx = match self.func_env {
func_env @ Some(_) => Context::ExternalWithEnv(self.vmctx, func_env),
None => Context::Internal,
};
let signature = Arc::new(FuncSig::new(Args::types(), Rets::types()));
Export::Function {

View File

@ -523,24 +523,65 @@ impl Ctx {
}
}
enum InnerFunc {}
/// Used to provide type safety (ish) for passing around function pointers.
/// The typesystem ensures this cannot be dereferenced since an
/// empty enum cannot actually exist.
#[repr(C)]
pub struct Func(InnerFunc);
/// Represents a function pointer. It is mostly used in the
/// `typed_func` module within the `wrap` functions, to wrap imported
/// functions.
#[repr(transparent)]
pub struct Func(pub(self) *mut c_void);
/// An imported function, which contains the vmctx that owns this function.
/// Represents a function environment pointer, like a captured
/// environment of a closure. It is mostly used in the `typed_func`
/// module within the `wrap` functions, to wrap imported functions.
#[repr(transparent)]
pub struct FuncEnv(pub(self) *mut c_void);
/// Represents a function context. It is used by imported functions
/// only.
#[derive(Debug)]
#[repr(C)]
pub struct FuncCtx {
/// The `Ctx` pointer.
pub(crate) vmctx: NonNull<Ctx>,
/// A pointer to the function environment. It is used by imported
/// functions only to store the pointer to the real host function,
/// whether it is a regular function, or a closure with or without
/// a captured environment.
pub(crate) func_env: Option<NonNull<FuncEnv>>,
}
impl FuncCtx {
/// Offset to `vmctx`.
pub fn offset_vmctx() -> u8 {
0 * (mem::size_of::<usize>() as u8)
}
/// Offset to `func_env`.
pub fn offset_func_env() -> u8 {
1 * (mem::size_of::<usize>() as u8)
}
/// Size of a `FuncCtx`.
pub fn size() -> u8 {
mem::size_of::<Self>() as u8
}
}
/// An imported function is a function pointer associated to a
/// function context.
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ImportedFunc {
/// Const pointer to `Func`.
pub func: *const Func,
/// Mutable pointer to `Ctx`.
pub vmctx: *mut Ctx,
pub(crate) func: *const Func,
/// Mutable non-null pointer to `FuncCtx`.
pub(crate) func_ctx: NonNull<FuncCtx>,
}
// manually implemented because ImportedFunc contains raw pointers directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: review this, shouldn't `Ctx` be Send?))
// Manually implemented because ImportedFunc contains raw pointers
// directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO:
// review this, shouldn't `Ctx` be Send?))
unsafe impl Send for ImportedFunc {}
impl ImportedFunc {
@ -550,8 +591,8 @@ impl ImportedFunc {
0 * (mem::size_of::<usize>() as u8)
}
/// Offset to vmctx.
pub fn offset_vmctx() -> u8 {
/// Offset to func_ctx.
pub fn offset_func_ctx() -> u8 {
1 * (mem::size_of::<usize>() as u8)
}
@ -709,7 +750,9 @@ impl Anyfunc {
#[cfg(test)]
mod vm_offset_tests {
use super::{Anyfunc, Ctx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable};
use super::{
Anyfunc, Ctx, FuncCtx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable,
};
// Inspired by https://internals.rust-lang.org/t/discussion-on-offset-of/7440/2.
macro_rules! offset_of {
@ -844,6 +887,13 @@ mod vm_offset_tests {
);
}
#[test]
fn func_ctx() {
assert_eq!(FuncCtx::offset_vmctx() as usize, 0,);
assert_eq!(FuncCtx::offset_func_env() as usize, 8,);
}
#[test]
fn imported_func() {
assert_eq!(
@ -852,8 +902,8 @@ mod vm_offset_tests {
);
assert_eq!(
ImportedFunc::offset_vmctx() as usize,
offset_of!(ImportedFunc, vmctx),
ImportedFunc::offset_func_ctx() as usize,
offset_of!(ImportedFunc, func_ctx),
);
}