Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Source; | |
| 3 | use Trees; | |
| 4 | use Logging; | |
| 5 | ||
| 6 | // SPILL_AWAITS — wraps operand-position subexpressions evaluated | |
| 7 | // to the left of an `await` in `Expressions.SPILL` so their | |
| 8 | // values land in a frame field rather than the CLR stack. AWAIT | |
| 9 | // suspends with an internal `leave`, and the CLR requires the | |
| 10 | // evaluation stack to be empty at the suspend point — anything | |
| 11 | // not yet consumed by an enclosing operation would be lost on | |
| 12 | // re-entry (CLR locals reset; only frame fields persist). | |
| 13 | // | |
| 14 | // Rule (applied bottom-up at every composite node): if any | |
| 15 | // later-evaluated child contains an `await`, wrap every prior | |
| 16 | // child in `SPILL`. The operand-position contract (one stack | |
| 17 | // value per operand) makes the rule uniform — no purity | |
| 18 | // analysis needed. | |
| 19 | class SPILL_AWAITS: Visitor is | |
| 20 | init() is | |
| 21 | super.init(); | |
| 22 | si | |
| 23 | ||
| 24 | apply(node: Node) is | |
| 25 | node.walk(self); | |
| 26 | si | |
| 27 | ||
| 28 | visit(b: Expressions.BINARY) is | |
| 29 | if _contains_await(b.right) then | |
| 30 | _wrap_unless_await(b, b.left); | |
| 31 | fi | |
| 32 | si | |
| 33 | ||
| 34 | // CALL: receiver first, then args in source order. An await | |
| 35 | // in any arg means wrap every prior arg, plus the | |
| 36 | // MEMBER receiver if this is an instance call. | |
| 37 | visit(call: Expressions.CALL) is | |
| 38 | if call.arguments.count == 0 then | |
| 39 | return; | |
| 40 | fi | |
| 41 | ||
| 42 | let elements = call.arguments.expressions; | |
| 43 | ||
| 44 | let latest_await_index mut = -1; | |
| 45 | for i in 0..elements.count do | |
| 46 | if _contains_await(elements[i]) then | |
| 47 | latest_await_index = i; | |
| 48 | fi | |
| 49 | od | |
| 50 | ||
| 51 | if latest_await_index < 0 then | |
| 52 | return; | |
| 53 | fi | |
| 54 | ||
| 55 | if isa Expressions.MEMBER(call.function) then | |
| 56 | let mem = cast Expressions.MEMBER(call.function); | |
| 57 | if !_is_await_node(mem.left) /\ !_is_spill_node(mem.left) then | |
| 58 | mem.replace_child( | |
| 59 | mem.left, | |
| 60 | Expressions.SPILL(mem.left.location, mem.left) | |
| 61 | ); | |
| 62 | fi | |
| 63 | fi | |
| 64 | ||
| 65 | for i in 0..latest_await_index do | |
| 66 | let e = elements[i]; | |
| 67 | if !_is_await_node(e) then | |
| 68 | call.arguments.replace_element( | |
| 69 | i, | |
| 70 | Expressions.SPILL(e.location, e) | |
| 71 | ); | |
| 72 | fi | |
| 73 | od | |
| 74 | si | |
| 75 | ||
| 76 | visit(idx: Expressions.INDEX) is | |
| 77 | if _contains_await(idx.index) then | |
| 78 | if !_is_await_node(idx.left) /\ !_is_spill_node(idx.left) then | |
| 79 | idx.replace_child( | |
| 80 | idx.left, | |
| 81 | Expressions.SPILL(idx.left.location, idx.left) | |
| 82 | ); | |
| 83 | fi | |
| 84 | fi | |
| 85 | si | |
| 86 | ||
| 87 | visit(`new: Expressions.NEW) is | |
| 88 | if `new.arguments.count == 0 then | |
| 89 | return; | |
| 90 | fi | |
| 91 | ||
| 92 | let elements = `new.arguments.expressions; | |
| 93 | ||
| 94 | let latest_await_index mut = -1; | |
| 95 | for i in 0..elements.count do | |
| 96 | if _contains_await(elements[i]) then | |
| 97 | latest_await_index = i; | |
| 98 | fi | |
| 99 | od | |
| 100 | ||
| 101 | if latest_await_index < 0 then | |
| 102 | return; | |
| 103 | fi | |
| 104 | ||
| 105 | for i in 0..latest_await_index do | |
| 106 | let e = elements[i]; | |
| 107 | if !_is_await_node(e) then | |
| 108 | `new.arguments.replace_element( | |
| 109 | i, | |
| 110 | Expressions.SPILL(e.location, e) | |
| 111 | ); | |
| 112 | fi | |
| 113 | od | |
| 114 | si | |
| 115 | ||
| 116 | visit(t: Expressions.TUPLE) is | |
| 117 | if t.elements.count == 0 then | |
| 118 | return; | |
| 119 | fi | |
| 120 | ||
| 121 | let elements = t.elements.expressions; | |
| 122 | ||
| 123 | let latest_await_index mut = -1; | |
| 124 | for i in 0..elements.count do | |
| 125 | if _contains_await(elements[i]) then | |
| 126 | latest_await_index = i; | |
| 127 | fi | |
| 128 | od | |
| 129 | ||
| 130 | if latest_await_index < 0 then | |
| 131 | return; | |
| 132 | fi | |
| 133 | ||
| 134 | for i in 0..latest_await_index do | |
| 135 | let e = elements[i]; | |
| 136 | if !_is_await_node(e) then | |
| 137 | t.elements.replace_element( | |
| 138 | i, | |
| 139 | Expressions.SPILL(e.location, e) | |
| 140 | ); | |
| 141 | fi | |
| 142 | od | |
| 143 | si | |
| 144 | ||
| 145 | _wrap_unless_await(parent: Expressions.Expression, child: Expressions.Expression) is | |
| 146 | if _is_await_node(child) then | |
| 147 | return; | |
| 148 | fi | |
| 149 | parent.replace_child(child, Expressions.SPILL(child.location, child)); | |
| 150 | si | |
| 151 | ||
| 152 | _is_await_node(e: Expressions.Expression) -> bool => | |
| 153 | isa Expressions.AWAIT(e); | |
| 154 | ||
| 155 | _is_spill_node(e: Expressions.Expression) -> bool => | |
| 156 | isa Expressions.SPILL(e); | |
| 157 | ||
| 158 | _contains_await(e: Expressions.Expression) -> bool is | |
| 159 | let scanner = CONTAINS_AWAIT_SCANNER(); | |
| 160 | e.walk(scanner); | |
| 161 | return scanner.found; | |
| 162 | si | |
| 163 | si | |
| 164 | ||
| 165 | // Scans for any `Expressions.AWAIT`. Stops at function-literal | |
| 166 | // boundaries — a nested lambda's awaits belong to its own | |
| 167 | // state machine. | |
| 168 | class CONTAINS_AWAIT_SCANNER: Visitor is | |
| 169 | found: bool public; | |
| 170 | ||
| 171 | init() is | |
| 172 | super.init(); | |
| 173 | si | |
| 174 | ||
| 175 | pre(a: Expressions.AWAIT) -> bool is | |
| 176 | found = true; | |
| 177 | return true; | |
| 178 | si | |
| 179 | ||
| 180 | pre(f: Expressions.FUNCTION) -> bool => true; | |
| 181 | si | |
| 182 | ||
| 183 | // Scans for any state-machine suspend site (`Expressions.AWAIT` | |
| 184 | // or `Statements.YIELD`) without descending into a nested | |
| 185 | // function literal. Used by generate-il to decide whether a | |
| 186 | // composite value-BLOCK (LIST in expression position, `if`/ | |
| 187 | // `case` in expression position) needs to be spilled to a | |
| 188 | // frame field — captured-suspend IL inside the BLOCK has to | |
| 189 | // live in the outer MoveNext stream, not get replayed inside | |
| 190 | // a consumer's stack setup. | |
| 191 | class CONTAINS_SUSPEND_SCANNER: Visitor is | |
| 192 | found: bool public; | |
| 193 | ||
| 194 | init() is | |
| 195 | super.init(); | |
| 196 | si | |
| 197 | ||
| 198 | pre(a: Expressions.AWAIT) -> bool is | |
| 199 | found = true; | |
| 200 | return true; | |
| 201 | si | |
| 202 | ||
| 203 | pre(y: Statements.YIELD) -> bool is | |
| 204 | found = true; | |
| 205 | return true; | |
| 206 | si | |
| 207 | ||
| 208 | pre(f: Expressions.FUNCTION) -> bool => true; | |
| 209 | si | |
| 210 | ||
| 211 | si |