* Because of the strange way we manipulate the stack, this function actually
* gets called twice as often as you would expect. Essentially, for this
* (psudo-)loop:
*
*
* for (new i = iter_func(); Iter_YieldLoop(); )
* {
* }
*
*
* The loop is entered and iter_func() is called. This indirectly
* calls yield, which returns to the call point of that function. The
* loop check is then entered and Iter_YieldLoop() is called. Depending
* on if yield was actually used, the main loop body is entered. At the
* end of that iteration, the loop check is run again and so
* Iter_YieldLoop() is called again.
*
* This is where it gets wierd!
*
* Iter_YieldLoop() does a stack copy and a jump in to the earlier
* call to iter_func, whose return address is earlier in the code. When
* a yield is done again, that return is to the first part of the
* for loop, which then instantly enters the loop check section and calls
* Iter_YieldLoop() again (as a side-effect, saving the iterator value in
* the loop variable).
*
* So for N iterations of the loop, Iter_YieldLoop() is called
* 2N + 1 times, and should be made aware of which phase of its calls it
* is in.
*
* This is, of course, made more complicated by nested loops, but that just
* means we need to store the state on our own stack.
*
*/
static stock
size_l,
src_l;
stock bool:Iter_YieldLoop()
{
if ((YSI_g_sIteratorStack[YSI_gIteratorDepth + 1][E_ITER_YIELD_FIRST] ^= 1))
{
// If there is nothing allocated here, we fell out of the iterator
// function and so the loop is over.
if (!YSI_g_sIteratorStack[YSI_gIteratorDepth + 1][E_ITER_YIELD_STACK_SIZE])
{
// Release our stack.
return false;
}
// Otherwise, the iterator continued, so the loop should as well.
}
else
{
#emit INC YSI_gIteratorDepth
#emit CONST.alt YSI_g_sIteratorStack
#emit LOAD.pri YSI_gIteratorDepth
#emit IDXADDR
#emit MOVE.alt
#emit LOAD.I
#emit ADD
#emit ADD.C 8
#emit STOR.pri YSI_g_sPtr
#emit MOVE.alt
// Restore the heap.
#emit CONST.pri 3
#emit LIDX
#emit PUSH.pri
#emit PUSH.pri
#emit PUSH.C 0
#emit LOAD.alt YSI_g_sStackPtr
#emit SUB.alt
#emit PUSH.pri
#emit STOR.pri YSI_g_sStackPtr
#emit LOAD.alt YSI_g_sPtr
#emit CONST.pri 1
#emit LIDX
#emit PUSH.pri
#emit CONST.pri 2
#emit LIDX
#emit SCTRL 2
#emit PUSH.C 20
#emit SYSREQ.C memcpy
// Restore the stack.
#emit LOAD.alt YSI_g_sPtr
#emit CONST.pri 0xFFFFFFFF // -1
#emit LIDX
#emit SCTRL 4
#emit LREF.pri YSI_g_sPtr // Or ZERO.pri, LIDX
#emit PUSH.pri
#emit PUSH.pri
#emit LOAD.alt YSI_g_sStackPtr
#emit SUB.alt
#emit ZERO.alt
#emit PUSH.alt
#emit SREF.alt YSI_g_sPtr
#emit PUSH.pri
#emit STOR.pri YSI_g_sStackPtr
#emit LCTRL 4
#emit ADD.C 16
#emit PUSH.pri
#emit PUSH.C 20
#emit SYSREQ.C memcpy
#emit STACK 24
// Jump back in to our earlier function.
#emit LOAD.alt YSI_g_sPtr
#emit CONST.pri 5
#emit LIDX
#emit SCTRL 5
#emit CONST.pri 6
#emit LIDX
#emit SCTRL 6
// Technically, we never return from here, but the compiler can't know!
}
return true;
}
stock Iter_YieldReturn(value)
{
// This does the return through the global scope.
I@ = value;
// Load a pointer to the first address. We know we are in range, so there's
// no `BOUNDS` check here.
#emit CONST.alt YSI_g_sIteratorStack
#emit LOAD.pri YSI_gIteratorDepth
#emit IDXADDR
#emit MOVE.alt
#emit LOAD.I
#emit ADD
#emit STOR.pri YSI_g_sPtr
// Stack (excluding this function and intermediate results).
#emit ADD.C 4
#emit MOVE.alt
#emit LCTRL 4
#emit ADD.C 16
#emit STOR.I
#emit LREF.alt YSI_g_sPtr
#emit SUB.alt
#emit MOVE.alt
#emit PUSH.pri
#emit PUSH.pri
#emit PUSH.C 0
#emit LOAD.pri YSI_g_sPtr
#emit ADD.C 8
#emit XCHG
#emit STOR.I
#emit LCTRL 4
#emit ADD.C 28
#emit PUSH.pri
#emit LOAD.alt YSI_g_sStackPtr
#emit PUSH.alt
#emit PUSH.C 20
#emit SYSREQ.C memcpy
#emit MOVE.pri
#emit STACK 20
#emit POP.alt
#emit ADD
#emit STOR.pri YSI_g_sStackPtr
#emit LOAD.pri YSI_g_sPtr
#emit ADD.C 12
#emit STOR.pri YSI_g_sPtr
// Heap.
#emit ADD.C 4
#emit MOVE.alt
#emit LCTRL 2
#emit STOR.I
// Using `LCTRL 2` is faster than saving and restoring the value.
#emit LREF.alt YSI_g_sPtr
#emit SUB
#emit MOVE.alt
#emit PUSH.pri
#emit PUSH.pri
#emit PUSH.C 0
#emit LOAD.pri YSI_g_sPtr
#emit ADD.C 8
#emit XCHG
#emit STOR.I
#emit LCTRL 2
#emit PUSH.pri
#emit LOAD.alt YSI_g_sStackPtr
#emit PUSH.alt
#emit PUSH.C 20
#emit SYSREQ.C memcpy
#emit MOVE.pri
#emit STACK 20
#emit POP.alt
#emit ADD
#emit STOR.pri YSI_g_sStackPtr
#emit LOAD.pri YSI_g_sPtr
#emit ADD.C 16
#emit MOVE.alt
#emit LOAD.S.pri 0
#emit STOR.I
// Frame.
#emit MOVE.pri
#emit ADD.C 4
#emit MOVE.alt
#emit LOAD.S.pri 4
#emit STOR.I
// Go to the caller. From this point on, we can't use any stack-local
// storage, because we just destroyed the stack!
#emit CONST.pri 1
#emit LIDX
#emit SCTRL 5
#emit CONST.pri 0xFFFFFFF8 // -8
#emit LIDX
#emit SCTRL 4
#emit CONST.pri 0xFFFFFFFB // -5
#emit LIDX
#emit SCTRL 2
// No longer in this iterator.
#emit DEC YSI_gIteratorDepth
#emit CONST.pri 2
#emit LIDX
#emit SCTRL 6
}