yield.inc 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #if !defined MAX_NESTED_ITERATORS
  2. #define MAX_NESTED_ITERATORS (4)
  3. #endif
  4. #define yield%0\32;%1; yield%0%1;
  5. #define yieldreturn%0; Iter_YieldReturn(%0);
  6. #define yieldbreak%0; return;
  7. #define iteryield%9;_:(%9=Iter%9@%0$[_:%2]%9; F@o)&&(I@=-1,Iter_YieldEnter())))?I@:_:F@u:F@l:(F@C:Iter_Func@%0$(),--YSI_gIteratorDepth)?1:2;Iter_YieldLoop();
  8. #define F@C:%0(,%1) %0(%1)
  9. #if !defined MAX_YIELD_MEMORY
  10. #define MAX_YIELD_MEMORY (512)
  11. #endif
  12. enum E_ITER_YIELD
  13. {
  14. E_ITER_YIELD_STACK_START, // Where the pointer was before the loop.
  15. E_ITER_YIELD_STACK_END, // Where the pointer was at `yield`.
  16. E_ITER_YIELD_STACK_SIZE, // `malloc`ed memory.
  17. E_ITER_YIELD_HEAP_START, // Where the pointer was before the loop.
  18. E_ITER_YIELD_HEAP_END, // Where the pointer was at `yield`.
  19. E_ITER_YIELD_HEAP_SIZE, // `malloc`ed memory.
  20. E_ITER_YIELD_FIRST, // First call of a pair to `Iter_YieldLoop`.
  21. E_ITER_YIELD_FRM, // The iterator function context.
  22. E_ITER_YIELD_CIP, // The iterator function continuation.
  23. E_ITER_YIELD_FRAME, // The return frame from `yield`.
  24. E_ITER_YIELD_RETURN // The return address from `yield`.
  25. }
  26. static stock
  27. YSI_g_sIteratorStack[MAX_NESTED_ITERATORS][E_ITER_YIELD],
  28. YSI_g_sStack[MAX_YIELD_MEMORY],
  29. YSI_g_sStackPtr;
  30. stock
  31. YSI_gIteratorDepth = -1;
  32. static
  33. YSI_g_sPtr;
  34. #pragma unused YSI_g_sPtr
  35. /**
  36. * <remarks>
  37. * </remarks>
  38. */
  39. hook public OnScriptInit()
  40. {
  41. #emit CONST.pri YSI_g_sStack
  42. #emit STOR.pri YSI_g_sStackPtr
  43. }
  44. /**
  45. * <remarks>
  46. * </remarks>
  47. */
  48. stock bool:Iter_YieldEnter()
  49. {
  50. // This is called as:
  51. //
  52. // iter_var = (I@ = -1, Iter_YieldEnter() ? I@ : (iter_func(), --YSI_gIteratorDepth));
  53. //
  54. // (More-or-less, there's an extra conditional to avoid a compiler bug).
  55. //
  56. // This means we can skip ever entering the iterator in error cases. Better
  57. // yet, we can use the default iterator value for the fail case! It is
  58. // important that the `true` case is the fail case, as the return value may
  59. // often be a non-zero jump address (i.e. the return address).
  60. if (++YSI_gIteratorDepth >= MAX_NESTED_ITERATORS)
  61. {
  62. P:E("Too many nested `foreach` yield loops. Increase `MAX_NESTED_ITERATORS`.");
  63. return true;
  64. }
  65. {}
  66. // Load a pointer to the first address. We know we are in range, so there's
  67. // no `BOUNDS` check here.
  68. #emit CONST.alt YSI_g_sIteratorStack
  69. #emit LOAD.pri YSI_gIteratorDepth
  70. #emit IDXADDR
  71. #emit MOVE.alt
  72. #emit LOAD.I
  73. #emit ADD
  74. #emit MOVE.alt
  75. // Blank a load of data.
  76. #emit ZERO.pri
  77. #emit FILL 44
  78. // Save the stack size after this function ends.
  79. #emit LCTRL 4
  80. #emit ADD.C 12
  81. #emit STOR.I
  82. #emit XCHG
  83. #emit ADD.C 12 // Move on to `E_ITER_YIELD_HEAP_START`.
  84. #emit XCHG
  85. // Save the heap size.
  86. #emit LCTRL 2
  87. #emit STOR.I
  88. #emit XCHG
  89. #emit ADD.C 24 // Move on to `E_ITER_YIELD_FRAME`.
  90. #emit XCHG
  91. // Load of data already blanked.
  92. // Store the previous function's frame pointer.
  93. #emit LOAD.S.pri 0
  94. #emit STOR.I
  95. #emit XCHG
  96. #emit ADD.C 4 // Move on to `E_ITER_YIELD_RETURN`.
  97. #emit XCHG
  98. // Store the return address (next instruction to execute).
  99. #emit LOAD.S.pri 4
  100. #emit STOR.I
  101. return false;
  102. }
  103. /**
  104. * <remarks>
  105. * <p>Because of the strange way we manipulate the stack, this function actually
  106. * gets called twice as often as you would expect. Essentially, for this
  107. * (psudo-)loop:</p>
  108. *
  109. * <code>
  110. * for (new i = iter_func(); Iter_YieldLoop(); ) <br />
  111. * { <br />
  112. * }
  113. * </code>
  114. *
  115. * <p>The loop is entered and <c>iter_func()</c> is called. This indirectly
  116. * calls <c>yield</c>, which returns to the call point of that function. The
  117. * loop check is then entered and <c>Iter_YieldLoop()</c> is called. Depending
  118. * on if <c>yield</c> was actually used, the main loop body is entered. At the
  119. * end of that iteration, the loop check is run again and so
  120. * <c>Iter_YieldLoop()</c> is called again.</p>
  121. *
  122. * <p>This is where it gets wierd!</p>
  123. *
  124. * <p><c>Iter_YieldLoop()</c> does a stack copy and a jump in to the earlier
  125. * call to <c>iter_func</c>, whose return address is earlier in the code. When
  126. * a <c>yield</c> is done again, that return is to the first part of the
  127. * <c>for</c> loop, which then instantly enters the loop check section and calls
  128. * <c>Iter_YieldLoop()</c> again (as a side-effect, saving the iterator value in
  129. * the loop variable).</p>
  130. *
  131. * <p>So for <c>N</c> iterations of the loop, <c>Iter_YieldLoop()</c> is called
  132. * <c>2N + 1</c> times, and should be made aware of which phase of its calls it
  133. * is in.</p>
  134. *
  135. * <p>This is, of course, made more complicated by nested loops, but that just
  136. * means we need to store the state on our own stack.</p>
  137. * </remarks>
  138. */
  139. static stock
  140. size_l,
  141. src_l;
  142. stock bool:Iter_YieldLoop()
  143. {
  144. if ((YSI_g_sIteratorStack[YSI_gIteratorDepth + 1][E_ITER_YIELD_FIRST] ^= 1))
  145. {
  146. // If there is nothing allocated here, we fell out of the iterator
  147. // function and so the loop is over.
  148. if (!YSI_g_sIteratorStack[YSI_gIteratorDepth + 1][E_ITER_YIELD_STACK_SIZE])
  149. {
  150. // Release our stack.
  151. return false;
  152. }
  153. // Otherwise, the iterator continued, so the loop should as well.
  154. }
  155. else
  156. {
  157. #emit INC YSI_gIteratorDepth
  158. #emit CONST.alt YSI_g_sIteratorStack
  159. #emit LOAD.pri YSI_gIteratorDepth
  160. #emit IDXADDR
  161. #emit MOVE.alt
  162. #emit LOAD.I
  163. #emit ADD
  164. #emit ADD.C 8
  165. #emit STOR.pri YSI_g_sPtr
  166. #emit MOVE.alt
  167. // Restore the heap.
  168. #emit CONST.pri 3
  169. #emit LIDX
  170. #emit PUSH.pri
  171. #emit PUSH.pri
  172. #emit PUSH.C 0
  173. #emit LOAD.alt YSI_g_sStackPtr
  174. #emit SUB.alt
  175. #emit PUSH.pri
  176. #emit STOR.pri YSI_g_sStackPtr
  177. #emit LOAD.alt YSI_g_sPtr
  178. #emit CONST.pri 1
  179. #emit LIDX
  180. #emit PUSH.pri
  181. #emit CONST.pri 2
  182. #emit LIDX
  183. #emit SCTRL 2
  184. #emit PUSH.C 20
  185. #emit SYSREQ.C memcpy
  186. // Restore the stack.
  187. #emit LOAD.alt YSI_g_sPtr
  188. #emit CONST.pri 0xFFFFFFFF // -1
  189. #emit LIDX
  190. #emit SCTRL 4
  191. #emit LREF.pri YSI_g_sPtr // Or ZERO.pri, LIDX
  192. #emit PUSH.pri
  193. #emit PUSH.pri
  194. #emit LOAD.alt YSI_g_sStackPtr
  195. #emit SUB.alt
  196. #emit ZERO.alt
  197. #emit PUSH.alt
  198. #emit SREF.alt YSI_g_sPtr
  199. #emit PUSH.pri
  200. #emit STOR.pri YSI_g_sStackPtr
  201. #emit LCTRL 4
  202. #emit ADD.C 16
  203. #emit PUSH.pri
  204. #emit PUSH.C 20
  205. #emit SYSREQ.C memcpy
  206. #emit STACK 24
  207. // Jump back in to our earlier function.
  208. #emit LOAD.alt YSI_g_sPtr
  209. #emit CONST.pri 5
  210. #emit LIDX
  211. #emit SCTRL 5
  212. #emit CONST.pri 6
  213. #emit LIDX
  214. #emit SCTRL 6
  215. // Technically, we never return from here, but the compiler can't know!
  216. }
  217. return true;
  218. }
  219. stock Iter_YieldReturn(value)
  220. {
  221. // This does the return through the global scope.
  222. I@ = value;
  223. // Load a pointer to the first address. We know we are in range, so there's
  224. // no `BOUNDS` check here.
  225. #emit CONST.alt YSI_g_sIteratorStack
  226. #emit LOAD.pri YSI_gIteratorDepth
  227. #emit IDXADDR
  228. #emit MOVE.alt
  229. #emit LOAD.I
  230. #emit ADD
  231. #emit STOR.pri YSI_g_sPtr
  232. // Stack (excluding this function and intermediate results).
  233. #emit ADD.C 4
  234. #emit MOVE.alt
  235. #emit LCTRL 4
  236. #emit ADD.C 16
  237. #emit STOR.I
  238. #emit LREF.alt YSI_g_sPtr
  239. #emit SUB.alt
  240. #emit MOVE.alt
  241. #emit PUSH.pri
  242. #emit PUSH.pri
  243. #emit PUSH.C 0
  244. #emit LOAD.pri YSI_g_sPtr
  245. #emit ADD.C 8
  246. #emit XCHG
  247. #emit STOR.I
  248. #emit LCTRL 4
  249. #emit ADD.C 28
  250. #emit PUSH.pri
  251. #emit LOAD.alt YSI_g_sStackPtr
  252. #emit PUSH.alt
  253. #emit PUSH.C 20
  254. #emit SYSREQ.C memcpy
  255. #emit MOVE.pri
  256. #emit STACK 20
  257. #emit POP.alt
  258. #emit ADD
  259. #emit STOR.pri YSI_g_sStackPtr
  260. #emit LOAD.pri YSI_g_sPtr
  261. #emit ADD.C 12
  262. #emit STOR.pri YSI_g_sPtr
  263. // Heap.
  264. #emit ADD.C 4
  265. #emit MOVE.alt
  266. #emit LCTRL 2
  267. #emit STOR.I
  268. // Using `LCTRL 2` is faster than saving and restoring the value.
  269. #emit LREF.alt YSI_g_sPtr
  270. #emit SUB
  271. #emit MOVE.alt
  272. #emit PUSH.pri
  273. #emit PUSH.pri
  274. #emit PUSH.C 0
  275. #emit LOAD.pri YSI_g_sPtr
  276. #emit ADD.C 8
  277. #emit XCHG
  278. #emit STOR.I
  279. #emit LCTRL 2
  280. #emit PUSH.pri
  281. #emit LOAD.alt YSI_g_sStackPtr
  282. #emit PUSH.alt
  283. #emit PUSH.C 20
  284. #emit SYSREQ.C memcpy
  285. #emit MOVE.pri
  286. #emit STACK 20
  287. #emit POP.alt
  288. #emit ADD
  289. #emit STOR.pri YSI_g_sStackPtr
  290. #emit LOAD.pri YSI_g_sPtr
  291. #emit ADD.C 16
  292. #emit MOVE.alt
  293. #emit LOAD.S.pri 0
  294. #emit STOR.I
  295. // Frame.
  296. #emit MOVE.pri
  297. #emit ADD.C 4
  298. #emit MOVE.alt
  299. #emit LOAD.S.pri 4
  300. #emit STOR.I
  301. // Go to the caller. From this point on, we can't use any stack-local
  302. // storage, because we just destroyed the stack!
  303. #emit CONST.pri 1
  304. #emit LIDX
  305. #emit SCTRL 5
  306. #emit CONST.pri 0xFFFFFFF8 // -8
  307. #emit LIDX
  308. #emit SCTRL 4
  309. #emit CONST.pri 0xFFFFFFFB // -5
  310. #emit LIDX
  311. #emit SCTRL 2
  312. // No longer in this iterator.
  313. #emit DEC YSI_gIteratorDepth
  314. #emit CONST.pri 2
  315. #emit LIDX
  316. #emit SCTRL 6
  317. }