-
Notifications
You must be signed in to change notification settings - Fork 14
Conversation
* WIP: Wasmer FFI export
crates/svm-runtime/src/import.rs
Outdated
#[derive(Clone)] | ||
#[repr(C)] | ||
pub struct svm_env_t { | ||
pub inner_env: *const c_void, |
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.
Since inner_env
is always a Context
, any reason for not calling it ctx
?
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.
It's a matter of taste. I don't really mind changing that.
The motivation was to keep the API with the env
naming.
|
||
if let Some(callback) = callback { | ||
let args = prepare_args(args); | ||
let ctx = env.inner_mut(); |
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.
Perhaps we can settle on having a single name, env
or ctx
, in all places.
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.
The current code base still uses inner_env
under svm_env_t
If you feel it's critical let's chat about that
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.
It's fine, whatever you think.
unsafe extern "C" fn trampoline( | ||
env: *mut c_void, | ||
args: *const wasm_val_vec_t, | ||
results: *mut wasm_val_vec_t, |
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.
The usage of wasm_val_vec_t
(in wasm_func_callback_with_env_t
signature) creates a dependency between svm
client and wasmer
's C API.
This type is being constructed after wasmer
is calling svm
's inner callback, hence svm
could use a different type and a different signature for trampoline
.
This type is defined in wasmer
using a macro. I'm not sure if importing it will require merely using the C headers, or the lib as well. In any case, this would be suboptimal.
It seems the most straightforward to use svm
's Vec<WasmValue>
(which is also more restrictive in the supported Wasm types). Its encoding format is already defined and used. We can use it, or define Vec<T>
and WasmValue
as C types, similarly to how it's done in wasmer
C API.
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.
good point, I've overlooked that fact (I guess I was on an auto-pilot since I'm used to the way things were in the past in the old version of wasmer)
crates/svm-runtime/src/import.rs
Outdated
let trap: Box<wasm_trap_t> = Box::from_raw(trap); | ||
|
||
/// TODO: we want access to `trap.inner` | ||
/// (this field has visibility of `pub(crate)`) |
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.
So we don't have a solution for this yet?
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.
no, we need to ask the wasmer team to change that
env: *mut c_void, | ||
args: *const wasm_val_vec_t, | ||
results: *mut wasm_val_vec_t, | ||
) -> *mut wasm_trap_t { |
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.
wasm_trap_t
also has the same problem of creating a dependency with wasmer
's C API.
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.
yes - same
723ecd7
to
b1051a3
Compare
…s after host function completion).
crates/svm-ffi/src/env.rs
Outdated
|
||
pub host_env: *const c_void, | ||
|
||
pub returns: svm_wasm_types_t, |
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.
What's the purpose of this field? Will it ever need to be used by the extern client?
I see that svm
use it in inner_callback
to allocate the results
buffer, but I don't think it's necessary - why not just read it via closure, instead of doing set/get via env
?
crates/svm-ffi/src/value.rs
Outdated
|
||
bytes.write_u8(nvalues as u8).unwrap(); | ||
|
||
for ty in types { |
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.
I'd rather have a simpler version of this function, and have it to merely allocate the needed buffer size.
But you can leave as-is if you prefer.
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.
I've added a comment explaining why I prefer that we allocate the worst case.
Since memory allocation is a costly operation we should strive to minimize the number of its usages.
The savings of a few bytes is negligible in this case.
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.
That was my comment in #187 (comment). In this comment I'm not referring to that, but to the filling of the allocated bytes (with nvalues
, and ty
for each value). Although the extern client must comply with the signature, I think it would be better for this function to just allocate the buffer.
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.
done
crates/svm-ffi/src/byte_array.rs
Outdated
#[allow(non_camel_case_types)] | ||
#[derive(Clone)] | ||
#[repr(C)] | ||
pub struct svm_byte_array { |
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.
Need to be exposed via the C API.
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.
yes - I'm on it
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.
The only workaround I've found for now is:
-
use
cbindgen
for thesvm-ffi
crate - it should output a C header namedsvm_types.h
This header file will contain the definition forsvm_byte_array
along with other used FFI-structs withinsvm-runtime-c-api
-
The
cbindgen
ofsvm-runtime-c-api
will still output thesvm.h
header but it will have an include statement ofinclude "svm_types.h"
inside. -
Each successful GitHub Actions CI run will have
svm_types.h
in its generated artifacts.
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.
I tested the artifacts - works fine.
// this heap-allocated memory will be released by SVM. | ||
// (See: `ExternImport#wasmer_export`) | ||
|
||
Box::into_raw(Box::new(trap)) |
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.
Same as previously with results
, this is expected to be allocated by the extern client, and to be released by svm
, hence can lead to a crash or a leak.
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.
That's a bit tricky since SVM
can't know ahead-of-time how much space to allocate for svm_trap_t
.
The only workaround I've in mind is to agree on a maximum trap size - say 1024 bytes for example.
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.
In go-wasmer
the solution is:
- Allocate the error in Go
- Create C-compatible alias that Rust can use (via
to_wasm_trap_new
, defined in cgo preamble) - Call Rust's C-API and re-allocate it on the heap (via
wasm_trap_new
) - Go allocation to be automatically released
- Rust allocation to be released by Go (via
wasm_trap_delete
). I'm not sure about this step, because Rust is using the trap raw point (trampoline
return value), so it's not clear how would it still be valid (if released by Go immediately aftertrampoline
returns), or why Go even need to bother with timing its release. So i'm either missing something, or the code is still in the works.
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.
svm-runtime-c-api
added a method of trap allocation. (also a method for deallocation - in general, it should be used only for tests since SVM is in charge of deallocation of traps)
… the structs defined under `svm-ffi`.
…y `svm.h` (at the `svm-runtime-c-api` crate).
crates/svm-ffi/src/byte_array.rs
Outdated
#[allow(non_camel_case_types)] | ||
#[derive(Clone)] | ||
#[repr(C)] | ||
pub struct svm_byte_array { |
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.
The only workaround I've found for now is:
-
use
cbindgen
for thesvm-ffi
crate - it should output a C header namedsvm_types.h
This header file will contain the definition forsvm_byte_array
along with other used FFI-structs withinsvm-runtime-c-api
-
The
cbindgen
ofsvm-runtime-c-api
will still output thesvm.h
header but it will have an include statement ofinclude "svm_types.h"
inside. -
Each successful GitHub Actions CI run will have
svm_types.h
in its generated artifacts.
crates/svm-ffi/src/value.rs
Outdated
|
||
bytes.write_u8(nvalues as u8).unwrap(); | ||
|
||
for ty in types { |
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.
I've added a comment explaining why I prefer that we allocate the worst case.
Since memory allocation is a costly operation we should strive to minimize the number of its usages.
The savings of a few bytes is negligible in this case.
// this heap-allocated memory will be released by SVM. | ||
// (See: `ExternImport#wasmer_export`) | ||
|
||
Box::into_raw(Box::new(trap)) |
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.
That's a bit tricky since SVM
can't know ahead-of-time how much space to allocate for svm_trap_t
.
The only workaround I've in mind is to agree on a maximum trap size - say 1024 bytes for example.
|
||
if let Some(callback) = callback { | ||
let args = prepare_args(args); | ||
let ctx = env.inner_mut(); |
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.
The current code base still uses inner_env
under svm_env_t
If you feel it's critical let's chat about that
pub func_ptr: *const c_void, | ||
params: Vec<WasmType>, | ||
|
||
returns: Rc<Vec<WasmType>>, |
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.
@moshababo
I've thought about how to simplify the code of the results
allocations and still gain performance.
So I've picked using Rc
to share the returns
types with the inner_callback
using shallow clone.
(the runtime overhead is minimal and should be much less expansive than cloning the Vec
.
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.
Looks good.
crates/svm-runtime/src/import.rs
Outdated
|
||
match Vec::<WasmValue>::try_from(&results) { | ||
Ok(vals) => { | ||
// TODO: validate the returns types. |
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.
In general, this should be done via the host trampoline
as part of injecting the results
.
I'm not sure we want to have this validation logic duplicated here
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.
Yes, we can probably avoid doing it here.
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.
I've added validation of the results
since SVM only allocates a zeroed-buffer before calling the host function.
crates/svm-ffi/build.rs
Outdated
.include_item("svm_trap_t") | ||
.include_item("svm_func_callback_t") | ||
.include_item("svm_env_t") | ||
.with_documentation(false) |
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 without documentation (unlike svm-runtime-c-api
)?
crates/svm-runtime/src/import.rs
Outdated
_ => panic!("Only i32 and i64 are supported."), | ||
}) | ||
.collect() | ||
} | ||
|
||
#[inline] | ||
fn to_wasm_values(bytes: &svm_byte_array, types: &[WasmType]) -> Option<Vec<WasmValue>> { |
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.
Wouldn't it be better to return Result
instead of Option
, since you later transform the None
option to Err
anyway? That way you could return more specific errors from to_wasm_values
.
|
||
const COUNTER_MUL_FN_INDEX: u32 = 123; | ||
|
||
fn wasm_trap(err: String) -> *mut svm_trap_t { |
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.
Any reason for still keeping this function?
// that there no trap has occurred. | ||
return std::ptr::null_mut(); | ||
} | ||
Err(err) => wasm_trap(err.to_string()), |
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.
Instead of wasm_trap
you should use the new svm_trap_alloc
C-API function.
crates/svm-runtime-c-api/src/api.rs
Outdated
|
||
/// Allocates a new `svm_trap_t` with inner `error` of size `size`. | ||
#[no_mangle] | ||
pub unsafe extern "C" fn svm_trap_alloc(size: u32) -> *mut svm_trap_t { |
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.
I think that a simpler design would be to accept svm_byte_array
msg and to re-allocate it on the heap using Rust (similarly to wasmer's wasm_trap_new
). Copying bytes to an unsafe buffer should work in Go, but it's quite hacky.
Vec::<WasmValue>::try_from(args).map_err(|_| "Invalid args") | ||
} | ||
|
||
fn wasm_error(msg: String) -> *mut svm_byte_array { |
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.
Any reason for still using this instead of svm_wasm_error_create
?
Motivation
1.0.0-alpha5