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

Resumable function invocation #110

Merged
merged 14 commits into from
Jul 9, 2018
Merged

Resumable function invocation #110

merged 14 commits into from
Jul 9, 2018

Conversation

sorpaas
Copy link
Contributor

@sorpaas sorpaas commented Jul 6, 2018

Implements #85. If a trap of kind Host happens, we allow the caller to feed back in another return value for that host function, and then continue the execution.

  • For interpreter, the call stack is moved to Interpreter struct itself. We drop the interpreter altogether for normal executions anyway. resume_execution, which takes the feeded-back return value, will continue the execution with the loop.
  • For public interface, invoke_resumable is added to FuncInstance. This function returns a handle FuncInvocation. The caller can then use this to start or resume an execution.

@sorpaas
Copy link
Contributor Author

sorpaas commented Jul 9, 2018

Added a simple test. And for resumable calls, I moved passing &mut Externals from initializing the interpreter to calling start/resume. The rationale is that for most of the time, when dealing with resumables, we would mostly want: do interpretation, modify externals, do interpretation, modify externals, ... Passing externals on initialization would mean this routine would face lifetime issues.

I think this PR is ready for review now.

src/runner.rs Outdated

pub fn start_execution<'a, E: Externals + 'a>(&mut self, externals: &'a mut E) -> Result<Option<RuntimeValue>, Trap> {
// Ensure that the VM has not been executed.
assert!(self.state == InterpreterState::Initialized);
Copy link
Contributor

@NikVolf NikVolf Jul 9, 2018

Choose a reason for hiding this comment

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

Not sure about all those asserts

This all supposed to be called in places with zero tolerance to panics

Can we at least write why this assert (and all others) will always pass?

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 public interface will not panic. In func mod's FuncInvocation::start_execution, we check whether the state is Initialized first before calling Interpreter::start_execution. But if there's an internal incorrect usage in our code, then panic do happen. Do we prefer to return additional errors here or is this panic okay?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, an explanation is ok

src/runner.rs Outdated
});

// Ensure that stack is empty after the execution.
assert!(self.value_stack.len() == 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

What about this assert?
How is that guaranteed that there are no more values?

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'm not sure about this either. This assert is added in #98. @pepyakin Do we have guarantees on this or should we change this to a Trap?

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry my bad. Yeah we do, it’s guaranteed by the validation properties. I should have add a comment

Copy link
Collaborator

@pepyakin pepyakin left a comment

Choose a reason for hiding this comment

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

Looks awesome, well done! A few doc issues

.expect("Failed to instantiate module")
.assert_no_start();

let func_instance = match instance.export_by_name("test").unwrap() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can do the same thing a little bit cleaner (IMO) with ExternVal::as_func()

}
}

/// Resume an execution if a previous trap of Host kind happened.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we add some more documentation here?

E.g. what happens if the user provides return_val of mismatched type?

src/func.rs Outdated
/// Trap happened.
Trap(Trap),
/// The invocation is not resumable.
NotResumable,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we expand a little bit more on why the invocation can be not resumable?

src/func.rs Outdated

/// Invoke the function, get a resumable handle. This handle can then be used to actually start the execution. If a
/// Host trap happens, caller can use `resume_execution` to feed the expected return value back in, and then
/// continue the execution.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it is a good idea to add a note that this is an experimental API. Also it might be a good idea to also warn the user that they might not be able to find anything like this API on other execution engines.

@pepyakin pepyakin merged commit a605175 into master Jul 9, 2018
@pepyakin pepyakin deleted the sp-pause branch July 9, 2018 16:06
@pepyakin pepyakin mentioned this pull request Jul 9, 2018
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