Skip to content
← Back

src/ir/values/fused_consumer.ghul

1
namespace IR.Values is
2
use Semantic.Types.Type;
3
4
// One map/filter stage of a fused-consumer loop. The stage's lambda
5
// delegate is hoisted into a local (built once) and `apply` invokes it
6
// on the current element, yielding either a bool (a `filter` stage -
7
// branch to the next element when false) or the transformed element (a
8
// `map` stage - stored into `output_il_name` for the next stage). The
9
// delegate is called rather than inlined so a lambda that captures an
10
// enclosing local (or nests another closure) keeps its normal capture
11
// context.
12
class FUSED_CONSUMER_STAGE is
13
is_filter: bool public;
14
delegate_il_name: string public;
15
delegate_type: Type public;
16
delegate_init: Value public;
17
apply: Value public;
18
output_il_name: string public;
19
output_type: Type? public;
20
21
// A `take`/`skip` stage carries no delegate: the count is evaluated
22
// once into `counter_il_name` and each element decrements it (take
23
// leaves the loop when it runs out, skip drops the element while it
24
// remains). `is_countdown` marks either.
25
is_take: bool public;
26
is_skip: bool public;
27
counter_il_name: string public;
28
count_init: Value? public;
29
30
is_countdown: bool => is_take \/ is_skip;
31
32
init(is_filter: bool, delegate_il_name: string, delegate_type: Type, delegate_init: Value, apply: Value, output_il_name: string, output_type: Type?) is
33
self.is_filter = is_filter;
34
self.delegate_il_name = delegate_il_name;
35
self.delegate_type = delegate_type;
36
self.delegate_init = delegate_init;
37
self.apply = apply;
38
self.output_il_name = output_il_name;
39
self.output_type = output_type;
40
si
41
42
init(is_take: bool, is_skip: bool, counter_il_name: string, count_init: Value) is
43
self.is_take = is_take;
44
self.is_skip = is_skip;
45
self.counter_il_name = counter_il_name;
46
self.count_init = count_init;
47
si
48
si
49
50
// A terminal Pipe consumer (`count`, `any`, `all`, `for_each`, `reduce`,
51
// `find`, `first`, `collect_list`) fused with its chain: drives the
52
// chain's source iterator directly, applies the map/filter stages per
53
// element by calling each stage's hoisted delegate, runs the consumer's
54
// per-element body, and leaves the consumer's result on the stack -
55
// emitted in place of the consumer method call, so it flows through any
56
// expression context (like NULL_COALESCE_VALUE). A non-sealed source is
57
// guarded by `isa Pipe[element]`, falling back to the original call.
58
class FUSED_CONSUMER: Value is
59
_result_type: Type;
60
61
// Guard: when guard_isa_type is non-null the source could
62
// dynamically be a user Pipe, so emit `if source isa Pipe then
63
// fallback else fused`. `source` is the value the `isa` tests.
64
guard_isa_type: Type? public;
65
source: Value public;
66
fallback: Value public;
67
68
// Loop skeleton, pre-built against the '.fc_iter.N' iterator local.
69
// `iterator_init` produces the source's own iterator (or is the
70
// source itself when it is already an iterator); move_next /
71
// read_current drive it.
72
has_iterator: bool public;
73
iterator_init: Value public;
74
iterator_il_name: string public;
75
iterator_type: Type public;
76
move_next: Value public;
77
read_current: Value public;
78
79
// The element local the innermost stage reads.
80
element_il_name: string public;
81
element_type: Type public;
82
83
stages: Collections.List[FUSED_CONSUMER_STAGE] public;
84
85
// The surviving element after every stage - what the consumer body
86
// operates on (`collect` adds it, `find` returns it, ...).
87
final_element_il_name: string public;
88
final_element_type: Type public;
89
90
// Consumer specifics.
91
consumer_kind: string public;
92
93
// The result local, named in compile-expressions so pre-built
94
// values (a `reduce` accumulator) can reference it.
95
result_il_name: string public;
96
97
// A consumer with a function argument (`any`/`all`/`find`/`for_each`
98
// predicate/action, `reduce` accumulator) hoists it into a local so
99
// the delegate is built once. `consumer_apply` is that argument
100
// applied per element (to the final element, and for `reduce` the
101
// running result too), pre-built against the hoisted local.
102
consumer_arg_il_name: string? public;
103
consumer_arg_type: Type? public;
104
consumer_arg_init: Value? public;
105
consumer_apply: Value? public;
106
107
// `reduce` seeds the result with its first argument; `find`/`first`
108
// seed the result with an empty MAYBE; `collect` seeds it with a
109
// fresh list.
110
seed_init: Value? public;
111
112
// `find`/`first` wrap the surviving element in a MAYBE; `collect`
113
// adds it to the result list. Pre-built against the element / result
114
// locals.
115
wrap_element: Value? public;
116
add_element: Value? public;
117
118
type: Type => _result_type;
119
is_lightweight_pure: bool => false;
120
121
init(
122
result_type: Type,
123
source: Value,
124
fallback: Value,
125
iterator_init: Value,
126
has_iterator: bool,
127
iterator_il_name: string,
128
iterator_type: Type,
129
move_next: Value,
130
read_current: Value,
131
element_il_name: string,
132
element_type: Type,
133
stages: Collections.List[FUSED_CONSUMER_STAGE],
134
final_element_il_name: string,
135
final_element_type: Type,
136
consumer_kind: string,
137
result_il_name: string
138
) is
139
super.init();
140
141
self._result_type = result_type;
142
self.source = source;
143
self.fallback = fallback;
144
self.iterator_init = iterator_init;
145
self.has_iterator = has_iterator;
146
self.iterator_il_name = iterator_il_name;
147
self.iterator_type = iterator_type;
148
self.move_next = move_next;
149
self.read_current = read_current;
150
self.element_il_name = element_il_name;
151
self.element_type = element_type;
152
self.stages = stages;
153
self.final_element_il_name = final_element_il_name;
154
self.final_element_type = final_element_type;
155
self.consumer_kind = consumer_kind;
156
self.result_il_name = result_il_name;
157
si
158
159
gen(context: IR.CONTEXT) is
160
if guard_isa_type? then
161
let fused_label = IR.LABEL();
162
let end_label = IR.LABEL();
163
164
// if source isa Pipe[element] then fall back else fuse
165
gen(source, context);
166
context.write_line("isinst {guard_isa_type!.get_il_type()}");
167
context.write_line("brfalse {fused_label}");
168
169
gen(fallback, context);
170
context.write_line("br {end_label}");
171
172
context.write_line("{fused_label}:");
173
_gen_fused_loop(context);
174
175
context.write_line("{end_label}:");
176
else
177
_gen_fused_loop(context);
178
fi
179
si
180
181
is_void: bool => consumer_kind =~ "for_each";
182
183
// Emit the fused loop, leaving the consumer's result on the stack
184
// (nothing for a void `for_each`).
185
_gen_fused_loop(context: IR.CONTEXT) is
186
let loop_start = IR.LABEL();
187
let loop_end = IR.LABEL();
188
189
if !is_void then
190
context.write_line(".locals init ({_result_type.get_il_type()} {result_il_name})");
191
_gen_result_init(context);
192
fi
193
194
// A consumer's function argument is built once into a local.
195
if let arg_name = consumer_arg_il_name, arg_type = consumer_arg_type, arg_init = consumer_arg_init then
196
context.write_line(".locals init ({arg_type.get_il_type()} {arg_name})");
197
gen(arg_init, context);
198
context.write_line("stloc {arg_name}");
199
fi
200
201
// Each stage's lambda delegate is built once before the loop; a
202
// take/skip stage instead seeds its running counter with the
203
// count evaluated once.
204
for stage in stages do
205
if stage.is_countdown then
206
context.write_line(".locals init (int32 {stage.counter_il_name})");
207
gen(stage.count_init!, context);
208
context.write_line("stloc {stage.counter_il_name}");
209
else
210
context.write_line(".locals init ({stage.delegate_type.get_il_type()} {stage.delegate_il_name})");
211
gen(stage.delegate_init, context);
212
context.write_line("stloc {stage.delegate_il_name}");
213
fi
214
od
215
216
// The iterator always lives in a local - whether it came from
217
// the source's iterator member or the source is itself an
218
// iterator (a value-type range), move_next / current drive it
219
// through the local (by address for a value-type iterator).
220
context.write_line(".locals init ({iterator_type.get_il_type()} {iterator_il_name})");
221
gen(iterator_init, context);
222
context.write_line("stloc {iterator_il_name}");
223
224
context.write_line("{loop_start}:");
225
226
gen(move_next, context);
227
context.write_line("brfalse {loop_end}");
228
229
context.write_line(".locals init ({element_type.get_il_type()} {element_il_name})");
230
gen(read_current, context);
231
context.write_line("stloc {element_il_name}");
232
233
for stage in stages do
234
if stage.is_take then
235
// Decrement; once the counter goes negative the take
236
// limit is spent, so leave the loop.
237
context.write_line("ldloc {stage.counter_il_name}");
238
context.write_line("ldc.i4.1");
239
context.write_line("sub");
240
context.write_line("dup");
241
context.write_line("stloc {stage.counter_il_name}");
242
context.write_line("ldc.i4.0");
243
context.write_line("blt {loop_end}");
244
elif stage.is_skip then
245
// Decrement; while the counter stays non-negative this is
246
// a leading element to drop, so pull the next one.
247
context.write_line("ldloc {stage.counter_il_name}");
248
context.write_line("ldc.i4.1");
249
context.write_line("sub");
250
context.write_line("dup");
251
context.write_line("stloc {stage.counter_il_name}");
252
context.write_line("ldc.i4.0");
253
context.write_line("bge {loop_start}");
254
elif stage.is_filter then
255
gen(stage.apply, context);
256
context.write_line("brfalse {loop_start}");
257
else
258
context.write_line(".locals init ({stage.output_type!.get_il_type()} {stage.output_il_name})");
259
gen(stage.apply, context);
260
context.write_line("stloc {stage.output_il_name}");
261
fi
262
od
263
264
_gen_element_body(context, loop_start, loop_end);
265
266
context.write_line("br {loop_start}");
267
context.write_line("{loop_end}:");
268
269
if !is_void then
270
context.write_line("ldloc {result_il_name}");
271
fi
272
si
273
274
// Initialise the result local before the loop.
275
_gen_result_init(context: IR.CONTEXT) is
276
if consumer_kind =~ "count" \/ consumer_kind =~ "any" then
277
// count starts at 0; any starts false (0).
278
context.write_line("ldc.i4.0");
279
context.write_line("stloc {result_il_name}");
280
elif consumer_kind =~ "all" then
281
context.write_line("ldc.i4.1");
282
context.write_line("stloc {result_il_name}");
283
elif let seed = seed_init then
284
// reduce (seed), find / first (empty MAYBE), collect (list).
285
gen(seed, context);
286
context.write_line("stloc {result_il_name}");
287
fi
288
si
289
290
// Per-element consumer body (after the stages have produced the
291
// surviving element).
292
_gen_element_body(context: IR.CONTEXT, loop_start: IR.LABEL, loop_end: IR.LABEL) is
293
if consumer_kind =~ "count" then
294
context.write_line("ldloc {result_il_name}");
295
context.write_line("ldc.i4.1");
296
context.write_line("add");
297
context.write_line("stloc {result_il_name}");
298
elif consumer_kind =~ "any" then
299
let next = IR.LABEL();
300
gen(consumer_apply!, context);
301
context.write_line("brfalse {next}");
302
context.write_line("ldc.i4.1");
303
context.write_line("stloc {result_il_name}");
304
context.write_line("br {loop_end}");
305
context.write_line("{next}:");
306
elif consumer_kind =~ "all" then
307
let next = IR.LABEL();
308
gen(consumer_apply!, context);
309
context.write_line("brtrue {next}");
310
context.write_line("ldc.i4.0");
311
context.write_line("stloc {result_il_name}");
312
context.write_line("br {loop_end}");
313
context.write_line("{next}:");
314
elif consumer_kind =~ "for_each" then
315
gen(consumer_apply!, context);
316
elif consumer_kind =~ "reduce" then
317
gen(consumer_apply!, context);
318
context.write_line("stloc {result_il_name}");
319
elif consumer_kind =~ "find" then
320
let next = IR.LABEL();
321
gen(consumer_apply!, context);
322
context.write_line("brfalse {next}");
323
gen(wrap_element!, context);
324
context.write_line("stloc {result_il_name}");
325
context.write_line("br {loop_end}");
326
context.write_line("{next}:");
327
elif consumer_kind =~ "first" then
328
gen(wrap_element!, context);
329
context.write_line("stloc {result_il_name}");
330
context.write_line("br {loop_end}");
331
elif consumer_kind =~ "collect" then
332
gen(add_element!, context);
333
fi
334
si
335
336
to_string() -> string =>
337
"fused-consumer:[{consumer_kind}]({_result_type})";
338
si
339
si