Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add reentrancy support to Witty #960

Merged
merged 12 commits into from
Aug 24, 2023
Merged

Add reentrancy support to Witty #960

merged 12 commits into from
Aug 24, 2023

Conversation

jvff
Copy link
Contributor

@jvff jvff commented Aug 23, 2023

Motivation

One of the main goals of the Witty crate is to make it easy to export host functions to guest Wasm modules in a reentrant manner, i.e., allowing the host functions to call back to the guest before returning.

Proposal

Allow reentrant functions to be defined in impl blocks that use the #[wit_export] attribute procedural macro by having the functions have a generic type parameter representing the Wasm runtime-specific caller instance. The macro automatically detects this and generates code that will handle the parameter in a special manner.

Since the caller type has to be borrowed by the handler closure, the runtime instance traits must also be implemented for &mut T, not just T anymore.

Test Plan

Some new integration tests are included which have reentrant functions. These functions are called by the guest and act as proxies, calling back to the guest to complete the test.

The tests also cover fallible functions, covering the bug fixed by #959.

Links

Part of #906

Release Plan

  • All good!
  • Need to bump the major/minor version number in the next release of the crates.
  • Need to update the developer manual.
  • This PR is adding or removing Cargo features.
  • Release is blocked and/or tracked by other issues (see links above)

Reviewer Checklist

  • The title and the summary of the PR are short and descriptive.
  • The proposed solution achieves the goals stated in the PR.
  • The test plan is reproducible and provides sufficient coverage.
  • The release plan is adequate.
  • The commits correspond to distinct logical changes.
  • The code follows the coding guidelines.
  • The proposed changes look correct.
  • The CI is passing.
  • All of the above!

@jvff jvff added the enhancement New feature or request label Aug 23, 2023
@jvff jvff added this to the Devnet milestone Aug 23, 2023
@jvff jvff requested a review from ma2bd August 23, 2023 02:58
@jvff jvff self-assigned this Aug 23, 2023
@jvff jvff mentioned this pull request Aug 23, 2023
14 tasks
Comment on lines 465 to 479
let instance = factory.load_test_module("reentrancy", "operations", |instance| {
ExportedOperations::export_to(instance)
.expect("Failed to export reentrant operations WIT interface")
Copy link
Contributor

Choose a reason for hiding this comment

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

(I'm just guessing here but) Would it make sense to adjust the type of the lambda to be able to write this?

factory.load_test_module("reentrancy", "operations", ExportedOperations::export_to)

or perhaps this?

factory.load_test_module::<ExportedOperations>("reentrancy", "operations")

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 like the idea, thanks! The only thing is how to handle tests without any exports. I'll create a WithoutExports dummy type and use it as the default type to see how it looks 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

witty_macros::test_modules::getters::*,
};

struct Implementation;
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it possible to use a global variable to demonstrate modifying a state in successive calls?

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 think so. I'll look into it 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, at least I think? Let me know if the new test is what you were thinking.

#[test_case(MockInstanceFactory::default(); "with a mock instance")]
#[cfg_attr(feature = "wasmer", test_case(WasmerInstanceFactory; "with Wasmer"))]
#[cfg_attr(feature = "wasmtime", test_case(WasmtimeInstanceFactory; "with Wasmtime"))]
fn getters<InstanceFactory>(mut factory: InstanceFactory)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we start tests with test_ ?

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 can do that, but I'm a bit hesitant to do so because the output will start looking like:

test test_getters::with_a_mock_instance ... ok
...

instead of just

test getters::with_a_mock_instance ... ok
...

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, the usual way to name functions is "do_something", and for tests, ideally "test_that_bla_does_this".
To me, "something" feels like a getter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For methods and normal functions I agree. But for test functions not so much, because the test functions aren't called by anybody explicitly.

Applied the suggestion because it doesn't matter much anyway 😅

pub struct ExportedSetters;

#[wit_export(package = "witty-macros:test-modules", interface = "setters")]
impl ExportedSetters {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to support this more concise variant?

pub struct ExportedSetters<Caller>(std::marker::PhantomData<Caller>));

impl Default for ExportedSetters<Caller> { ... }

#[wit_export(package = "witty-macros:test-modules", interface = "setters")]
impl<Caller> ExportedSetters<Caller>
where
    Caller: InstanceForImportedSetters,
    <Caller::Runtime as Runtime>::Memory: RuntimeMemory<Caller>,
{
    fn set_bool(caller: Caller, value: bool) -> Result<(), RuntimeError> { ... }

    // ..
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would be better, thanks! I'll look into it 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

In a next PR right? (do we need a GH issue?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, I started doing it, but it's a bit more complex than I thought 😅 . I opened #963 to finish it later.

@jvff jvff force-pushed the witty-reentrancy branch from 594fd6b to bd73dd7 Compare August 23, 2023 14:06
jvff added 12 commits August 24, 2023 00:31
Prepare to allow using the instance types as callers inside reentrant
functions.
Check if it has a first parameter with a generic type for receiving the
caller instance.
Use only the WIT types in the parameters.
Make reentrant calls possible.
Ensure that the exported host functions can call the guest Wasm
instance.
Import and export the same functions to allow the host to act as a proxy
in order to test reentrancy.
Use the reentrancy test modules to test reentrancy with real Wasm
runtimes.
Add a verb to most test names.
Replace the closure with a type parameter for the `ExportTo`
implementation.
Improve debuggability of tests.
Allow some private state to be stored inside the handlers.
Check that a reentrant call back to the guest uses the same internal
state from when the guest called the host.
Copy link
Contributor

@ma2bd ma2bd left a comment

Choose a reason for hiding this comment

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

Nice work!

@jvff jvff merged commit 2ab872b into linera-io:main Aug 24, 2023
@jvff jvff deleted the witty-reentrancy branch August 24, 2023 01:20
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants