1
1
use crate :: error:: Error ;
2
- use crate :: instance:: { InstanceHandle , RunResult , State , TerminationDetails } ;
2
+ use crate :: instance:: { InstanceHandle , InternalRunResult , RunResult , State , TerminationDetails } ;
3
3
use crate :: val:: { UntypedRetVal , Val } ;
4
4
use crate :: vmctx:: { Vmctx , VmctxInternal } ;
5
5
use std:: any:: Any ;
6
6
use std:: future:: Future ;
7
7
use std:: pin:: Pin ;
8
+ use std:: task:: { Context , Poll } ;
8
9
9
10
/// This is the same type defined by the `futures` library, but we don't need the rest of the
10
11
/// library for this purpose.
@@ -75,23 +76,44 @@ impl Vmctx {
75
76
// Wrap the computation in `YieldedFuture` so that
76
77
// `Instance::run_async` can catch and run it. We will get the
77
78
// `ResumeVal` we applied to `f` above.
78
- self . yield_impl :: < YieldedFuture , ResumeVal > ( YieldedFuture ( f) , false ) ;
79
+ self . yield_impl :: < YieldedFuture , ResumeVal > ( YieldedFuture ( f) , false , false ) ;
79
80
let ResumeVal ( v) = self . take_resumed_val ( ) ;
80
81
// We may now downcast and unbox the returned Box<dyn Any> into an `R`
81
82
// again.
82
83
* v. downcast ( ) . expect ( "run_async broke invariant" )
83
84
}
84
85
}
85
86
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 > ) ;
91
-
92
- enum BounceInner < ' a > {
87
+ enum Bounce < ' a > {
93
88
Done ( UntypedRetVal ) ,
94
89
More ( BoxFuture < ' a , ResumeVal > ) ,
90
+ BoundExpired ,
91
+ }
92
+
93
+ /// A simple future that yields once. We use this to yield when a runtime bound is reached.
94
+ ///
95
+ /// Inspired by Tokio's `yield_now()`.
96
+ struct YieldNow {
97
+ yielded : bool ,
98
+ }
99
+
100
+ impl YieldNow {
101
+ fn new ( ) -> Self {
102
+ Self { yielded : false }
103
+ }
104
+ }
105
+
106
+ impl Future for YieldNow {
107
+ type Output = ( ) ;
108
+ fn poll ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < ( ) > {
109
+ if self . yielded {
110
+ Poll :: Ready ( ( ) )
111
+ } else {
112
+ self . yielded = true ;
113
+ cx. waker ( ) . wake_by_ref ( ) ;
114
+ Poll :: Pending
115
+ }
116
+ }
95
117
}
96
118
97
119
impl InstanceHandle {
@@ -101,51 +123,20 @@ impl InstanceHandle {
101
123
/// that use `Vmctx::block_on` and provides the trampoline that `.await`s those futures on
102
124
/// behalf of the guest.
103
125
///
126
+ /// If `runtime_bound` is provided, it will also pause the Wasm execution and yield a future
127
+ /// that resumes it after (approximately) that many Wasm opcodes have executed.
128
+ ///
104
129
/// # `Vmctx` Restrictions
105
130
///
106
131
/// This method permits the use of `Vmctx::block_on`, but disallows all other uses of `Vmctx::
107
132
/// yield_val_expecting_val` and family (`Vmctx::yield_`, `Vmctx::yield_expecting_val`,
108
133
/// `Vmctx::yield_val`).
109
- ///
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
- /// }
134
- ///
135
- /// instance.run_async("entrypoint", &[], |f| block_in_place(f)).await.unwrap();
136
- /// # }
137
- /// ```
138
- ///
139
- /// [tokio]: https://docs.rs/tokio/0.2.21/tokio/task/fn.block_in_place.html
140
- pub async fn run_async < ' a , F > (
134
+ pub async fn run_async < ' a > (
141
135
& ' a mut self ,
142
136
entrypoint : & ' a str ,
143
137
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
- {
138
+ runtime_bound : Option < u64 > ,
139
+ ) -> Result < UntypedRetVal , Error > {
149
140
if self . is_yielded ( ) {
150
141
return Err ( Error :: Unsupported (
151
142
"cannot run_async a yielded instance" . to_owned ( ) ,
@@ -156,37 +147,40 @@ impl InstanceHandle {
156
147
let mut resume_val: Option < ResumeVal > = None ;
157
148
loop {
158
149
// Run the WebAssembly program
159
- let bounce = wrap_blocking ( & mut || {
150
+ let bounce = {
160
151
let run_result = if self . is_yielded ( ) {
161
152
// A previous iteration of the loop stored the ResumeVal in
162
153
// `resume_val`, send it back to the guest ctx and continue
163
154
// running:
164
- self . resume_with_val_impl (
155
+ let result = self . resume_with_val_impl (
165
156
resume_val
166
157
. take ( )
167
158
. expect ( "is_yielded implies resume_value is some" ) ,
168
159
true ,
160
+ ) ?;
161
+ Ok ( InternalRunResult :: Normal ( result) )
162
+ } else if self . is_bound_expired ( ) {
163
+ self . resume_bounded (
164
+ runtime_bound. expect ( "should have bound if guest had expired bound" ) ,
169
165
)
170
166
} else {
171
167
// This is the first iteration, call the entrypoint:
172
168
let func = self . module . get_export_func ( entrypoint) ?;
173
- self . run_func ( func, args, true )
169
+ self . run_func ( func, args, true , runtime_bound )
174
170
} ;
175
171
match run_result? {
176
- RunResult :: Returned ( rval) => {
172
+ InternalRunResult :: Normal ( RunResult :: Returned ( rval) ) => {
177
173
// Finished running, return UntypedReturnValue
178
- return Ok ( Bounce ( BounceInner :: Done ( rval) ) ) ;
174
+ Ok ( Bounce :: Done ( rval) )
179
175
}
180
- RunResult :: Yielded ( yval) => {
176
+ InternalRunResult :: Normal ( RunResult :: Yielded ( yval) ) => {
181
177
// Check if the yield came from Vmctx::block_on:
182
178
if yval. is :: < YieldedFuture > ( ) {
183
179
let YieldedFuture ( future) = * yval. downcast :: < YieldedFuture > ( ) . unwrap ( ) ;
184
180
// Rehydrate the lifetime from `'static` to `'a`, which
185
181
// is morally the same lifetime as was passed into
186
182
// `Vmctx::block_on`.
187
- Ok ( Bounce ( BounceInner :: More (
188
- future as BoxFuture < ' a , ResumeVal > ,
189
- ) ) )
183
+ Ok ( Bounce :: More ( future as BoxFuture < ' a , ResumeVal > ) )
190
184
} else {
191
185
// Any other yielded value is not supported - die with an error.
192
186
Err ( Error :: Unsupported (
@@ -195,17 +189,22 @@ impl InstanceHandle {
195
189
) )
196
190
}
197
191
}
192
+ InternalRunResult :: BoundExpired => Ok ( Bounce :: BoundExpired ) ,
198
193
}
199
- } ) ?;
194
+ } ?;
200
195
match bounce {
201
- Bounce ( BounceInner :: Done ( rval) ) => return Ok ( rval) ,
202
- Bounce ( BounceInner :: More ( fut) ) => {
196
+ Bounce :: Done ( rval) => return Ok ( rval) ,
197
+ Bounce :: More ( fut) => {
203
198
// await on the computation. Store its result in
204
199
// `resume_val`.
205
200
resume_val = Some ( fut. await ) ;
206
201
// Now we want to `Instance::resume_with_val` and start
207
202
// this cycle over.
208
203
}
204
+ Bounce :: BoundExpired => {
205
+ // Await on a simple future that yields once then is ready.
206
+ YieldNow :: new ( ) . await
207
+ }
209
208
}
210
209
}
211
210
}
0 commit comments