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
//
13
// A stage is one shape or the other, so each constructor fills its
14
// own fields and leaves the other shape's alone; `is_countdown`
15
// says which are live.
16
@suppress("field-definite-assignment")
17
class FUSED_CONSUMER_STAGE is
18
is_filter: bool public
19
delegate_il_name: string public
20
delegate_type: Type public
21
delegate_init: Value public
22
apply: Value public
23
output_il_name: string public
24
output_type: Type? public
25
26
// A `take`/`skip` stage carries no delegate: the count is evaluated
27
// once into `counter_il_name` and each element decrements it (the
28
// loop stops pulling once a take's runs out, skip drops the element
29
// while its remains). `is_countdown` marks either.
30
is_take: bool public
31
is_skip: bool public
32
counter_il_name: string public
33
count_init: Value? public
34
35
// An index stage carries a running counter like a countdown does,
36
// but produces an output local as a map stage does: `apply` builds
37
// the INDEXED_VALUE from the counter and the incoming element.
38
is_index: bool public
39
40
is_countdown: bool => is_take \/ is_skip
41
42
init(is_filter: bool, delegate_il_name: string, delegate_type: Type, delegate_init: Value, apply: Value, output_il_name: string, output_type: Type?) is
43
self.is_filter = is_filter
44
self.delegate_il_name = delegate_il_name
45
self.delegate_type = delegate_type
46
self.delegate_init = delegate_init
47
self.apply = apply
48
self.output_il_name = output_il_name
49
self.output_type = output_type
50
si
51
52
init(is_take: bool, is_skip: bool, counter_il_name: string, count_init: Value) is
53
self.is_take = is_take
54
self.is_skip = is_skip
55
self.counter_il_name = counter_il_name
56
self.count_init = count_init
57
si
58
59
init(counter_il_name: string, start_init: Value, output_il_name: string, output_type: Type, apply: Value) is
60
self.is_index = true
61
self.counter_il_name = counter_il_name
62
self.count_init = start_init
63
self.output_il_name = output_il_name
64
self.output_type = output_type
65
self.apply = apply
66
si
67
si
68
69
// A terminal Pipe consumer (`count`, `any`, `all`, `each`, `reduce`,
70
// `find`, `first`, `collect_list`) fused with its chain: drives the
71
// chain's source iterator directly, applies the map/filter stages per
72
// element by calling each stage's hoisted delegate, runs the consumer's
73
// per-element body, and leaves the consumer's result on the stack -
74
// emitted in place of the consumer method call, so it flows through any
75
// expression context (like NULL_COALESCE_VALUE).
76
class FUSED_CONSUMER: Value is
77
_result_type: Type
78
79
80
// Loop skeleton, pre-built against the '.fc_iter.N' iterator local.
81
// `iterator_init` produces the source's own iterator (or is the
82
// source itself when it is already an iterator); move_next /
83
// read_current drive it.
84
has_iterator: bool public
85
iterator_init: Value public
86
iterator_il_name: string public
87
iterator_type: Type public
88
move_next: Value public
89
read_current: Value public
90
91
// The rewind a spent take owes the cursor. `pipe_cast` tests it
92
// for `Pipe[E]` into `pipe_il_name`, and `pipe_reset` resets that
93
// when it is one - exactly what a stage pipe rewinding its own
94
// cursor does. Unset when the cursor is a value type, which the
95
// loop holds a copy of and owes nothing.
96
pipe_cast: Value? public
97
pipe_reset: Value? public
98
pipe_il_name: string public
99
pipe_type: Type? public
100
101
// The element local the innermost stage reads.
102
element_il_name: string public
103
element_type: Type public
104
105
stages: Collections.List[FUSED_CONSUMER_STAGE] public
106
107
// The surviving element after every stage - what the consumer body
108
// operates on (`collect` adds it, `find` returns it, ...).
109
final_element_il_name: string public
110
final_element_type: Type public
111
112
// Consumer specifics.
113
consumer_kind: string public
114
115
// The result local, named in compile-expressions so pre-built
116
// values (a `reduce` accumulator) can reference it.
117
result_il_name: string public
118
119
// A consumer with a function argument (`any`/`all`/`find`/`each`
120
// predicate/action, `reduce` accumulator) hoists it into a local so
121
// the delegate is built once. `consumer_apply` is that argument
122
// applied per element (to the final element, and for `reduce` the
123
// running result too), pre-built against the hoisted local.
124
consumer_arg_il_name: string? public
125
consumer_arg_type: Type? public
126
consumer_arg_init: Value? public
127
consumer_apply: Value? public
128
129
// `reduce` seeds the result with its first argument; `find`/`first`
130
// seed the result with an empty MAYBE; `collect` seeds it with a
131
// fresh list.
132
seed_init: Value? public
133
134
// `find`/`first` wrap the surviving element in a MAYBE; `collect`
135
// adds it to the result list. Pre-built against the element / result
136
// locals.
137
wrap_element: Value? public
138
add_element: Value? public
139
140
type: Type => _result_type
141
is_lightweight_pure: bool => false
142
143
init(
144
result_type: Type,
145
iterator_init: Value,
146
has_iterator: bool,
147
iterator_il_name: string,
148
iterator_type: Type,
149
move_next: Value,
150
read_current: Value,
151
element_il_name: string,
152
element_type: Type,
153
stages: Collections.List[FUSED_CONSUMER_STAGE],
154
final_element_il_name: string,
155
final_element_type: Type,
156
consumer_kind: string,
157
result_il_name: string
158
) is
159
super.init()
160
161
// Left empty unless a rewind is wired up; nothing else reads it.
162
pipe_il_name = ""
163
164
self._result_type = result_type
165
self.iterator_init = iterator_init
166
self.has_iterator = has_iterator
167
self.iterator_il_name = iterator_il_name
168
self.iterator_type = iterator_type
169
self.move_next = move_next
170
self.read_current = read_current
171
self.element_il_name = element_il_name
172
self.element_type = element_type
173
self.stages = stages
174
self.final_element_il_name = final_element_il_name
175
self.final_element_type = final_element_type
176
self.consumer_kind = consumer_kind
177
self.result_il_name = result_il_name
178
si
179
180
gen(context: IR.CONTEXT) =>
181
_gen_fused_loop(context)
182
183
is_void: bool => Syntax.Process.PIPE_CONSUMER_KIND.is_void(consumer_kind)
184
185
// The loop below is one body of control flow with two ways of
186
// being written down, so the choice of back end is made per
187
// instruction here rather than by keeping a second copy of the
188
// loop. The logic is where the complexity is, and a duplicate of
189
// it would be the thing that drifts.
190
//
191
// The loop names its locals and labels rather than numbering
192
// them, because SRM_METHOD_BODY_EMITTER maps a name to a slot
193
// and to a label handle, forward references included.
194
_declare(context: IR.CONTEXT, name: string, type: Semantic.Types.Type) is
195
let body = context.current_srm_body_emitter!
196
body.declare_local(name, type)
197
si
198
199
_stloc(context: IR.CONTEXT, name: string) is
200
let body = context.current_srm_body_emitter!
201
body.stloc(name)
202
si
203
204
_ldloc(context: IR.CONTEXT, name: string) is
205
let body = context.current_srm_body_emitter!
206
body.ldloc(name)
207
si
208
209
_mark(context: IR.CONTEXT, label: IR.LABEL) is
210
let body = context.current_srm_body_emitter!
211
body.mark_label(label)
212
si
213
214
_branch(
215
context: IR.CONTEXT,
216
mnemonic: string,
217
op_code: System.Reflection.Metadata.ILOpCode,
218
label: IR.LABEL
219
) is
220
let body = context.current_srm_body_emitter!
221
body.branch(op_code, label)
222
si
223
224
_ldc_i4(context: IR.CONTEXT, value: int) is
225
let body = context.current_srm_body_emitter!
226
body.ldc_i4(value)
227
si
228
229
_op(
230
context: IR.CONTEXT,
231
mnemonic: string,
232
op_code: System.Reflection.Metadata.ILOpCode
233
) is
234
let body = context.current_srm_body_emitter!
235
body.op(op_code)
236
si
237
238
// Emit the fused loop, leaving the consumer's result on the stack
239
// (nothing for a void `each`).
240
_gen_fused_loop(context: IR.CONTEXT) is
241
let loop_start = IR.LABEL()
242
let loop_end = IR.LABEL()
243
let take_exit = if pipe_reset? then IR.LABEL() else loop_end fi
244
245
if !is_void then
246
_declare(context, result_il_name, _result_type)
247
_gen_result_init(context)
248
fi
249
250
// A consumer's function argument is built once into a local.
251
if let arg_name = consumer_arg_il_name, arg_type = consumer_arg_type, arg_init = consumer_arg_init then
252
_declare(context, arg_name, arg_type)
253
gen(arg_init, context)
254
_stloc(context, arg_name)
255
fi
256
257
// Each stage's lambda delegate is built once before the loop; a
258
// take/skip stage instead seeds its running counter with the
259
// count evaluated once.
260
for stage in stages do
261
if stage.is_countdown \/ stage.is_index then
262
_declare(
263
context,
264
stage.counter_il_name,
265
IoC.CONTAINER.instance.innate_symbol_lookup.get_int_type())
266
267
gen(stage.count_init!, context)
268
_stloc(context, stage.counter_il_name)
269
else
270
_declare(context, stage.delegate_il_name, stage.delegate_type)
271
gen(stage.delegate_init, context)
272
_stloc(context, stage.delegate_il_name)
273
fi
274
od
275
276
// The iterator always lives in a local - whether it came from
277
// the source's iterator member or the source is itself an
278
// iterator (a value-type range), move_next / current drive it
279
// through the local (by address for a value-type iterator).
280
_declare(context, iterator_il_name, iterator_type)
281
gen(iterator_init, context)
282
_stloc(context, iterator_il_name)
283
284
_mark(context, loop_start)
285
286
// Leave before pulling once any take stage has spent its count,
287
// so the loop pulls exactly the elements it yields and never
288
// advances the source past them.
289
for stage in stages do
290
if stage.is_take then
291
_ldloc(context, stage.counter_il_name)
292
_ldc_i4(context, 0)
293
_branch(context, "ble", System.Reflection.Metadata.ILOpCode.BLE, take_exit)
294
fi
295
od
296
297
gen(move_next, context)
298
_branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, loop_end)
299
300
_declare(context, element_il_name, element_type)
301
gen(read_current, context)
302
_stloc(context, element_il_name)
303
304
for stage in stages do
305
if stage.is_take then
306
// This element spends one of the take's count; the check
307
// at the top of the loop decides when to stop.
308
_ldloc(context, stage.counter_il_name)
309
_ldc_i4(context, 1)
310
_op(context, "sub", System.Reflection.Metadata.ILOpCode.SUB)
311
_stloc(context, stage.counter_il_name)
312
elif stage.is_skip then
313
// Decrement; while the counter stays non-negative this is
314
// a leading element to drop, so pull the next one.
315
_gen_countdown(context, stage)
316
_branch(context, "bge", System.Reflection.Metadata.ILOpCode.BGE, loop_start)
317
elif stage.is_filter then
318
gen(stage.apply, context)
319
_branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, loop_start)
320
else
321
_declare(context, stage.output_il_name, stage.output_type!)
322
gen(stage.apply, context)
323
_stloc(context, stage.output_il_name)
324
fi
325
od
326
327
_gen_element_body(context, loop_start, loop_end)
328
329
_branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_start)
330
331
// The rewind falls straight into the loop exit. Only a spent
332
// take reaches it: an exhausted source has already rewound
333
// itself, and a consumer that stops at its first hit abandons
334
// the chain exactly as the unfused lowering does. Whether the
335
// cursor is rewound at all is asked of it at run time, as a
336
// stage pipe rewinding its own cursor asks.
337
if let reset = pipe_reset, `cast = pipe_cast, cursor_type = pipe_type then
338
_mark(context, take_exit)
339
340
_declare(context, pipe_il_name, cursor_type)
341
gen(`cast, context)
342
_stloc(context, pipe_il_name)
343
344
_ldloc(context, pipe_il_name)
345
_branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, loop_end)
346
347
gen(reset, context)
348
fi
349
350
_mark(context, loop_end)
351
352
if !is_void then
353
_ldloc(context, result_il_name)
354
fi
355
si
356
357
// A skip stage's countdown: decrement the running counter, leaving
358
// both the new value in the local and a copy on the stack for the
359
// caller to compare against zero.
360
_gen_countdown(context: IR.CONTEXT, stage: FUSED_CONSUMER_STAGE) is
361
_ldloc(context, stage.counter_il_name)
362
_ldc_i4(context, 1)
363
_op(context, "sub", System.Reflection.Metadata.ILOpCode.SUB)
364
_op(context, "dup", System.Reflection.Metadata.ILOpCode.DUP)
365
_stloc(context, stage.counter_il_name)
366
_ldc_i4(context, 0)
367
si
368
369
// Initialise the result local before the loop.
370
_gen_result_init(context: IR.CONTEXT) is
371
if consumer_kind =~ "count" \/ consumer_kind =~ "any" then
372
// count starts at 0; any starts false (0).
373
_ldc_i4(context, 0)
374
_stloc(context, result_il_name)
375
elif consumer_kind =~ "all" then
376
_ldc_i4(context, 1)
377
_stloc(context, result_il_name)
378
elif let seed = seed_init then
379
// reduce (seed), find / first (empty MAYBE), collect (list).
380
gen(seed, context)
381
_stloc(context, result_il_name)
382
fi
383
si
384
385
// Per-element consumer body (after the stages have produced the
386
// surviving element).
387
_gen_element_body(context: IR.CONTEXT, loop_start: IR.LABEL, loop_end: IR.LABEL) is
388
if consumer_kind =~ "count" then
389
_ldloc(context, result_il_name)
390
_ldc_i4(context, 1)
391
_op(context, "add", System.Reflection.Metadata.ILOpCode.ADD)
392
_stloc(context, result_il_name)
393
elif consumer_kind =~ "any" then
394
let next = IR.LABEL()
395
gen(consumer_apply!, context)
396
_branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, next)
397
_ldc_i4(context, 1)
398
_stloc(context, result_il_name)
399
_branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end)
400
_mark(context, next)
401
elif consumer_kind =~ "all" then
402
let next = IR.LABEL()
403
gen(consumer_apply!, context)
404
_branch(context, "brtrue", System.Reflection.Metadata.ILOpCode.BRTRUE, next)
405
_ldc_i4(context, 0)
406
_stloc(context, result_il_name)
407
_branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end)
408
_mark(context, next)
409
elif consumer_kind =~ "each" then
410
gen(consumer_apply!, context)
411
elif consumer_kind =~ "reduce" then
412
gen(consumer_apply!, context)
413
_stloc(context, result_il_name)
414
elif consumer_kind =~ "find" then
415
let next = IR.LABEL()
416
gen(consumer_apply!, context)
417
_branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, next)
418
gen(wrap_element!, context)
419
_stloc(context, result_il_name)
420
_branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end)
421
_mark(context, next)
422
elif consumer_kind =~ "first" then
423
gen(wrap_element!, context)
424
_stloc(context, result_il_name)
425
_branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end)
426
elif consumer_kind =~ "collect" then
427
gen(add_element!, context)
428
fi
429
si
430
431
to_string() -> string =>
432
"fused-consumer:[{consumer_kind}]({_result_type})"
433
si
434
si