@@ -184,9 +184,42 @@ struct ExnData {
184184// but the shared idea is that to resume code we simply need to get to where we
185185// were when we suspended, so we have a "resuming" mode in which we walk the IR
186186// but do not execute normally. While resuming we basically re-wind the stack,
187- // using data we stashed on the side while unwinding. For example, if we unwind
188- // an If instruction then we note which arm of the If we unwound from, and then
189- // when we re-wind we enter that proper arm, etc.
187+ // using data we stashed on the side while unwinding.
188+ //
189+ // The key idea in this approach to suspending and resuming is that to suspend
190+ // you want to unwind the stack - you "jump" back to some outer scope - and to
191+ // reume, we want to rewind the stack - to get everything back exactly the way
192+ // it was, so we can pick things back up. And, to achieve that, we really just
193+ // need two things:
194+ // * To rewind the call stack. If we called foo() and then bar(), we want to
195+ // have foo and bar on the stack, so that when bar finishes, we return to
196+ // foo, etc., as if we never suspended/resumed.
197+ // * To have the same values as before. If we are an i32.add, and we
198+ // suspended in the second arm, we need to have the same value for the
199+ // first arm as before the suspend.
200+ //
201+ // Implementing these is conceptually simple:
202+ // * For control flow, each structure handles itself. For example, if we
203+ // unwind an If instruction then we note which arm of the If we unwound
204+ // from, and then when we re-wind we enter that proper arm. For a Block,
205+ // we can note the index we had executed up to, etc.
206+ // * For values, we just save them automatically (specific visitFoo methods
207+ // do not need to do anything themselves), see below on |valueStack|. (Note
208+ // that we do an optimization for speed that avoids using that stack unless
209+ // actually necessary.)
210+ //
211+ // Once we have those two things handled, pretty much everything else "just
212+ // works," and 99% of instructions need no special handling at all. Even some
213+ // instructions you might think would need custom code do not, like CallRef:
214+ // while that instruction does a call and changes the call stack, it calls the
215+ // value of its last child, so if we restore that child's value while resuming,
216+ // the normal code is exactly what we want (calling that child rewinds the stack
217+ // in exactly the right way). That is, once control flow structures know what to
218+ // do (which is unique to each one, but trivial), and once we have values
219+ // restored, the interpreter "wants" to return to the exact place we suspended
220+ // at, and we just let it do that. (And when it reaches the place we suspended
221+ // from, we do a special operation to stop resuming, and to proceed with normal
222+ // execution, as if we never suspended.)
190223//
191224// This is not the most efficient way to pause and resume execution (a program
192225// counter/goto would be much faster!) but this is very simple to implement in
0 commit comments