Skip to content

Commit c443688

Browse files
authored
[NFC] Add more overview docs for stack switching (#7806)
1 parent 15c1d2d commit c443688

File tree

1 file changed

+36
-3
lines changed

1 file changed

+36
-3
lines changed

src/wasm-interpreter.h

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)