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(`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