Skip to content
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

Clean up host env code, add more Send, Sync bounds #1944

Merged
merged 5 commits into from
Dec 18, 2020

Conversation

MarkMcCaskey
Copy link
Contributor

@MarkMcCaskey MarkMcCaskey commented Dec 16, 2020

This PR contains:

  • Code clean up, refactored the unsafe metadata creation into a function which can probably be safe
  • Add Send + Sync bounds in some places where they should have been to begin with

Review

  • Add a short description of the the change to the CHANGELOG.md file

Comment on lines +72 to +75
import_init_function_ptr: for<'a> fn(
&'a mut Env,
&'a crate::Instance,
) -> Result<(), crate::HostEnvInitError>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the for syntax? First time I'm seeing this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha yeah, it's not super common

it's used to bind a lifetime in a scope, it's a universal qualifier (like ∀), it's just saying "for all lifetimes 'a, this thing, it's a way of constraining the type (and it's required here because it'll be inferred incorrectly)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@syrusakbary That's also because you can't write :

import_init_function_ptr: fn<'a>(&'a mut Env, &'a crate::Instance) -> …,

It's a syntax error.

@MarkMcCaskey I wonder though if there is a difference with:

fn build_export_function_metadata<'a, Env>(
    env: Env,
    import_init_function_ptr: fn(&'a mut Env, &'a crate::Instance) -> …,
) -> …

Anyway, I'm OK with this change!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, obviously there is a difference. The difference with 'a on the function itself is that it will be inferred from the calling context. That's not what we want! Forget it.

@@ -104,7 +147,7 @@ impl Function {
pub fn new<FT, F>(store: &Store, ty: FT, func: F) -> Self
where
FT: Into<FunctionType>,
F: Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static,
F: Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static + Send + Sync,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What it means for require a function to be Send + Sync ?
Could it still be possible to be called in parallel through different threads?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Sync with Fn does mean that the function can be called from multiple threads at the same time. Send just means it can be executed on any thread.

For Send, this means functions can't use thread-local variables.

For Sync, this means functions should not use unsynchronized global variables.

I can't think of any other cases where this is really an issue right now, I'm sure there are a few other restrictions, but most functions that use unsafe correctly or don't use unsafe at all should be both Send and Sync

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it also means that the environment captured by the function must also implement Send + Sync.

Confirmed with:

use std::rc::Rc;

fn main() {
    fn check<F>(f: F) where F: Fn() + 'static + Send + Sync {}
    
    let x = Rc::new(42);
    check(|| { let _ = x; });
}

which raises the following issue:

error[E0277]: `Rc<i32>` cannot be shared between threads safely
 --> src/main.rs:7:5
  |
4 |     fn check<F>(f: F) where F: Fn() + 'static + Send + Sync {}
  |                                                 ---- required by this bound in `check`
...
7 |     check(|| { let _ = x; });
  |     ^^^^^ `Rc<i32>` cannot be shared between threads safely
  |
  = help: the trait `Sync` is not implemented for `Rc<i32>`
  = note: required because of the requirements on the impl of `Send` for `&Rc<i32>`
  = note: required because it appears within the type `[closure@src/main.rs:7:11: 7:28]`

(see the playground)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hywan , I think having a Send + Sync environment is ultimately what we need for the Wasmer API... because Instance is Send + Sync, so a single function can be called from multiple threads concurrently and in parallel. I don't know a good way around this yet but I think Send + Sync does fix everything for us

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative might be to have separate code and abstractions for non-thread safe things, but that seems pretty complicated 🤔 . I think encouraging code to work on multiple threads by default is a very Rusty thing anyways and I think it positions us and users of wasmer well to scale their applications

Copy link
Contributor

@Hywan Hywan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this PR. It's a very welcomed clean up!

I'm approving it to not be a blocker, but I would be delighted to see my comments addressed 🙂.

Comment on lines +72 to +75
import_init_function_ptr: for<'a> fn(
&'a mut Env,
&'a crate::Instance,
) -> Result<(), crate::HostEnvInitError>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@syrusakbary That's also because you can't write :

import_init_function_ptr: fn<'a>(&'a mut Env, &'a crate::Instance) -> …,

