-
Notifications
You must be signed in to change notification settings - Fork 822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix memory leak in host function envs #1865
Changes from 9 commits
1eaea6e
e43d9d2
4ec8c9e
ca4736c
476e8d1
6e95c50
99e0357
be3c381
ec4d6e6
bd01d6a
f798f34
27f7e7e
62d15fa
6a21169
8081aae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,8 @@ pub use inner::{UnsafeMutableEnv, WithUnsafeMutableEnv}; | |
|
||
use std::cmp::max; | ||
use std::fmt; | ||
use wasmer_engine::{Export, ExportFunction}; | ||
use std::sync::Arc; | ||
use wasmer_engine::{Export, ExportFunction, ExportFunctionMetadata}; | ||
use wasmer_vm::{ | ||
raise_user_trap, resume_panic, wasmer_call_trampoline, VMCallerCheckedAnyfunc, | ||
VMDynamicFunctionContext, VMExportFunction, VMFunctionBody, VMFunctionEnvironment, | ||
|
@@ -87,23 +88,41 @@ impl Function { | |
where | ||
F: Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static, | ||
{ | ||
let dynamic_ctx = VMDynamicFunctionContext::from_context(VMDynamicFunctionWithoutEnv { | ||
func: Box::new(func), | ||
function_type: ty.clone(), | ||
}); | ||
let dynamic_ctx: VMDynamicFunctionContext<DynamicFunctionWithoutEnv> = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I bet this type annotation isn't required (regardless, I prefer having type annotation in this kind of code, so it's perfectly fine to keep it, just wondered whether it changes something). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not strictly no, I thought it was helpful for understanding what's happening with the unsafe block here though. Actually all these wrapper functions can be created by a helper function, so I should probably just do that |
||
VMDynamicFunctionContext::from_context(DynamicFunctionWithoutEnv { | ||
func: Arc::new(func), | ||
function_type: ty.clone(), | ||
}); | ||
// We don't yet have the address with the Wasm ABI signature. | ||
// The engine linker will replace the address with one pointing to a | ||
// generated dynamic trampoline. | ||
let address = std::ptr::null() as *const VMFunctionBody; | ||
let vmctx = VMFunctionEnvironment { | ||
host_env: Box::into_raw(Box::new(dynamic_ctx)) as *mut _, | ||
let host_env = Box::into_raw(Box::new(dynamic_ctx)) as *mut _; | ||
let vmctx = VMFunctionEnvironment { host_env }; | ||
let host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void = |ptr| { | ||
let duped_env: VMDynamicFunctionContext<DynamicFunctionWithoutEnv> = unsafe { | ||
let ptr: *mut VMDynamicFunctionContext<DynamicFunctionWithoutEnv> = ptr as _; | ||
let item: &VMDynamicFunctionContext<DynamicFunctionWithoutEnv> = &*ptr; | ||
item.clone() | ||
}; | ||
Box::into_raw(Box::new(duped_env)) as _ | ||
}; | ||
let host_env_drop_fn: fn(*mut std::ffi::c_void) = |ptr: *mut std::ffi::c_void| { | ||
unsafe { | ||
Box::from_raw(ptr as *mut VMDynamicFunctionContext<DynamicFunctionWithoutEnv>) | ||
}; | ||
}; | ||
|
||
Self { | ||
store: store.clone(), | ||
definition: FunctionDefinition::Host(HostFunctionDefinition { has_env: false }), | ||
exported: ExportFunction { | ||
import_init_function_ptr: None, | ||
metadata: Some(Arc::new(ExportFunctionMetadata::new( | ||
host_env, | ||
None, | ||
host_env_clone_fn, | ||
host_env_drop_fn, | ||
))), | ||
vm_function: VMExportFunction { | ||
address, | ||
kind: VMFunctionKind::Dynamic, | ||
|
@@ -143,30 +162,60 @@ impl Function { | |
F: Fn(&Env, &[Val]) -> Result<Vec<Val>, RuntimeError> + 'static, | ||
Env: Sized + WasmerEnv + 'static, | ||
{ | ||
let dynamic_ctx = VMDynamicFunctionContext::from_context(VMDynamicFunctionWithEnv { | ||
env: Box::new(env), | ||
func: Box::new(func), | ||
function_type: ty.clone(), | ||
}); | ||
let dynamic_ctx: VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>> = | ||
VMDynamicFunctionContext::from_context(DynamicFunctionWithEnv { | ||
env: { | ||
let e = Box::new(env); | ||
e | ||
}, | ||
func: Arc::new(func), | ||
function_type: ty.clone(), | ||
}); | ||
// We don't yet have the address with the Wasm ABI signature. | ||
// The engine linker will replace the address with one pointing to a | ||
// generated dynamic trampoline. | ||
let address = std::ptr::null() as *const VMFunctionBody; | ||
let vmctx = VMFunctionEnvironment { | ||
host_env: Box::into_raw(Box::new(dynamic_ctx)) as *mut _, | ||
}; | ||
// TODO: look into removing transmute by changing API type signatures | ||
let host_env = Box::into_raw(Box::new(dynamic_ctx)) as *mut _; | ||
let vmctx = VMFunctionEnvironment { host_env }; | ||
let import_init_function_ptr: fn(_, _) -> Result<(), _> = | ||
|ptr: *mut std::ffi::c_void, instance: *const std::ffi::c_void| { | ||
let ptr = ptr as *mut VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>>; | ||
unsafe { | ||
let env = &mut *ptr; | ||
let env: &mut Env = &mut *env.ctx.env; | ||
let instance = &*(instance as *const crate::Instance); | ||
Env::init_with_instance(env, instance) | ||
} | ||
}; | ||
let import_init_function_ptr = Some(unsafe { | ||
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>( | ||
Env::init_with_instance, | ||
import_init_function_ptr, | ||
) | ||
}); | ||
let host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void = |ptr| { | ||
let duped_env: VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>> = unsafe { | ||
let ptr: *mut VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>> = ptr as _; | ||
let item: &VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>> = &*ptr; | ||
item.clone() | ||
}; | ||
Box::into_raw(Box::new(duped_env)) as _ | ||
}; | ||
let host_env_drop_fn: fn(*mut std::ffi::c_void) = |ptr: *mut std::ffi::c_void| { | ||
unsafe { | ||
Box::from_raw(ptr as *mut VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>>) | ||
}; | ||
}; | ||
|
||
Self { | ||
store: store.clone(), | ||
definition: FunctionDefinition::Host(HostFunctionDefinition { has_env: true }), | ||
exported: ExportFunction { | ||
import_init_function_ptr, | ||
metadata: Some(Arc::new(ExportFunctionMetadata::new( | ||
host_env, | ||
import_init_function_ptr, | ||
host_env_clone_fn, | ||
host_env_drop_fn, | ||
))), | ||
vm_function: VMExportFunction { | ||
address, | ||
kind: VMFunctionKind::Dynamic, | ||
|
@@ -220,7 +269,7 @@ impl Function { | |
exported: ExportFunction { | ||
// TODO: figure out what's going on in this function: it takes an `Env` | ||
// param but also marks itself as not having an env | ||
import_init_function_ptr: None, | ||
metadata: None, | ||
vm_function: VMExportFunction { | ||
address, | ||
vmctx, | ||
|
@@ -274,10 +323,20 @@ impl Function { | |
// Wasm-defined functions have a `VMContext`. | ||
// In the case of Host-defined functions `VMContext` is whatever environment | ||
// the user want to attach to the function. | ||
let box_env = Box::new(env); | ||
let vmctx = VMFunctionEnvironment { | ||
host_env: Box::into_raw(box_env) as *mut _, | ||
let host_env = Box::into_raw(Box::new(env)) as *mut _; | ||
let vmctx = VMFunctionEnvironment { host_env }; | ||
let host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void = |ptr| { | ||
let duped_env = unsafe { | ||
let ptr: *mut Env = ptr as _; | ||
let item: &Env = &*ptr; | ||
item.clone() | ||
}; | ||
Box::into_raw(Box::new(duped_env)) as _ | ||
}; | ||
let host_env_drop_fn: fn(*mut std::ffi::c_void) = |ptr: *mut std::ffi::c_void| { | ||
unsafe { Box::from_raw(ptr as *mut Env) }; | ||
}; | ||
|
||
// TODO: look into removing transmute by changing API type signatures | ||
let import_init_function_ptr = Some(unsafe { | ||
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>( | ||
|
@@ -290,7 +349,12 @@ impl Function { | |
store: store.clone(), | ||
definition: FunctionDefinition::Host(HostFunctionDefinition { has_env: true }), | ||
exported: ExportFunction { | ||
import_init_function_ptr, | ||
metadata: Some(Arc::new(ExportFunctionMetadata::new( | ||
host_env, | ||
import_init_function_ptr, | ||
host_env_clone_fn, | ||
host_env_drop_fn, | ||
))), | ||
vm_function: VMExportFunction { | ||
address, | ||
kind: VMFunctionKind::Static, | ||
|
@@ -329,9 +393,20 @@ impl Function { | |
let address = function.address(); | ||
|
||
let box_env = Box::new(env); | ||
let vmctx = VMFunctionEnvironment { | ||
host_env: Box::into_raw(box_env) as *mut _, | ||
let host_env = Box::into_raw(box_env) as *mut _; | ||
let vmctx = VMFunctionEnvironment { host_env }; | ||
let host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void = |ptr| { | ||
let duped_env: Env = { | ||
let ptr: *mut Env = ptr as _; | ||
let item: &Env = &*ptr; | ||
item.clone() | ||
}; | ||
Box::into_raw(Box::new(duped_env)) as _ | ||
}; | ||
let host_env_drop_fn: fn(*mut std::ffi::c_void) = |ptr: *mut std::ffi::c_void| { | ||
Box::from_raw(ptr as *mut Env); | ||
}; | ||
|
||
let signature = function.ty(); | ||
// TODO: look into removing transmute by changing API type signatures | ||
let import_init_function_ptr = Some(std::mem::transmute::< | ||
|
@@ -343,7 +418,12 @@ impl Function { | |
store: store.clone(), | ||
definition: FunctionDefinition::Host(HostFunctionDefinition { has_env: true }), | ||
exported: ExportFunction { | ||
import_init_function_ptr, | ||
metadata: Some(Arc::new(ExportFunctionMetadata { | ||
host_env, | ||
host_env_clone_fn, | ||
host_env_drop_fn, | ||
import_init_function_ptr, | ||
})), | ||
vm_function: VMExportFunction { | ||
address, | ||
kind: VMFunctionKind::Static, | ||
|
@@ -712,13 +792,14 @@ pub(crate) trait VMDynamicFunction { | |
fn function_type(&self) -> &FunctionType; | ||
} | ||
|
||
pub(crate) struct VMDynamicFunctionWithoutEnv { | ||
#[derive(Clone)] | ||
pub(crate) struct DynamicFunctionWithoutEnv { | ||
#[allow(clippy::type_complexity)] | ||
func: Box<dyn Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static>, | ||
func: Arc<dyn Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static>, | ||
MarkMcCaskey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function_type: FunctionType, | ||
} | ||
|
||
impl VMDynamicFunction for VMDynamicFunctionWithoutEnv { | ||
impl VMDynamicFunction for DynamicFunctionWithoutEnv { | ||
fn call(&self, args: &[Val]) -> Result<Vec<Val>, RuntimeError> { | ||
(*self.func)(&args) | ||
} | ||
|
@@ -727,19 +808,27 @@ impl VMDynamicFunction for VMDynamicFunctionWithoutEnv { | |
} | ||
} | ||
|
||
#[repr(C)] | ||
pub(crate) struct VMDynamicFunctionWithEnv<Env> | ||
pub(crate) struct DynamicFunctionWithEnv<Env> | ||
where | ||
Env: Sized + 'static, | ||
{ | ||
// This field _must_ come first in this struct. | ||
env: Box<Env>, | ||
Comment on lines
-779
to
-780
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason before was due to a bad hack where we'd double deref a pointer ignoring the fact that it was in a struct. We now have each function that is called on the |
||
function_type: FunctionType, | ||
#[allow(clippy::type_complexity)] | ||
func: Box<dyn Fn(&Env, &[Val]) -> Result<Vec<Val>, RuntimeError> + 'static>, | ||
func: Arc<dyn Fn(&Env, &[Val]) -> Result<Vec<Val>, RuntimeError> + 'static>, | ||
env: Box<Env>, | ||
} | ||
|
||
impl<Env: Sized + Clone + 'static> Clone for DynamicFunctionWithEnv<Env> { | ||
fn clone(&self) -> Self { | ||
Self { | ||
env: self.env.clone(), | ||
function_type: self.function_type.clone(), | ||
func: self.func.clone(), | ||
} | ||
} | ||
} | ||
|
||
impl<Env> VMDynamicFunction for VMDynamicFunctionWithEnv<Env> | ||
impl<Env> VMDynamicFunction for DynamicFunctionWithEnv<Env> | ||
where | ||
Env: Sized + 'static, | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,7 +76,7 @@ impl ValFuncRef for Val { | |
let export = wasmer_engine::ExportFunction { | ||
// TODO: | ||
// figure out if we ever need a value here: need testing with complicated import patterns | ||
import_init_function_ptr: None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm not 100% confident in this code which is why that comment is still there, I can investigate it later... I'm not aware of a good way to test this yet, I can investigate that though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should investigate. I bet is fine as None, but it will be good to verify |
||
metadata: None, | ||
vm_function: wasmer_vm::VMExportFunction { | ||
address: item.func_ptr, | ||
signature, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need the
<'a>
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<'a>
is needed because of the&
: trait impls on things with lifetimes can't omit them. And the reason we need&
is to make these types shared somehow, otherwise theimpl WasmerEnv for X
can't be used here unless we have recursive definitions.These
impl
s on std types definitely aren't critical; these actually are only work with'static
lifetimes because of other constraints, so this only really works when putting an atomic in a static variable or otherwise doing unsafe things.I'd be fine to remove them