Skip to content
← Back

src/syntax/process/spill_awaits.ghul

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(construct: Expressions.CONSTRUCT) is
88
if construct.arguments.count == 0 then
89
return
90
fi
91
92
let elements = construct.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
construct.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(y: Statements.YIELD_ALL) -> bool is
209
found = true
210
return true
211
si
212
213
pre(f: Expressions.FUNCTION) -> bool => true
214
si
215
216
si