It's a syntax error.

@MarkMcCaskey I wonder though if there is a difference with:

fn build_export_function_metadata<'a, Env>(
    env: Env,
    import_init_function_ptr: fn(&'a mut Env, &'a crate::Instance) -> …,
) -> …

Anyway, I'm OK with this change!

Comment on lines +80 to +84
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>(
import_init_function_ptr,
)
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code can be simplified to:

Suggested change
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>(
import_init_function_ptr,
)
});
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<_, fn(_, _) -> Result<(), _>>(
import_init_function_ptr,
)
});

or even:

Suggested change
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>(
import_init_function_ptr,
)
});
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<_, _>(
import_init_function_ptr,
)
});

so basically to:

Suggested change
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>(
import_init_function_ptr,
)
});
let import_init_function_ptr = Some(unsafe { std::mem::transmute(import_init_function_ptr) });

Rust is able to infer the type correctly all the way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's true, I thought listing both types was good documentation though: std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>( communicates that the function arguments and error value are changing, but the number of arguments, and the fact that it's Result<(), are not. I'll rereview the code to see if this extra info is useful, but I think it's good to be super explicit with things like transmute as so many things can go wrong

lib/api/src/externals/function.rs Outdated Show resolved Hide resolved
lib/api/src/externals/function.rs Outdated Show resolved Hide resolved
@@ -104,7 +147,7 @@ impl Function {
pub fn new<FT, F>(store: &Store, ty: FT, func: F) -> Self
where
FT: Into<FunctionType>,
F: Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static,
F: Fn(&[Val]) -> Result<Vec<Val>, RuntimeError> + 'static + Send + Sync,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it also means that the environment captured by the function must also implement Send + Sync.

Confirmed with:

use std::rc::Rc;

fn main() {
    fn check<F>(f: F) where F: Fn() + 'static + Send + Sync {}
    
    let x = Rc::new(42);
    check(|| { let _ = x; });
}

which raises the following issue:

error[E0277]: `Rc<i32>` cannot be shared between threads safely
 --> src/main.rs:7:5
  |
4 |     fn check<F>(f: F) where F: Fn() + 'static + Send + Sync {}
  |                                                 ---- required by this bound in `check`
...
7 |     check(|| { let _ = x; });
  |     ^^^^^ `Rc<i32>` cannot be shared between threads safely
  |
  = help: the trait `Sync` is not implemented for `Rc<i32>`
  = note: required because of the requirements on the impl of `Send` for `&Rc<i32>`
  = note: required because it appears within the type `[closure@src/main.rs:7:11: 7:28]`

(see the playground)

)
};

(env, metadata)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just returning metadata here, and let the rest of the code accessing metadata.env to get the environment? Is it by convenience?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata.env is private! It can only be seen in wasmer_engine not in wasmer_API, this is for the best as the contents of the metadata struct can be dangerous

Comment on lines +266 to +270
let import_init_function_ptr: for<'a> fn(&'a mut _, &'a _) -> Result<(), _> =
|env: &mut VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>>,
instance: &crate::Instance| {
Env::init_with_instance(&mut *env.ctx.env, instance)
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@MarkMcCaskey
Copy link
Contributor Author

bors r+

bors bot added a commit that referenced this pull request Dec 18, 2020
1944: Clean up host env code, add more `Send`, `Sync` bounds r=MarkMcCaskey a=MarkMcCaskey

This PR contains:
- Code clean up, refactored the unsafe metadata creation into a function which can probably be safe
- Add `Send` + `Sync` bounds in some places where they should have been to begin with

# Review

- [x] Add a short description of the the change to the CHANGELOG.md file


Co-authored-by: Mark McCaskey <[email protected]>
Co-authored-by: Mark McCaskey <[email protected]>
@bors
Copy link
Contributor

bors bot commented Dec 18, 2020

Build failed:

@MarkMcCaskey
Copy link
Contributor Author

bors r+

@bors
Copy link
Contributor

bors bot commented Dec 18, 2020

@bors bors bot merged commit ada9a36 into master Dec 18, 2020
@bors bors bot deleted the feature/clean-up-host-env-code branch December 18, 2020 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants