-
Notifications
You must be signed in to change notification settings - Fork 824
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
Ctx needs ability to invoke functions #638
Comments
I've created an example that does this, but unless I'm misunderstanding the spec, then there's an issue with the Wasm Rustc is generating rust-lang/rust#64521 . The only change needed is for I've confirmed that Wasm and host fn callbacks now work with all 3 backends. The issue is that given what Rustc currently does, it's not possible to tell the difference between a host fn and a wasm fn. And not only that, but if the Rust code tries to do anything with a host-fn as a callback, it clobbers all the other indices making them invalid. The way I'm doing it here is closer to #670, passing the functions directly. I agree that we should have other ways to do this like this issue mentions! |
803: Add method to call function at index on Ctx r=MarkMcCaskey a=MarkMcCaskey For #638 and #670 ```Rust fn call_guest_fn(ctx: &mut Ctx, guest_fn: u32) -> u32 { println!("{}", guest_fn); let guest_fn_typed = unsafe { guest_fn }; let result = ctx.call_with_table_index(guest_fn_typed, &[]).unwrap(); println!(" -> {:?}", result); 0 } ``` is what this looks like from the Host side # Review - [x] Create a short description of the the change in the CHANGELOG.md file Co-authored-by: Mark McCaskey <[email protected]> Co-authored-by: Mark McCaskey <[email protected]>
803: Add method to call function at index on Ctx r=MarkMcCaskey a=MarkMcCaskey For #638 and #670 ```Rust fn call_guest_fn(ctx: &mut Ctx, guest_fn: u32) -> u32 { println!("{}", guest_fn); let guest_fn_typed = unsafe { std::mem::transmute(guest_fn) }; let result = ctx.call_with_table_index(guest_fn_typed, &[]).unwrap(); println!(" -> {:?}", result); 0 } ``` is what this looks like from the Host side See `examples/callback.rs` for an example that doesn't require `transmute` # Review - [x] Create a short description of the the change in the CHANGELOG.md file Co-authored-by: Mark McCaskey <[email protected]> Co-authored-by: Mark McCaskey <[email protected]>
Okay! Update on this front: We have a PR out for making Instance Send (#807) which may resolve some of your issues, but it sounds like those might be a work around anyways. #803 merged which allows calling functions via the table. In order to get the table mapping, you'd have to get data from the Wasm side. Is this something that seems feasible for your use case? I think the biggest missing piece now that I think about it is that the change I made requires you to pass in arguments and doesn't let the caller query the signature of the function. If that's an issue, I can prioritize looking into that, too. |
this seems really fundamental to me i'm trying to get strings out of the host into the guest from an imported host function call using wee_alloc so i have a setup looking something like this: pub fn import_object() -> ImportObject {
imports! {
"env" => {
"__host_process_string" => func!(host_process_string),
},
}
} fn host_process_string(ctx: &mut Ctx, ptr: i32, cap: i32) -> u64 {
println!("hiii {} {}", ptr, cap);
let guest_string = read_guest_string(ctx, ptr, cap);
println!("guest_string {}", guest_string);
let processed_string = format!("host: {}", guest_string);
u64_merge_bits(processed_string.as_ptr() as _, processed_string.len() as _)
} but i get memory out of bounds issues here, because the so i need to write from the host to the guest and then tell the guest to go pick up a string from the guest's pointer on the return side to work with wee_alloc i have this function to allow hosts to write to the guest: pub fn write_guest_string(instance: &mut Instance, s: String) -> (i32, i32) {
let guest_ptr = match instance
.call("pre_alloc_string", &[Value::I32(s.len() as _)])
.expect("run pre alloc")[0]
{
Value::I32(i) => i,
_ => unreachable!(),
};
let memory = instance.context_mut().memory(0);
for (byte, cell) in s
.bytes()
.zip(memory.view()[guest_ptr as _..(guest_ptr + s.len() as i32) as _].iter()) {
cell.set(byte)
}
(guest_ptr, s.len() as i32)
} with the "pre alloc" function inside the guest looking like this: pub extern "C" fn pre_alloc_string(cap: u32) -> i32 {
// https://doc.rust-lang.org/std/string/struct.String.html#examples-8
// Prevent automatically dropping the String's data
let dummy:Vec<u8> = Vec::with_capacity(cap as _);
let ptr = dummy.as_slice().as_ptr() as i32;
mem::ManuallyDrop::new(dummy);
ptr
} so a ptr gets calculated by wee_alloc inside the guest, and then wee_alloc forgets about it so it doesn't get cleaned up, so then the host can write to that, then on the guest side again we can pick it back up with: pub fn host_string(ptr: u32, len: u32) -> String {
let slice = unsafe { slice::from_raw_parts(ptr as _, len as _) };
String::from(std::str::from_utf8(slice).unwrap())
} my problem is that i need the instance to call the pre allocation function but inside the this means that AFAICS i cannot safely have the host return strings to the guest from imported functions i feel like i must be missing something obvious here 😅 can someone ELI5 what the situation is for calling guest functions from inside imported host functions? |
The wapc project (https://GitHub.com/wapc) solved that issue by using a
specific stateless protocol for communication between host and guest. It
might solve your issues, it solved mine which is why we built it 😀
…On Thu, Jan 30, 2020, 5:09 PM David Meister ***@***.***> wrote:
this seems really fundamental to me
i'm trying to get strings out of the host into the guest *from an
imported host function call* using wee_alloc
so i have a setup looking something like this:
pub fn import_object() -> ImportObject {
imports! {
"env" => {
"__host_process_string" => func!(host_process_string),
},
}
}
fn host_process_string(ctx: &mut Ctx, ptr: i32, cap: i32) -> u64 {
println!("hiii {} {}", ptr, cap);
let guest_string = read_guest_string(ctx, ptr, cap);
println!("guest_string {}", guest_string);
let processed_string = format!("host: {}", guest_string);
u64_merge_bits(processed_string.as_ptr() as _, processed_string.len() as _)
}
but i get memory out of bounds issues here, because the processed_string
ptr and len point to the host ptr and len
so i need to write from the host to the guest.
to work with wee_alloc i have this function to allow hosts to write to the
guest:
pub fn write_guest_string(instance: &mut Instance, s: String) -> (i32, i32) {
let guest_ptr = match instance
.call("pre_alloc_string", &[Value::I32(s.len() as _)])
.expect("run pre alloc")[0]
{
Value::I32(i) => i,
_ => unreachable!(),
};
let memory = instance.context_mut().memory(0);
for (byte, cell) in s
.bytes()
.zip(memory.view()[guest_ptr as _..(guest_ptr + s.len() as i32) as _].iter()) {
cell.set(byte)
}
(guest_ptr, s.len() as i32)
}
with the "pre alloc" function inside the guest looking like this:
pub extern "C" fn pre_alloc_string(cap: u32) -> i32 {
// https://doc.rust-lang.org/std/string/struct.String.html#examples-8
// Prevent automatically dropping the String's data
let dummy:Vec<u8> = Vec::with_capacity(cap as _);
let ptr = dummy.as_slice().as_ptr() as i32;
mem::ManuallyDrop::new(dummy);
ptr
}
so a ptr gets calculated by wee_alloc inside the guest, and then wee_alloc
forgets about it so it doesn't get cleaned up, so then the host can write
to that, then on the guest side again we can pick it back up with:
pub fn host_string(ptr: u32, len: u32) -> String {
let slice = unsafe { slice::from_raw_parts(ptr as _, len as _) };
String::from(std::str::from_utf8(slice).unwrap())
}
my problem is that i need the *instance* to call the pre allocation
function but inside the host_process_string imported function i only have
access to a *context*
this means that AFAICS i *cannot safely have the host return strings to
the guest from imported functions*
i feel like i must be missing something obvious here 😅
can someone ELI5 what the situation is for calling guest functions from
inside imported host functions?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#638?email_source=notifications&email_token=ADI4TIKZCV5G7YDHUS67IYLRANFYBA5CNFSM4IKAJWA2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKMX6AY#issuecomment-580484867>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADI4TILPR7HEORRGTKDJXNDRANFYBANCNFSM4IKAJWAQ>
.
|
i see https://github.com/confio/cosmwasm/commit/92d25c8f8e457a4895bc0a578afdbb579d49e567 as a solution in a referencing issue it relies on the guest preallocating some memory that the host writes into, then the host returns the actual amount of memory allocated, which seems like a half solution that could work for some situations and not others:
|
Not trying to push my own stuff but the reason wapc does it the way it does
is so that neither side of the call relies on foreknowledge of how they
allocate memory.
…On Thu, Jan 30, 2020, 5:43 PM David Meister ***@***.***> wrote:
i see ***@***.***
<confio/priv_cosmwasm@92d25c8>
as a solution in a referencing issue
it relies on the guest preallocating some memory that the host writes
into, then the host returns the actual amount of memory allocated, which
seems like a half solution that could work for some situations and not
others:
- guest has to allocate "enough" memory, which in many situations
can't be known ahead of time so we'd have to shoot for "way more than we
expect to need", which in wasm can mean new pages added that are never used
and can never be removed
- host could potentially need more than the guest allows, which would
panic, the solution linked has a TODO for this situation
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#638?email_source=notifications&email_token=ADI4TIPMOHYJY5XQLFQRKGDRANJYRA5CNFSM4IKAJWA2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKM24OA#issuecomment-580496952>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADI4TIKYHZABAUI7VGM2XKTRANJYRANCNFSM4IKAJWAQ>
.
|
@autodidaddict looks awesome! just confirming that if so, is there something in i'm trying to work with atomic writes on the host so that subsequent/nested wasm calls that have the host write on behalf of a single guest making calls all succeed or fail together (the references captured by the closures are all to something handling the atomicity for me) i'm hoping to both be able to capture references to something that co-ordinates writes (seems easy with wasmer) and be able to return data from the host from imported functions (seems easy with wapc) |
Wapc is a layer above the raw vm interpreters, right now supporting
wasmtime but others will be added in as time permits
…On Thu, Jan 30, 2020, 6:03 PM David Meister ***@***.***> wrote:
@autodidaddict <https://github.com/autodidaddict> looks awesome! just
confirming that wapc is a complete replacement/alternative for wasmer,
not something that extends it?
if so, is there something in wapc that works like the closures that wasmer
provides for its imports! and func! macros (capturing e.g. references to
things at the moment of instantiating the wasm instance)?
i'm trying to work with atomic writes on the host so that
subsequent/nested wasm calls that have the host write on behalf of a single
guest making calls all succeed or fail together (the references captured by
the closures are all to something handling the atomicity for me)
i'm hoping to both be able to capture references to something that
co-ordinates writes (seems easy with wasmer) and be able to return data
from the host from imported functions (seems easy with wapc)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#638?email_source=notifications&email_token=ADI4TIJUDIITCA7TFS2DWL3RANMFHA5CNFSM4IKAJWA2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKM4M2I#issuecomment-580503145>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADI4TIOIPKMD4DR5Q6GHQILRANMFHANCNFSM4IKAJWAQ>
.
|
ok, based on your blog post i think i know what to do https://medium.com/@KevinHoffman/introducing-wapc-dc9d8b0c2223 it is late here but tomorrow i think i'll try not having the imported host function call the instance again, instead it can return the pointer/length to the host version of the data as a u64 with high/low bits as u32 pointer/length the guest could then use this to prepare its own pointer and length, and pass this back to the host alongside the hosts pointer and length, then the host can copy its data into the guests data i think that's how wapc roughly works :) sounds good to try out |
i managed to get something working with a two step process u64 pointer -> [u64; 2] pointer+length -> data because the intermediate pointer+length is always 2 u64 ints i know it is 16 bytes so can copy things between host/guest without ambiguity in a fairly standard way |
It seems this is somewhat possible with the I found this example, but it's not so convenient as you can't just call a function by name: https://github.com/wasmerio/wasmer/pull/803/files#diff-e9732635192012c4363a1b003e040e5e |
Also, you could use the following scheme:
use std::alloc::alloc as global_alloc;
use std::alloc::dealloc as global_dealloc;
use std::alloc::Layout;
/// Allocates memory area of specified size and returns its address.
/// Returns 0 if supplied size is too long.
#[no_mangle]
pub unsafe fn allocate(size: usize) -> usize {
let layout = match Layout::from_size_align(size, std::mem::align_of::<u8>()) {
Ok(layout) => layout,
// in this case a err may occur only in a case of too long allocated size,
// so just return 0
Err(_) => return 0,
};
global_alloc(layout) as _
}
/// Deallocates memory area for provided memory pointer and size.
/// Does nothing if supplied size is too long.
#[no_mangle]
pub unsafe fn deallocate(ptr: *mut u8, size: usize) {
let layout = match Layout::from_size_align(size, std::mem::align_of::<u8>()) {
Ok(layout) => layout,
// in this case a err may occur only in a case of too long allocated size,
// so just done nothing
Err(_) => return,
};
global_dealloc(ptr, layout);
}
|
@michaelvoronov i'm doing something similar now, but using @dsherret thanks for the link to the example of nested calls :) |
Yes, Thank you for the link to your project, will take a look. |
Also, it seems that in the refactored Wasmer the 2nd step would be simplified by providing a special API, and 5th step could be rewritten with multi-value. |
@michaelvoronov also instead of using globals to track things i'm always having the guest functions just return/receive pointers to memory in the guest, and i prefix the i haven't seen the new wasmer, maybe multi value would allow ptr/len to be represented directly :) |
Heh, I've used this approach in our previous sdk, but now with support of interface-types I decided to use these globals, because from interface-types side I can't decode/encode Vec and extract size. From IT side it looks like this. So, I am super waiting for the new Wasmer with multi-value support to refactor this messy approach with global variables. Regarding access to new Wasmer - it seems you can join to the Wasmer slack and contact Syrus (based on this) for granting access. |
We completely changed the way Context works in upcoming Wasmer 1.0. @MarkMcCaskey is also working on the PR #1739 which will make the implementation trivial. Closing the issue |
Motivation
My project has a wasmer runtime embedded in it, and my use case involves the host runtime invoking a guest module function. In response, the guest module can invoke multiple host functions. Within those host functions, I might need to make further guest function calls to interrogate data.
tl;dr host functions that are free-floating (not bound to a struct instance) have no way to invoke functions in the module, though they can read/write the module's memory block.
Proposed solution
Add the
func
call to theCtx
struct, or add a reference to theinstance
that invoked the host function to the signature of the function pointed at by thefunc!
macro.Alternatives
The only alternatives we have are trying to store global references to the
Instance
, but since theInstance
triggers all kinds of thread safety errors as soon as we try and do that, it's not a viable option. Right now it appears that host functions cannot call guest functions, which severely inhibits some RPC-style call patterns.Additional context
I'm looking at this functionality as a way to simplify the
wascap
interface standard to be more fluid and to avoid explicit cross-wasm boundary calls to allocate memory (decoupling allocation from the RPC interface). I can't do this if the host functions can't make guest function calls.The text was updated successfully, but these errors were encountered: