1
1
use crate :: error:: Error ;
2
- use crate :: instance:: { InstanceHandle , RunResult , State , TerminationDetails } ;
2
+ use crate :: instance:: { InstanceHandle , InternalRunResult , RunResult , State , TerminationDetails } ;
3
+ use crate :: module:: FunctionHandle ;
3
4
use crate :: val:: { UntypedRetVal , Val } ;
4
5
use crate :: vmctx:: { Vmctx , VmctxInternal } ;
5
6
use std:: any:: Any ;
6
7
use std:: future:: Future ;
7
8
use std:: pin:: Pin ;
9
+ use std:: task:: { Context , Poll } ;
8
10
9
11
/// This is the same type defined by the `futures` library, but we don't need the rest of the
10
12
/// library for this purpose.
@@ -75,23 +77,38 @@ impl Vmctx {
75
77
// Wrap the computation in `YieldedFuture` so that
76
78
// `Instance::run_async` can catch and run it. We will get the
77
79
// `ResumeVal` we applied to `f` above.
78
- self . yield_impl :: < YieldedFuture , ResumeVal > ( YieldedFuture ( f) , false ) ;
80
+ self . yield_impl :: < YieldedFuture , ResumeVal > ( YieldedFuture ( f) , false , false ) ;
79
81
let ResumeVal ( v) = self . take_resumed_val ( ) ;
80
82
// We may now downcast and unbox the returned Box<dyn Any> into an `R`
81
83
// again.
82
84
* v. downcast ( ) . expect ( "run_async broke invariant" )
83
85
}
84
86
}
85
87
86
- /// This struct needs to be exposed publicly in order for the signature of a
87
- /// "block_in_place" function to be writable, a concession we must make because
88
- /// Rust does not have rank 2 types. To prevent the user from inspecting or
89
- /// constructing the inside of this type, it is completely opaque.
90
- pub struct Bounce < ' a > ( BounceInner < ' a > ) ;
88
+ /// A simple future that yields once. We use this to yield when a runtime bound is reached.
89
+ ///
90
+ /// Inspired by Tokio's `yield_now()`.
91
+ struct YieldNow {
92
+ yielded : bool ,
93
+ }
94
+
95
+ impl YieldNow {
96
+ fn new ( ) -> Self {
97
+ Self { yielded : false }
98
+ }
99
+ }
91
100
92
- enum BounceInner < ' a > {
93
- Done ( UntypedRetVal ) ,
94
- More ( BoxFuture < ' a , ResumeVal > ) ,
101
+ impl Future for YieldNow {
102
+ type Output = ( ) ;
103
+ fn poll ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < ( ) > {
104
+ if self . yielded {
105
+ Poll :: Ready ( ( ) )
106
+ } else {
107
+ self . yielded = true ;
108
+ cx. waker ( ) . wake_by_ref ( ) ;
109
+ Poll :: Pending
110
+ }
111
+ }
95
112
}
96
113
97
114
impl InstanceHandle {
@@ -101,51 +118,59 @@ impl InstanceHandle {
101
118
/// that use `Vmctx::block_on` and provides the trampoline that `.await`s those futures on
102
119
/// behalf of the guest.
103
120
///
121
+ /// If `runtime_bound` is provided, it will also pause the Wasm execution and yield a future
122
+ /// that resumes it after (approximately) that many Wasm opcodes have executed.
123
+ ///
104
124
/// # `Vmctx` Restrictions
105
125
///
106
126
/// This method permits the use of `Vmctx::block_on`, but disallows all other uses of `Vmctx::
107
127
/// yield_val_expecting_val` and family (`Vmctx::yield_`, `Vmctx::yield_expecting_val`,
108
128
/// `Vmctx::yield_val`).
129
+ pub async fn run_async < ' a > (
130
+ & ' a mut self ,
131
+ entrypoint : & ' a str ,
132
+ args : & ' a [ Val ] ,
133
+ runtime_bound : Option < u64 > ,
134
+ ) -> Result < UntypedRetVal , Error > {
135
+ let func = self . module . get_export_func ( entrypoint) ?;
136
+ self . run_async_internal ( func, args, runtime_bound) . await
137
+ }
138
+
139
+ /// Run the module's [start function][start], if one exists.
109
140
///
110
- /// # Blocking thread
111
- ///
112
- /// The `wrap_blocking` argument is a function that is called with a closure that runs the Wasm
113
- /// program. Since Wasm may execute for an arbitrarily long time without `await`ing, we need to
114
- /// make sure that it runs on a thread that is allowed to block.
115
- ///
116
- /// This argument is designed with [`tokio::task::block_in_place`][tokio] in mind. The odd type
117
- /// is a concession to the fact that we don't have rank 2 types in Rust, and so must fall back
118
- /// to trait objects in order to be able to take an argument that is itself a function that
119
- /// takes a closure.
120
- ///
121
- /// In order to provide an appropriate function, you may have to wrap the library function in
122
- /// another closure so that the types are compatible. For example:
123
- ///
124
- /// ```no_run
125
- /// # async fn f() {
126
- /// # let instance: lucet_runtime_internals::instance::InstanceHandle = unimplemented!();
127
- /// fn block_in_place<F, R>(f: F) -> R
128
- /// where
129
- /// F: FnOnce() -> R,
130
- /// {
131
- /// // ...
132
- /// # f()
133
- /// }
141
+ /// If there is no start function in the module, this does nothing.
134
142
///
135
- /// instance.run_async("entrypoint", &[], |f| block_in_place(f)).await.unwrap();
136
- /// # }
137
- /// ```
143
+ /// All of the other restrictions on the start function, what it may do, and
144
+ /// the requirement that it must be invoked first, are described in the
145
+ /// documentation for `Instance::run_start()`. This async version of that
146
+ /// function satisfies the requirement to run the start function first, as
147
+ /// long as the async function fully returns (not just yields).
138
148
///
139
- /// [tokio]: https://docs.rs/tokio/0.2.21/tokio/task/fn.block_in_place.html
140
- pub async fn run_async < ' a , F > (
149
+ /// This method is similar to `Instance::run_start()`, except that it bounds
150
+ /// runtime between async future yields (invocations of `.poll()` on the
151
+ /// underlying generated future) if `runtime_bound` is provided. This
152
+ /// behaves the same way as `Instance::run_async()`.
153
+ pub async fn run_async_start < ' a > (
141
154
& ' a mut self ,
142
- entrypoint : & ' a str ,
155
+ runtime_bound : Option < u64 > ,
156
+ ) -> Result < ( ) , Error > {
157
+ if let Some ( start) = self . module . get_start_func ( ) ? {
158
+ if !self . is_not_started ( ) {
159
+ return Err ( Error :: StartAlreadyRun ) ;
160
+ }
161
+ self . run_async_internal ( start, & [ ] , runtime_bound) . await ?;
162
+ }
163
+ Ok ( ( ) )
164
+ }
165
+
166
+ /// Shared async run-loop implementation for both `run_async()` and
167
+ /// `run_start_async()`.
168
+ async fn run_async_internal < ' a > (
169
+ & ' a mut self ,
170
+ func : FunctionHandle ,
143
171
args : & ' a [ Val ] ,
144
- wrap_blocking : F ,
145
- ) -> Result < UntypedRetVal , Error >
146
- where
147
- F : for < ' b > Fn ( & mut ( dyn FnMut ( ) -> Result < Bounce < ' b > , Error > ) ) -> Result < Bounce < ' b > , Error > ,
148
- {
172
+ runtime_bound : Option < u64 > ,
173
+ ) -> Result < UntypedRetVal , Error > {
149
174
if self . is_yielded ( ) {
150
175
return Err ( Error :: Unsupported (
151
176
"cannot run_async a yielded instance" . to_owned ( ) ,
@@ -156,55 +181,55 @@ impl InstanceHandle {
156
181
let mut resume_val: Option < ResumeVal > = None ;
157
182
loop {
158
183
// Run the WebAssembly program
159
- let bounce = wrap_blocking ( & mut || {
160
- let run_result = if self . is_yielded ( ) {
161
- // A previous iteration of the loop stored the ResumeVal in
162
- // `resume_val`, send it back to the guest ctx and continue
163
- // running:
164
- self . resume_with_val_impl (
165
- resume_val
166
- . take ( )
167
- . expect ( "is_yielded implies resume_value is some" ) ,
168
- true ,
169
- )
170
- } else {
171
- // This is the first iteration, call the entrypoint:
172
- let func = self . module . get_export_func ( entrypoint) ?;
173
- self . run_func ( func, args, true )
174
- } ;
175
- match run_result? {
176
- RunResult :: Returned ( rval) => {
177
- // Finished running, return UntypedReturnValue
178
- return Ok ( Bounce ( BounceInner :: Done ( rval) ) ) ;
179
- }
180
- RunResult :: Yielded ( yval) => {
181
- // Check if the yield came from Vmctx::block_on:
182
- if yval. is :: < YieldedFuture > ( ) {
183
- let YieldedFuture ( future) = * yval. downcast :: < YieldedFuture > ( ) . unwrap ( ) ;
184
- // Rehydrate the lifetime from `'static` to `'a`, which
185
- // is morally the same lifetime as was passed into
186
- // `Vmctx::block_on`.
187
- Ok ( Bounce ( BounceInner :: More (
188
- future as BoxFuture < ' a , ResumeVal > ,
189
- ) ) )
190
- } else {
191
- // Any other yielded value is not supported - die with an error.
192
- Err ( Error :: Unsupported (
193
- "cannot yield anything besides a future in Instance::run_async"
194
- . to_owned ( ) ,
195
- ) )
196
- }
197
- }
184
+ let run_result = if self . is_yielded ( ) {
185
+ // A previous iteration of the loop stored the ResumeVal in
186
+ // `resume_val`, send it back to the guest ctx and continue
187
+ // running:
188
+ self . resume_with_val_impl (
189
+ resume_val
190
+ . take ( )
191
+ . expect ( "is_yielded implies resume_value is some" ) ,
192
+ true ,
193
+ runtime_bound,
194
+ )
195
+ } else if self . is_bound_expired ( ) {
196
+ self . resume_bounded (
197
+ runtime_bound. expect ( "should have bound if guest had expired bound" ) ,
198
+ )
199
+ } else {
200
+ // This is the first iteration, call the entrypoint:
201
+ self . run_func ( func, args, true , runtime_bound)
202
+ } ;
203
+ match run_result? {
204
+ InternalRunResult :: Normal ( RunResult :: Returned ( rval) ) => {
205
+ // Finished running, return UntypedReturnValue
206
+ return Ok ( rval) ;
198
207
}
199
- } ) ?;
200
- match bounce {
201
- Bounce ( BounceInner :: Done ( rval) ) => return Ok ( rval) ,
202
- Bounce ( BounceInner :: More ( fut) ) => {
203
- // await on the computation. Store its result in
204
- // `resume_val`.
205
- resume_val = Some ( fut. await ) ;
208
+ InternalRunResult :: Normal ( RunResult :: Yielded ( yval) ) => {
209
+ // Check if the yield came from Vmctx::block_on:
210
+ if yval. is :: < YieldedFuture > ( ) {
211
+ let YieldedFuture ( future) = * yval. downcast :: < YieldedFuture > ( ) . unwrap ( ) ;
212
+ // Rehydrate the lifetime from `'static` to `'a`, which
213
+ // is morally the same lifetime as was passed into
214
+ // `Vmctx::block_on`.
215
+ let future = future as BoxFuture < ' a , ResumeVal > ;
216
+
217
+ // await on the computation. Store its result in
218
+ // `resume_val`.
219
+ resume_val = Some ( future. await ) ;
206
220
// Now we want to `Instance::resume_with_val` and start
207
221
// this cycle over.
222
+ } else {
223
+ // Any other yielded value is not supported - die with an error.
224
+ return Err ( Error :: Unsupported (
225
+ "cannot yield anything besides a future in Instance::run_async"
226
+ . to_owned ( ) ,
227
+ ) ) ;
228
+ }
229
+ }
230
+ InternalRunResult :: BoundExpired => {
231
+ // Await on a simple future that yields once then is ready.
232
+ YieldNow :: new ( ) . await
208
233
}
209
234
}
210
235
}
0 commit comments