Skip to content
← Back

src/syntax/process/compile-expressions/compile_expressions_pipe_fusion.ghul

1
namespace Syntax.Process is
2
use System.Exception
3
4
use IO.Std
5
6
use Logging
7
use Source
8
9
use IR.Values
10
use IR.VALUE_CONVERTER
11
use IR.VALUE_BOXER
12
13
use Semantic.LEAST_UPPER_BOUND_MAP
14
use Semantic.Types.Type
15
16
use Syntax.Trees.Definitions.PRAGMA
17
18
use Ghul.Pipes
19
20
21
// Iterator setup for for loops and the pipe-fusion recognizer and stage builder.
22
partial COMPILE_EXPRESSIONS is
23
// Iterable inference: a still-placeholder iterated expression
24
// records an ITERABLE_CONSTRAINT on its origin so the body-retry
25
// loop can filter candidate types to those that actually
26
// iterate. set_iterator_for will still emit "not iterable" on
27
// this iteration - the speculate/roll-back wrapper drops the
28
// error on retry once the placeholder resolves.
29
record_iterable_constraint(expression: Trees.Expressions.Expression?) is
30
if let
31
ev = expression?.value,
32
ev_type = ev.type /\
33
isa Semantic.Types.INFERRED_VARIABLE_TYPE(ev_type)
34
then
35
let placeholder = cast Semantic.Types.INFERRED_VARIABLE_TYPE(ev_type)
36
37
_logger.mark_consumed_any_if(Semantic.INFERENCE_TRACE.add_constraint("pipe_fusion.iterable", placeholder.origin, Semantic.ITERABLE_CONSTRAINT()))
38
fi
39
si
40
41
set_iterator_for(carrier: Trees.IteratorCarrier, type: Type, recursing: bool) -> bool is
42
let expression = carrier.iterated!
43
44
expression.value!.check_is_consumable(_logger, expression.location)
45
46
if type.is_error then
47
return false
48
fi
49
50
let move_next = get_zero_argument_function(type, "move_next")
51
52
if move_next? then
53
let read_current mut = get_zero_argument_function(type, "$get_current")
54
55
if !read_current? then
56
read_current = get_zero_argument_function(type, "$get_Current")
57
fi
58
59
if !read_current? then
60
_logger.error(expression.location, "incomplete iterator type (has move_next method but no current property)")
61
return false
62
fi
63
64
carrier.move_next = move_next
65
carrier.read_current = read_current
66
67
_declare_state_machine_iterator_field(carrier, type)
68
69
return true
70
elif !recursing then
71
let read_iterator = get_zero_argument_function(type, "$get_iterator")
72
73
if read_iterator? then
74
carrier.read_iterator = read_iterator
75
76
return set_iterator_for(carrier, read_iterator.return_type!, true)
77
fi
78
fi
79
80
_logger.error(expression.location, "not iterable")
81
82
return false
83
si
84
85
// Recognise a fusible Pipe[T] chain on the loop expression and,
86
// if found, resolve the source's own iterator members (exactly
87
// as a plain `for x in source` would) so the IL pass can drive
88
// them directly, skipping the pipe objects entirely. Returns
89
// null - fall back to the normal iterator loop - when the chain
90
// isn't fusible or the source doesn't expose a complete iterator
91
// surface.
92
_recognize_pipe_fusion(`for: Trees.Statements.FOR) -> PIPE_FUSION? is
93
// `@suppress("pipe-fusion")` disables fusion for a scope, so the
94
// loop runs the ordinary pipe-object lowering - used where the
95
// real Pipe implementations must be exercised (the Ghul.Pipes
96
// unit tests) rather than the fused equivalent.
97
if _logger.is_suppressed("pipe-fusion", `for.location) then
98
return null
99
fi
100
101
let fusion = PIPE_FUSION_RECOGNIZER(_innate_symbol_lookup.get_pipes_fusion_functions()).recognize(`for)
102
103
if !_resolve_chain_fusion(fusion) then
104
return null
105
fi
106
107
return fusion
108
si
109
110
// Finish a recognised chain plan: resolve the source's own iterator
111
// members, and resolve each stage (index constructor, or inline a
112
// literal lambda). Returns false when any of that fails, in which case the
113
// chain must not be fused. Shared by the `for` and consumer paths.
114
_resolve_chain_fusion(fusion: PIPE_FUSION?) -> bool =>
115
_resolve_chain_fusion(fusion, true)
116
117
// `inline_stages` false skips the inline re-walk of map/filter lambda
118
// bodies - consumer fusion calls the stage delegates rather than
119
// inlining them, and the re-walk can disturb a stage lambda whose
120
// body nests a capturing closure.
121
_resolve_chain_fusion(fusion: PIPE_FUSION?, inline_stages: bool) -> bool is
122
if !fusion? then
123
return false
124
fi
125
126
let source_value = fusion.source.value
127
128
if !source_value? \/ !source_value.type? then
129
return false
130
fi
131
132
if !_resolve_fusion_source_iterator(fusion, source_value.type, false) then
133
return false
134
fi
135
136
for stage in fusion.stages_outermost_first do
137
if stage.is_countdown then
138
// take/skip carry an int count evaluated once into the
139
// running counter; nothing to inline or resolve, but the
140
// count must have compiled to a value.
141
let count_value = stage.argument!.value
142
143
if !count_value? \/ !count_value.type? then
144
return false
145
fi
146
elif stage.is_index then
147
// The INDEXED_VALUE the stage builds is the element
148
// type of the `index` call's own `Pipe[INDEXED_VALUE[E]]`
149
// return, so it is read off the call rather than
150
// rebuilt from the element type flowing into the stage.
151
if !_resolve_index_stage(stage) then
152
return false
153
fi
154
else
155
// A map/filter stage's function argument must have
156
// compiled to a function-typed value. One that failed
157
// to resolve - a bare overloaded group with no
158
// member the formal could pick - carries a group
159
// type with no call shape, and building consumer
160
// stages from it would index past an empty argument
161
// list. Decline fusion; the ordinary call path
162
// reports the argument's own error.
163
let stage_argument = stage.argument!.value
164
165
166
if
167
!stage_argument? \/
168
!stage_argument.type? \/
169
!stage_argument.type.is_function \/
170
stage_argument.type.arguments.count == 0
171
then
172
return false
173
fi
174
175
if inline_stages then
176
_try_inline_stage(stage)
177
fi
178
fi
179
od
180
181
return true
182
si
183
184
// Determine whether `call` is a terminal Pipe consumer: a
185
// `Ghul.Pipes` free-function call, reached via |> thread-first
186
// desugaring (`chain |> count()`) or written directly
187
// (`count(chain)`), the two being indistinguishable by the time
188
// this pass runs. It is identified by comparing the call's
189
// resolved target against the fusible-combinator symbols, so
190
// what fuses follows the function the call bound rather than
191
// how it spells itself. Only a global call can be one: `Pipe[T]`
192
// carries no combinator methods of its own, so a member call
193
// never resolves to a combinator. Returns the consumer's role
194
// and the chain expression feeding it, which is the call's
195
// first argument.
196
_consumer_call_shape(call: Trees.Expressions.CALL) -> (name: string, chain_before: Trees.Expressions.Expression)? is
197
if !isa IR.Values.Call.GLOBAL(call.value) then
198
return null
199
fi
200
201
let function = (cast IR.Values.Call.GLOBAL(call.value)).function
202
let role: string mut
203
204
if !_innate_symbol_lookup.get_pipes_fusion_functions().try_get_value(function.root_specialized_from, role ref) then
205
return null
206
fi
207
208
if call.arguments.expressions.count == 0 then
209
return null
210
fi
211
212
return (role, call.arguments.expressions[0])
213
si
214
215
// Recognise a terminal Pipe consumer (`chain |> count()`, or the
216
// equivalent `count(chain)`) whose chain is fusible, and build the
217
// FUSED_CONSUMER value the call lowers to.
218
// Every stage fuses (a map/filter delegate is called rather than
219
// inlined). A `memo` stage is elided - the consumer is the memo's
220
// only reader, so the cache would never be consulted.
221
_recognize_consumer_fusion(call: Trees.Expressions.CALL) -> IR.Values.FUSED_CONSUMER? is
222
if _logger.is_suppressed("pipe-fusion", call.location) then
223
return null
224
fi
225
226
let shape = _consumer_call_shape(call)
227
228
if !shape? then
229
return null
230
fi
231
232
let name = shape.name
233
let chain_before = shape.chain_before
234
let arg_count = call.arguments.expressions.count - 1
235
236
let recognized_kind = PIPE_CONSUMER_KIND.of(name, arg_count)
237
238
if !recognized_kind? then
239
return null
240
fi
241
242
let consumer_kind = recognized_kind
243
244
if !call.value? \/ !call.value.type? then
245
return null
246
fi
247
248
let fusion = PIPE_FUSION_RECOGNIZER(_innate_symbol_lookup.get_pipes_fusion_functions()).recognize_any(chain_before)
249
250
if !_resolve_chain_fusion(fusion, false) then
251
return null
252
fi
253
254
let plan = fusion!
255
256
let id = _next_fused_local_id()
257
258
let iterator_il_name = ".fc_iter.{id}"
259
let element_il_name = ".fc_element.{id}"
260
261
let source_read_iterator = plan.source_read_iterator
262
263
let iterator_type =
264
if source_read_iterator? then
265
source_read_iterator.return_type!
266
else
267
plan.source.value!.type!
268
fi
269
270
let iterator_init =
271
if source_read_iterator? then
272
source_read_iterator.call(plan.source.location, plan.source.value!, Collections.LIST[IR.Values.Value](0), null, _function_caller)
273
else
274
plan.source.value!
275
fi
276
277
let iterator_load = cast IR.Values.Value(IR.Values.Load.TEMP(iterator_il_name, iterator_type))
278
279
let move_next = plan.source_move_next!.call(plan.source.location, iterator_load, Collections.LIST[IR.Values.Value](0), null, _function_caller)
280
let read_current = plan.source_read_current!.call(plan.source.location, iterator_load, Collections.LIST[IR.Values.Value](0), null, _function_caller)
281
282
let element_type = plan.source_read_current!.return_type!
283
284
let built = _build_consumer_stages(plan, element_il_name, element_type)
285
286
let result_il_name = ".fc_result.{id}"
287
288
let fused = IR.Values.FUSED_CONSUMER(
289
call.value!.type!,
290
iterator_init,
291
source_read_iterator?,
292
iterator_il_name,
293
iterator_type,
294
move_next,
295
read_current,
296
element_il_name,
297
element_type,
298
built.ops,
299
built.final_il_name,
300
built.final_type,
301
consumer_kind,
302
result_il_name
303
)
304
305
if let plan.pipe_reset, pipe_type = plan.pipe_type then
306
let pipe_il_name = ".fc_pipe.{id}"
307
308
fused.pipe_il_name = pipe_il_name
309
fused.pipe_type = pipe_type
310
fused.pipe_cast = IR.Values.CAST(pipe_type, iterator_load, false)
311
fused.pipe_reset =
312
pipe_reset.call(
313
plan.source.location,
314
IR.Values.Load.TEMP(pipe_il_name, pipe_type),
315
Collections.LIST[IR.Values.Value](0),
316
null,
317
_function_caller)
318
fi
319
320
let final_element_load = cast IR.Values.Value(IR.Values.Load.TEMP(built.final_il_name, built.final_type))
321
322
if PIPE_CONSUMER_KIND.takes_function(consumer_kind) then
323
// Hoist the predicate / action delegate and apply it to the
324
// surviving element per iteration. A function argument that
325
// failed to resolve has no call shape to apply - decline
326
// fusion and let the ordinary call path report it.
327
let function = call.arguments.expressions[1].value
328
329
if !function? \/ !function.type? \/ !function.type.is_function then
330
return null
331
fi
332
333
let func_type = function.type
334
335
let arg_il_name = ".fc_arg.{id}"
336
337
let result_type =
338
if func_type.is_action then
339
_innate_symbol_lookup.get_void_type()
340
else
341
func_type.arguments[func_type.arguments.count - 1]
342
fi
343
344
let call_arguments = Collections.LIST[IR.Values.Value]()
345
call_arguments.add(final_element_load)
346
347
fused.consumer_arg_il_name = arg_il_name
348
fused.consumer_arg_type = func_type
349
fused.consumer_arg_init = function
350
fused.consumer_apply = IR.Values.Call.CLOSURE(
351
IR.Values.Load.TEMP(arg_il_name, func_type), result_type, func_type.is_action, func_type, call_arguments)
352
elif consumer_kind =~ "reduce" then
353
// running = accumulator(running, element).
354
let seed = call.arguments.expressions[1].value!
355
let accumulator = call.arguments.expressions[2].value!
356
let func_type = accumulator.type!
357
358
let arg_il_name = ".fc_arg.{id}"
359
let running_type = call.value!.type!
360
361
let call_arguments = Collections.LIST[IR.Values.Value]()
362
call_arguments.add(cast IR.Values.Value(IR.Values.Load.TEMP(result_il_name, running_type)))
363
call_arguments.add(final_element_load)
364
365
fused.seed_init = seed
366
fused.consumer_arg_il_name = arg_il_name
367
fused.consumer_arg_type = func_type
368
fused.consumer_arg_init = accumulator
369
fused.consumer_apply = IR.Values.Call.CLOSURE(
370
IR.Values.Load.TEMP(arg_il_name, func_type), running_type, false, func_type, call_arguments)
371
fi
372
373
// find / first return a MAYBE: seed with the empty MAYBE, and
374
// wrap the surviving element with MAYBE(element).
375
if consumer_kind =~ "find" \/ consumer_kind =~ "first" then
376
let maybe_type = call.value!.type!
377
378
let empty_ctor = _resolve_ctor_by_arity(maybe_type, 0)
379
let wrap_ctor = _resolve_ctor_by_arity(maybe_type, 1)
380
381
if !empty_ctor? \/ !wrap_ctor? then
382
return null
383
fi
384
385
fused.seed_init = IR.Values.NEW(maybe_type, empty_ctor, Collections.LIST[IR.Values.Value](0))
386
387
let wrap_args = Collections.LIST[IR.Values.Value]()
388
wrap_args.add(final_element_load)
389
390
fused.wrap_element = IR.Values.NEW(maybe_type, wrap_ctor, wrap_args)
391
fi
392
393
// collect_list returns a fresh LIST[element]; seed it empty and
394
// add the surviving element each iteration.
395
if consumer_kind =~ "collect" then
396
let list_type = call.value!.type!
397
398
let ctor = _resolve_ctor_by_arity(list_type, 0)
399
let add_fn = _resolve_member_by_arity(list_type, "add", 1)
400
401
if !ctor? \/ !add_fn? then
402
return null
403
fi
404
405
fused.seed_init = IR.Values.NEW(list_type, ctor, Collections.LIST[IR.Values.Value](0))
406
407
let add_args = Collections.LIST[IR.Values.Value]()
408
add_args.add(final_element_load)
409
410
fused.add_element = add_fn.call(
411
call.location, IR.Values.Load.TEMP(result_il_name, list_type), add_args, null, _function_caller)
412
fi
413
414
return fused
415
si
416
417
// A member of `type` named `name` taking `arity` arguments (or null).
418
_resolve_member_by_arity(type: Type, name: string, arity: int) -> Semantic.Symbols.Function? is
419
let member = type.find_member(name)
420
421
if !member? \/ !isa Semantic.Symbols.FUNCTION_GROUP(member) then
422
return null
423
fi
424
425
let group = cast Semantic.Symbols.FUNCTION_GROUP(member)
426
427
for f in group.functions do
428
if f.argument_names.count == arity then
429
return f
430
fi
431
od
432
433
return null
434
si
435
436
// The constructor of `type` taking `arity` arguments (or null).
437
_resolve_ctor_by_arity(type: Type, arity: int) -> Semantic.Symbols.Function? =>
438
_resolve_member_by_arity(type, "init", arity)
439
440
// Pre-build the per-stage application for a consumer loop, threading
441
// the element through freshly-named locals (a map produces a new
442
// typed local the next stage reads). Every stage is an inlinable
443
// lambda: assign the current element to the lambda's parameter local,
444
// then the harvested inline body is the applied value.
445
_build_consumer_stages(fusion: PIPE_FUSION, start_il_name: string, start_type: Type) -> (ops: Collections.LIST[IR.Values.FUSED_CONSUMER_STAGE], final_il_name: string, final_type: Type) is
446
let ops = Collections.LIST[IR.Values.FUSED_CONSUMER_STAGE]()
447
448
let current_il_name mut = start_il_name
449
let current_type mut = start_type
450
451
let index mut = fusion.stages_outermost_first.count - 1
452
453
while index >= 0 do
454
let stage = fusion.stages_outermost_first[index]
455
456
if stage.is_countdown then
457
// take/skip: a running counter, no delegate and no
458
// output local - the element passes through unchanged.
459
let counter_il_name = ".fc_countdown.{_next_fused_local_id()}"
460
461
ops.add(IR.Values.FUSED_CONSUMER_STAGE(stage.is_take, stage.is_skip, counter_il_name, stage.argument!.value!))
462
463
index = index - 1
464
465
continue
466
fi
467
468
if stage.is_index then
469
// A running counter and an INDEXED_VALUE built per
470
// element from it, in place of an INDEX_PIPE. The
471
// counter is seeded with the caller's start where one
472
// was given, and with zero otherwise.
473
let int_type = _innate_symbol_lookup.get_int_type()
474
let counter_il_name = ".fc_index.{_next_fused_local_id()}"
475
let output_il_name = ".fc_indexed.{_next_fused_local_id()}"
476
let indexed_type = stage.indexed_value_type!
477
478
let start =
479
if let start_expression = stage.argument then
480
start_expression.value!
481
else
482
cast IR.Values.Value(IR.Values.Literal.NUMBER(0, int_type))
483
fi
484
485
let build_arguments = Collections.LIST[IR.Values.Value]()
486
build_arguments.add(
487
cast IR.Values.Value(IR.Values.POST_INCREMENT(counter_il_name, int_type)))
488
build_arguments.add(
489
cast IR.Values.Value(IR.Values.Load.TEMP(current_il_name, current_type)))
490
491
ops.add(
492
IR.Values.FUSED_CONSUMER_STAGE(
493
counter_il_name,
494
start,
495
output_il_name,
496
indexed_type,
497
IR.Values.NEW(indexed_type, stage.indexed_value_constructor!, build_arguments)))
498
499
current_il_name = output_il_name
500
current_type = indexed_type
501
502
index = index - 1
503
504
continue
505
fi
506
507
let delegate = stage.argument!.value!
508
let func_type = delegate.type!
509
510
let delegate_il_name = ".fc_stage.{_next_fused_local_id()}"
511
512
let result_type =
513
if func_type.is_action then
514
_innate_symbol_lookup.get_void_type()
515
else
516
func_type.arguments[func_type.arguments.count - 1]
517
fi
518
519
let call_arguments = Collections.LIST[IR.Values.Value]()
520
call_arguments.add(cast IR.Values.Value(IR.Values.Load.TEMP(current_il_name, current_type)))
521
522
let apply = cast IR.Values.Value(IR.Values.Call.CLOSURE(
523
IR.Values.Load.TEMP(delegate_il_name, func_type), result_type, func_type.is_action, func_type, call_arguments))
524
525
if stage.is_filter then
526
ops.add(IR.Values.FUSED_CONSUMER_STAGE(true, delegate_il_name, func_type, delegate, apply, "", null))
527
else
528
ops.add(IR.Values.FUSED_CONSUMER_STAGE(false, delegate_il_name, func_type, delegate, apply, ".fc_map.{_next_fused_local_id()}", result_type))
529
530
current_il_name = ops[ops.count - 1].output_il_name
531
current_type = result_type
532
fi
533
534
index = index - 1
535
od
536
537
return (ops, current_il_name, current_type)
538
si
539
540
// Fill an index stage's INDEXED_VALUE type and constructor, and
541
// check the start it was given compiled. False declines the whole
542
// chain, which then runs through its own INDEX_PIPE unchanged.
543
_resolve_index_stage(stage: PIPE_FUSION_STAGE) -> bool is
544
if let start = stage.argument then
545
let start_value = start.value
546
547
if !start_value? \/ !start_value.type? then
548
return false
549
fi
550
fi
551
552
let pipe = _innate_symbol_lookup.get_unspecialized_pipe_type()
553
554
if !pipe? \/ !stage.chain_type? then
555
return false
556
fi
557
558
stage.indexed_value_type =
559
Semantic.Symbols.TYPE_ARGUMENT_EXTRACTOR.extract(stage.chain_type, pipe)
560
561
if !stage.indexed_value_type? then
562
return false
563
fi
564
565
stage.indexed_value_constructor = _resolve_index_constructor(stage.indexed_value_type)
566
567
return stage.is_index_ready
568
si
569
570
// The single INDEXED_VALUE[element] constructor `init(int, element)`,
571
// specialized to the stage's element type, that the fused index stage
572
// calls per element in place of building an INDEX_PIPE.
573
_resolve_index_constructor(indexed_value_type: Type) -> Semantic.Symbols.Function? is
574
let init_symbol = indexed_value_type.find_member("init")
575
576
if !init_symbol? \/ !isa Semantic.Symbols.FUNCTION_GROUP(init_symbol) then
577
return null
578
fi
579
580
let group = cast Semantic.Symbols.FUNCTION_GROUP(init_symbol)
581
582
if group.count != 1 then
583
return null
584
fi
585
586
return group.functions[0]
587
si
588
589
_fused_local_id_counter: int static
590
591
_next_fused_local_id() -> int static is
592
let result = _fused_local_id_counter
593
_fused_local_id_counter = _fused_local_id_counter + 1
594
return result
595
si
596
597
// Attempt to inline a stage's `map`/`filter` lambda so the fused
598
// loop runs its body directly, with no delegate or closure
599
// frame. Only literal, single-parameter, expression-bodied
600
// lambdas qualify; anything else keeps the delegate path
601
// (`stage` left unmodified).
602
//
603
// Mechanism: declare a synthetic local, owned by the enclosing
604
// method, for the parameter, then re-walk the body with that
605
// local bound. Because the current function is the enclosing
606
// method (not the closure), the parameter and any captured outer
607
// locals resolve as ordinary local loads. The re-walk's
608
// diagnostics and symbol-use records are discarded (the first,
609
// normal walk already recorded them); the harvested IR is kept.
610
// A second, restoring walk of the whole lambda puts the
611
// closure-context IR back on the shared AST nodes so the (now
612
// unused) closure method body still emits valid IL.
613
_try_inline_stage(stage: PIPE_FUSION_STAGE) is
614
if !isa Trees.Expressions.FUNCTION(stage.argument) then
615
return
616
fi
617
618
let function = cast Trees.Expressions.FUNCTION(stage.argument)
619
620
if
621
function.is_recursive \/
622
function.contains_let_await \/
623
function.arguments.expressions.count != 1 \/
624
!isa Trees.Bodies.EXPRESSION(function.body)
625
then
626
return
627
fi
628
629
let param = function.arguments.expressions[0]
630
631
if !isa Trees.Expressions.VARIABLE(param) then
632
return
633
fi
634
635
let variable = cast Trees.Expressions.VARIABLE(param)
636
637
// The harvest below declares one enclosing-method local, for
638
// the formal's own name. A destructure pattern's leaves are
639
// separate symbols owned by the closure, and nothing here
640
// rebinds them, so the body would read them against the
641
// enclosing method. Inlining one needs a local per leaf and
642
// an unpack in the fused loop; until then the stage keeps
643
// the delegate.
644
if variable.is_destructuring then
645
return
646
fi
647
648
let param_name = variable.name.name
649
650
let closure = cast Semantic.Symbols.Closure?(_symbol_table.scope_for(function))
651
652
if !closure? then
653
return
654
fi
655
656
let param_symbol = closure.find_direct(param_name)
657
658
if !param_symbol? \/ !isa Semantic.Types.Typed(param_symbol) then
659
return
660
fi
661
662
let param_type = (cast Semantic.Types.Typed(param_symbol)).type
663
664
if !param_type? \/ param_type.is_sentinel \/ param_type.is_error then
665
return
666
fi
667
668
let body = cast Trees.Bodies.EXPRESSION?(function.body)
669
670
if !body? then
671
return
672
fi
673
674
if INLINE_DISQUALIFYING_SCANNER().contains_cast(body) then
675
return
676
fi
677
678
// Harvest: re-walk the body with the parameter bound to a
679
// fresh enclosing-method local.
680
let scope = Semantic.BLOCK_SCOPE(_symbol_table.current_scope)
681
682
_symbol_table.enter_scope(scope)
683
684
// The local-id generator is only in a function frame during
685
// the declare pass; push one so the LOCAL_VARIABLE ctor can
686
// mint an id, then give the local a globally-unique IL slot
687
// name so it can't collide with an enclosing local.
688
let local_id_generator = IoC.CONTAINER.instance.local_id_generator
689
690
local_id_generator.enter_function()
691
692
let local = scope.declare_variable(function.location, param_name, false, null)
693
694
local_id_generator.leave_function()
695
696
if !isa Semantic.Symbols.Variable(local) \/ !isa Semantic.Types.SettableTyped(local) then
697
_symbol_table.leave_scope(scope)
698
return
699
fi
700
701
local.il_name_override = ".fused_param.{_next_fused_local_id()}"
702
703
(cast Semantic.Symbols.Variable(local)).define()
704
(cast Semantic.Types.SettableTyped(local)).set_type(param_type)
705
706
let harvest_site = RETRY_SITE_STATS.enter("fusion.harvest", RetrySiteKind.ALTERNATIVE)
707
708
_logger.speculate()
709
_symbol_use_locations.speculate()
710
711
body.expression.walk(self)
712
713
let harvested = body.expression.value
714
715
_logger.roll_back()
716
_symbol_use_locations.roll_back()
717
718
harvest_site.dispose()
719
720
_symbol_table.leave_scope(scope)
721
722
// Restore the closure-context IR on the shared AST nodes.
723
let restore_site = RETRY_SITE_STATS.enter("fusion.restore", RetrySiteKind.ALTERNATIVE)
724
725
_logger.speculate()
726
_symbol_use_locations.speculate()
727
728
function.walk(self)
729
730
_logger.roll_back()
731
_symbol_use_locations.roll_back()
732
733
restore_site.dispose()
734
735
if !harvested? then
736
return
737
fi
738
739
let harvested_type = harvested.type
740
741
if !harvested_type? \/ harvested_type.is_error then
742
return
743
fi
744
745
stage.param_local = cast Semantic.Symbols.Variable(local)
746
stage.inline_body = harvested
747
si
748
749
// Fill the fusion plan's iterator slots for `type`, mirroring
750
// set_iterator_for: bind move_next / read_current directly when
751
// `type` is itself an iterator, else follow $get_iterator once
752
// and recurse. Returns false if no complete iterator surface is
753
// found (fall back to the normal loop).
754
_resolve_fusion_source_iterator(fusion: PIPE_FUSION, type: Type, recursing: bool) -> bool is
755
let move_next = get_zero_argument_function(type, "move_next")
756
757
if move_next? then
758
let read_current mut = get_zero_argument_function(type, "$get_current")
759
760
if !read_current? then
761
read_current = get_zero_argument_function(type, "$get_Current")
762
fi
763
764
if !read_current? then
765
return false
766
fi
767
768
fusion.source_move_next = move_next
769
fusion.source_read_current = read_current
770
771
_resolve_fusion_pipe_rewind(fusion, type, read_current)
772
773
return true
774
elif !recursing then
775
let read_iterator = get_zero_argument_function(type, "$get_iterator")
776
777
if read_iterator? then
778
fusion.source_read_iterator = read_iterator
779
780
return _resolve_fusion_source_iterator(fusion, read_iterator.return_type!, true)
781
fi
782
fi
783
784
return false
785
si
786
787
// Resolve the rewind a spent take owes the cursor `cursor_type`,
788
// as `Pipe[E].reset` over the element type the cursor yields. The
789
// loop tests the cursor for `Pipe[E]` before calling it, so a
790
// cursor that is one is reset wherever it came from and a cursor
791
// that is not is left alone - which is what rewinding a stage's
792
// own cursor does. A value-type cursor is the loop's own copy and
793
// is nobody else's to restore, so it gets none.
794
_resolve_fusion_pipe_rewind(
795
fusion: PIPE_FUSION,
796
cursor_type: Type,
797
read_current: Semantic.Symbols.Function
798
) is
799
if cursor_type.is_value_type then
800
return
801
fi
802
803
let unspecialized = _innate_symbol_lookup.get_unspecialized_pipe_type()
804
805
if !unspecialized? then
806
return
807
fi
808
809
let element_type = read_current.return_type
810
811
if !element_type? then
812
return
813
fi
814
815
let classy = cast Semantic.Symbols.Classy?(unspecialized.symbol.unspecialized_symbol)
816
817
if !classy? then
818
return
819
fi
820
821
let arguments = Collections.LIST[Type]()
822
arguments.add(element_type)
823
824
let pipe_type = cast Type(
825
Semantic.Types.GENERIC(Source.LOCATION.internal, classy, arguments))
826
827
let reset = get_zero_argument_function(pipe_type, "reset")
828
829
if !reset? then
830
return
831
fi
832
833
fusion.pipe_type = pipe_type
834
fusion.pipe_reset = reset
835
si
836
si
837
si