Skip to content
← Back

src/syntax/process/generate-il/generate_il_loops_and_exceptions.ghul

1
namespace Syntax.Process is
2
use System.Reflection.Metadata.ILOpCode
3
use IO.Std
4
5
use System.Text.StringBuilder
6
7
use Logging
8
use Trees
9
use Source
10
11
use IR
12
use IR.Values
13
14
use Ghul.Pipes
15
16
17
// Loop and exception IL: try, catch, do and for walks, and the fused for loop.
18
partial GENERATE_IL is
19
pre(`try: Statements.TRY) -> bool is
20
super.pre(`try)
21
22
return true
23
si
24
25
get_exception_handler_temps() -> (outer_try: LOOP_LABELS?, return_needed: TEMP, return_value: TEMP?) is
26
let return_type = current_function!.return_type
27
28
let return_needed: TEMP mut
29
let return_value: TEMP? mut = _
30
31
let outer_try = _loops.get_current_try()
32
33
if outer_try? then
34
// get_current_try returns only try-form labels, whose init sets return_needed
35
return_needed = outer_try.return_needed!
36
return_value = outer_try.return_value
37
else
38
return_needed = TEMP(current_block, "need_return", 1, _innate_symbol_lookup.get_bool_type())
39
40
if return_type? /\ !return_type.matches(_innate_symbol_lookup.get_void_type()) then
41
return_value = TEMP(current_block, "return", return_type)
42
fi
43
fi
44
45
return (outer_try, return_needed, return_value)
46
si
47
48
gen_exception_handler_exit(outer_try: LOOP_LABELS?, label: LOOP_LABELS, return_value: TEMP?) is
49
let brancher = get_brancher_for_block()
50
51
brancher.label(label.start)
52
brancher.branch(BRANCH.Z, label.return_needed!.load(), label.end)
53
54
if outer_try? then
55
if return_value? then
56
assert outer_try.return_value? else "outer try has no return value temporary"
57
outer_try.return_value.store(return_value.load())
58
fi
59
60
if outer_try.is_in_finally then
61
outer_try.return_needed!.store(Literal.NUMBER(1, _innate_symbol_lookup.get_bool_type()))
62
brancher.branch(outer_try.middle)
63
else
64
brancher.leave(outer_try.start)
65
fi
66
elif
67
_current_async_state_machine? /\
68
_current_async_success_label? /\
69
current_function == _current_async_state_machine!.function
70
then
71
// State-machine async: a return inside a try block
72
// accumulates the value in `return_value` and the
73
// "need return" flag. The post-try emit reaches here
74
// — for state-machine async we stash the value to
75
// `_result` and `leave success_label` (NOT `ret`),
76
// so the trailer's `builder.SetResult` fires.
77
let frame = _current_async_state_machine!.frame
78
79
assert frame? else "async state machine has no frame at exception handler exit emission"
80
81
let result_field = frame.result_field
82
83
if return_value? /\ result_field? then
84
add(_build_frame_field_store(frame, result_field, return_value.load()))
85
fi
86
87
add(Values.BRANCH_TO(ILOpCode.LEAVE, _current_async_success_label!))
88
else
89
if return_value? then
90
add(return_value.load())
91
fi
92
93
add(Values.RET())
94
fi
95
96
brancher.label(label.end)
97
si
98
99
visit(`try: Statements.TRY) is
100
if
101
`try.catches.count == 0 /\
102
!`try.`finally?
103
then
104
`try.body.walk(self)
105
106
super.visit(`try)
107
108
return
109
fi
110
111
let brancher = get_brancher_for_block()
112
113
let return_type = current_function!.return_type
114
115
let (outer_try, return_needed, return_value) = get_exception_handler_temps()
116
117
let label = _loops.enter_try(return_needed, return_value)
118
119
let need_double_try = `try.catches.count > 0 /\ `try.`finally?
120
121
// The extent each handler is attached to. With both
122
// catches and a finally the two `.try` blocks are separate
123
// extents, the finally's covering the catches; with only
124
// one kind of handler there is a single block and both
125
// names stand for it.
126
let body_extent = Values.TRY_EXTENT()
127
128
let finally_extent =
129
if need_double_try then
130
Values.TRY_EXTENT()
131
else
132
body_extent
133
fi
134
135
let outer_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null
136
let body_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null
137
138
if need_double_try then
139
add(Values.TRY_START(finally_extent))
140
outer_dispatch_holder = _maybe_push_dispatch_holder()
141
fi
142
143
add(Values.TRY_START(body_extent))
144
145
body_dispatch_holder = _maybe_push_dispatch_holder()
146
147
`try.body.walk(self)
148
149
_maybe_pop_dispatch_holder(body_dispatch_holder)
150
151
brancher.leave(label.start)
152
153
add(Values.TRY_END(body_extent))
154
155
_try_extents.push(body_extent)
156
157
for c in `try.catches do
158
c.walk(self)
159
od
160
161
_try_extents.pop()
162
163
if need_double_try then
164
_maybe_pop_dispatch_holder(outer_dispatch_holder)
165
add(Values.TRY_END(finally_extent))
166
fi
167
168
let `finally = `try.`finally
169
170
if `finally? then
171
label.is_in_finally = true
172
173
let region = Values.PROTECTED_REGION(finally_extent, null)
174
175
add(Values.HANDLER_START(region))
176
177
let skip_finally_label = _open_finally_state_guard()
178
179
`finally.walk(self)
180
181
_close_finally_state_guard(skip_finally_label)
182
183
brancher.label(label.middle)
184
185
add(Values.INSTRUCTION(ILOpCode.ENDFINALLY))
186
add(Values.HANDLER_END(region))
187
fi
188
189
_loops.leave_loop()
190
191
// let outer_try = _loops.get_current_try();
192
gen_exception_handler_exit(outer_try, label, return_value)
193
194
super.visit(`try)
195
si
196
197
pre(`catch: Statements.CATCH) -> bool is
198
super.pre(`catch)
199
200
return true
201
si
202
203
visit(`catch: Statements.CATCH) is
204
let variable = `catch.variable!
205
206
let region =
207
Values.PROTECTED_REGION(
208
_try_extents.peek(),
209
variable.type_expression.type!)
210
211
add(Values.HANDLER_START(region))
212
213
let brancher = get_brancher_for_block()
214
215
// Catch handler enters with the exception on the stack
216
// and immediately stores it into the catch variable. The
217
// loop-scoped default-store added for issue #483 must be
218
// suppressed here — it would clobber the caught value.
219
_in_catch_variable = true
220
variable.walk(self)
221
_in_catch_variable = false
222
223
let symbol = find(variable.name!)
224
225
// State-machine-resident catch variable (async function):
226
// the exception's on the stack but `stloc <name>` would
227
// reference an undeclared CLR local since pre(VARIABLE)
228
// skipped the `.locals init` directive. Route through
229
// the frame field via a temp instead.
230
if let v: Semantic.Symbols.Variable = symbol, sm_field = v.state_machine_field then
231
let exception_type = variable.type_expression.type!
232
let caught = ".catch_exc"
233
234
add(Values.DECLARE_LOCAL(caught, exception_type))
235
add(Values.STORE_TEMP(caught, exception_type))
236
237
add(
238
_build_frame_field_store(
239
cast Semantic.Symbols.STATE_MACHINE_FRAME_BASE?(sm_field.owner)!,
240
sm_field,
241
Values.Load.TEMP(caught, exception_type)))
242
else
243
add(
244
Values.STORE_TEMP(
245
symbol!.il_name,
246
variable.type_expression.type!))
247
fi
248
249
`catch.body.walk(self)
250
251
brancher.leave(_loops.get_current_try()!.start)
252
253
add(Values.HANDLER_END(region))
254
255
super.visit(`catch)
256
si
257
258
pre(`do: Statements.DO) -> bool is
259
super.pre(`do)
260
261
return true
262
si
263
264
visit(`do: Statements.DO) is
265
let loop = _loops.enter_loop(`do)
266
267
// Loop-as-expression: compile-expressions decided capture-
268
// vs-spill (a suspend point in the body routes the result
269
// through a frame field). Every exit converges at one
270
// label carrying the result — see _gen_loop_result.
271
let value_block: Values.BLOCK? mut = null
272
273
if `do.want_value /\ `do.value? then
274
if isa Values.BLOCK(`do.value) then
275
value_block = `do.value
276
loop.wants_value = true
277
loop.result_type = `do.value!.type
278
fi
279
fi
280
281
let spiller = COMPOSITE_VALUE_SPILLER(self, `do, `do.value, _current_state_machine_frame())
282
283
spiller.enter()
284
285
// The brancher binds to the block current NOW — after the
286
// capture switch, so the loop's labels and branches land in
287
// the same stream as its body.
288
let brancher = get_brancher_for_block()
289
290
// Breaks emit during the body walk, so every label and
291
// slot they reach is allocated before it - mirroring how
292
// VAL_BLOCK pre-allocates its cross-try TEMP at block head.
293
if let vb = value_block, rt = loop.result_type then
294
if spiller.is_spilling then
295
loop.spill_field = spiller.spill_field
296
else
297
loop.cross_try_temp = TEMP(vb, "loop_cross_try", rt)
298
loop.cross_try_join = LABEL()
299
fi
300
301
loop.result_label = LABEL()
302
fi
303
304
brancher.label(loop.start)
305
306
if let `do.binding? then
307
// `while let` clauses: emit the same per-clause
308
// test / bind sequence as `if let`, but branch
309
// failures to `loop.end` (loop exit) instead of an
310
// arm-`next` label. Each iteration re-evaluates
311
// every clause's scrutinee and re-binds — the
312
// bound names are freshly stored on each pass.
313
let first = _emit_if_let_clause_test(binding.clauses[0], loop.end, true, brancher)
314
315
self.pre(binding)
316
317
if first? then
318
_emit_if_let_clause_bind(binding.clauses[0], first, loop.end)
319
fi
320
321
if binding.clauses[0].guard? then
322
binding.clauses[0].guard!.walk(self)
323
brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, loop.end, "while-let-guard")
324
fi
325
326
let i mut = 1
327
while i < binding.clauses.count do
328
let clause = binding.clauses[i]
329
330
let temp = _emit_if_let_clause_test(clause, loop.end, false, brancher)
331
332
if temp? then
333
_emit_if_let_clause_bind(clause, temp, loop.end)
334
fi
335
336
if clause.guard? then
337
clause.guard.walk(self)
338
brancher.branch(BRANCH.Z, clause.guard!.value!, loop.end, "while-let-guard")
339
fi
340
341
i = i + 1
342
od
343
elif `do.condition? then
344
`do.condition.walk(self)
345
346
brancher.branch(BRANCH.Z, `do.condition!.value!, loop.end, "while")
347
fi
348
349
`do.body.walk(self)
350
351
brancher.branch(loop.start)
352
353
brancher.label(loop.end)
354
355
if let vb = value_block, rt = loop.result_type then
356
_gen_loop_result(loop, rt, spiller)
357
fi
358
359
super.visit(`do)
360
361
spiller.leave()
362
363
_loops.leave_loop()
364
si
365
366
pre(`for: Statements.FOR) -> bool is
367
super.pre(`for)
368
369
return true
370
si
371
372
visit(`for: Statements.FOR) is
373
// A recognised, fusible Pipe[T] chain is lowered to one
374
// inline loop over the pinned base, applying the map/filter
375
// stages per element, instead of iterating the built pipe
376
// objects. Not in a state machine, whose iterator must live
377
// on the frame to survive yield/await re-entry - those fall
378
// through to the normal loop below. A loop in expression
379
// position also takes the normal path: its result-value
380
// convergence hangs off this method's shape.
381
if
382
!`for.want_value /\
383
`for.fusion? /\
384
!Semantic.Symbols.state_machine_for(current_function)? /\
385
!Semantic.Symbols.async_state_machine_for(current_function)?
386
then
387
_visit_fused_for(`for)
388
389
return
390
fi
391
392
let loop = _loops.enter_loop(`for)
393
394
let value_block: Values.BLOCK? mut = null
395
396
if `for.want_value /\ `for.value? then
397
if isa Values.BLOCK(`for.value) then
398
value_block = `for.value
399
loop.wants_value = true
400
loop.result_type = `for.value!.type
401
fi
402
fi
403
404
let spiller = COMPOSITE_VALUE_SPILLER(self, `for, `for.value, _current_state_machine_frame())
405
406
spiller.enter()
407
408
// The brancher binds to the block current NOW — after the
409
// capture switch, so the loop's labels and branches land in
410
// the same stream as its body.
411
let brancher = get_brancher_for_block()
412
413
// Breaks emit during the body walk, so every label and
414
// slot they reach is allocated before it.
415
if let vb = value_block, rt = loop.result_type then
416
if spiller.is_spilling then
417
loop.spill_field = spiller.spill_field
418
else
419
loop.cross_try_temp = TEMP(vb, "loop_cross_try", rt)
420
loop.cross_try_join = LABEL()
421
fi
422
423
loop.result_label = LABEL()
424
fi
425
426
let expression = `for.expression!
427
let variable = `for.variable!
428
let body = `for.body!
429
430
let iterator: Value mut
431
432
if `for.read_iterator? then
433
iterator = `for.read_iterator.call(expression.location, expression.value!, Collections.LIST[Value](0), null, _function_caller)
434
else
435
iterator = expression.value!
436
fi
437
438
variable.walk(self)
439
440
// Generator / async FOR: the iterator value has to survive
441
// every yield / await in the body, so it is read and written
442
// through the frame field compile-expressions allocated for
443
// it. The regular CLR-local TEMP path would reset on every
444
// MoveNext re-entry.
445
let iterator_field = `for.iterator_field
446
let iterator_frame = _current_state_machine_frame()
447
448
if iterator_field? /\ iterator_frame? then
449
add(_build_frame_field_store(iterator_frame, iterator_field, iterator))
450
fi
451
452
let temp: TEMP? mut = null
453
454
if !iterator_field? then
455
temp = TEMP(current_block, "iterator", iterator)
456
fi
457
458
brancher.label(loop.start)
459
460
let load_iter_a =
461
if iterator_field? then
462
cast Value(IR.Values.Load.INSTANCE_FIELD(
463
IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type),
464
iterator_field
465
))
466
else
467
cast Value(temp!.load())
468
fi
469
470
let has_next = `for.move_next!.call(expression.location, load_iter_a, Collections.LIST[Value](0), null, _function_caller)
471
472
brancher.branch(BRANCH.Z, has_next, loop.end)
473
474
let load_iter_b =
475
if iterator_field? then
476
cast Value(IR.Values.Load.INSTANCE_FIELD(
477
IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type),
478
iterator_field
479
))
480
else
481
cast Value(temp!.load())
482
fi
483
484
let current = `for.read_current!.call(expression.location, load_iter_b, Collections.LIST[Value](0), null, _function_caller)
485
486
gen_destructuring_initialize(variable.left, current)
487
488
// not to generate code for the expression itself, but to capture IL for any anonymous function bodies:
489
// TODO check that if expressions within the expression are emitted correctly
490
expression.walk(self)
491
492
body.walk(self)
493
494
brancher.branch(loop.start)
495
496
brancher.label(loop.end)
497
498
if let vb = value_block, rt = loop.result_type then
499
_gen_loop_result(loop, rt, spiller)
500
fi
501
502
super.visit(`for)
503
504
spiller.leave()
505
506
_loops.leave_loop()
507
si
508
509
// Declare the per-stage locals a fused chain needs before its loop,
510
// filling `stage_delegates` / `stage_counters` parallel to the
511
// stages (null where a slot does not apply): an inlined stage's
512
// parameter local, a delegate stage's hoisted function value, an
513
// index stage's running counter initialised to its start.
514
_setup_fused_stages(
515
fusion: Syntax.Process.PIPE_FUSION,
516
stage_delegates: Collections.LIST[TEMP?],
517
stage_counters: Collections.LIST[TEMP?]
518
) is
519
for stage in fusion.stages_outermost_first do
520
if stage.is_countdown then
521
// take/skip: evaluate the count once into a running
522
// counter the loop decrements per pulled element.
523
let counter = TEMP(current_block, "fused_countdown", _innate_symbol_lookup.get_int_type())
524
counter.store(stage.argument!.value!)
525
stage_delegates.add(null)
526
stage_counters.add(counter)
527
elif stage.is_inlined then
528
add(
529
Values.DECLARE_LOCAL(
530
stage.param_local!.il_name, stage.param_local!.storage_type!))
531
stage_delegates.add(null)
532
stage_counters.add(null)
533
elif stage.is_index then
534
// The counter starts at the caller's start where one
535
// was given, and at zero otherwise.
536
let int_type = _innate_symbol_lookup.get_int_type()
537
let counter = TEMP(current_block, "fused_index", int_type)
538
539
if let start = stage.argument then
540
counter.store(start.value!)
541
else
542
counter.store(Literal.NUMBER(0, int_type))
543
fi
544
545
stage_delegates.add(null)
546
stage_counters.add(counter)
547
else
548
stage_delegates.add(TEMP(current_block, "fused_stage", stage.argument!.value!))
549
stage_counters.add(null)
550
fi
551
od
552
si
553
554
// Before the loop pulls an element, leave via `take_exit` when any
555
// take stage has spent its count: a fused take then pulls exactly
556
// the elements it yields, as the TAKE_PIPE it replaces does, and
557
// never advances the source past them.
558
_gen_fused_take_guards(
559
fusion: Syntax.Process.PIPE_FUSION,
560
stage_counters: Collections.List[TEMP?],
561
brancher: BLOCK_BRANCHER,
562
take_exit: LABEL
563
) is
564
let int_type = _innate_symbol_lookup.get_int_type()
565
566
for index in 0..fusion.stages_outermost_first.count do
567
if fusion.stages_outermost_first[index].is_take then
568
brancher.branch(BRANCH.LE, stage_counters[index]!.load(), cast Value(Literal.NUMBER(0, int_type)), take_exit)
569
fi
570
od
571
si
572
573
// Apply a fused chain's stages to `element` innermost-first,
574
// returning the fully-transformed value. A map result and an index
575
// result each land in a fresh temp so a downstream stage can reload
576
// them; a filter reject branches to `loop_start` (the next pull); a
577
// take spends one of its count, checked before the next pull, which
578
// leaves via `take_exit` - `loop_end` unless the caller has a source
579
// to reset first; an index stage builds
580
// INDEXED_VALUE(counter++, current) directly.
581
_apply_fused_stages(
582
fusion: Syntax.Process.PIPE_FUSION,
583
element: Value,
584
stage_delegates: Collections.List[TEMP?],
585
stage_counters: Collections.List[TEMP?],
586
brancher: BLOCK_BRANCHER,
587
loop_start: LABEL,
588
loop_end: LABEL,
589
take_exit: LABEL
590
) -> Value is
591
let int_type = _innate_symbol_lookup.get_int_type()
592
593
let current_value: Value mut = element
594
595
let index mut = fusion.stages_outermost_first.count - 1
596
597
while index >= 0 do
598
let stage = fusion.stages_outermost_first[index]
599
600
if stage.is_take then
601
// This element spends one of the take's count; the loop
602
// checks the count before pulling the next element (see
603
// _gen_fused_take_guards). The element passes through
604
// unchanged.
605
add(IR.Values.DECREMENT(stage_counters[index]!.il_name, int_type))
606
elif stage.is_skip then
607
// Decrement the counter; while it stays non-negative this
608
// is a leading element to drop, so pull the next one.
609
brancher.branch(BRANCH.GE, cast Value(IR.Values.PRE_DECREMENT(stage_counters[index]!.il_name, int_type)), cast Value(Literal.NUMBER(0, int_type)), loop_start)
610
else
611
let stage_result: Value mut
612
613
if stage.is_inlined then
614
// Assign the incoming element to the parameter local;
615
// the pre-harvested body IR reads it (and any captured
616
// outer locals) as ordinary locals - no delegate, no
617
// frame.
618
add(IR.Values.Store.LOCAL_VARIABLE(stage.param_local!, current_value))
619
620
stage_result = stage.inline_body!
621
elif stage.is_index then
622
// INDEXED_VALUE(counter, current) with the counter
623
// post-incremented in place - no INDEX_PIPE object.
624
let arguments = Collections.LIST[Value]()
625
arguments.add(cast Value(IR.Values.POST_INCREMENT(stage_counters[index]!.il_name, int_type)))
626
arguments.add(current_value)
627
628
stage_result = IR.Values.NEW(stage.indexed_value_type!, stage.indexed_value_constructor!, arguments)
629
else
630
let delegate = stage_delegates[index]!.load()
631
let func_type = stage.argument!.value!.type!
632
633
let result_type =
634
if func_type.is_action then
635
_innate_symbol_lookup.get_void_type()
636
else
637
func_type.arguments[func_type.arguments.count - 1]
638
fi
639
640
let call_arguments = Collections.LIST[Value]()
641
call_arguments.add(current_value)
642
643
stage_result = IR.Values.Call.CLOSURE(delegate, result_type, func_type.is_action, func_type, call_arguments)
644
fi
645
646
if stage.is_filter then
647
// Rejected elements skip straight to the next pull.
648
brancher.branch(BRANCH.Z, stage_result, loop_start)
649
else
650
current_value = TEMP(current_block, "fused_mapped", stage_result).load()
651
fi
652
fi
653
654
index = index - 1
655
od
656
657
return current_value
658
si
659
660
// Lower a recognised fusible Pipe[T] chain to a single loop:
661
// drive the pinned base's own iterator, apply each map/filter/index
662
// stage inline per element (map/index assign a temp, filter skips to
663
// the next element), then bind the loop variable and run the
664
// body. The built MAP_PIPE / FilterPipe / INDEX_PIPE objects and
665
// their per-element virtual dispatch are never emitted.
666
// Convergence for a loop used as an expression (see LOOP_
667
// LABELS): the natural exit yields absence; valued breaks
668
// arrive having pushed or stored their value; a cross-try
669
// join reloads the stashed temp. One result-typed value is
670
// available to the consumer afterwards. The result label was
671
// allocated at loop entry — breaks target it from inside the
672
// body walk.
673
_gen_loop_result(loop: LOOP_LABELS, result_type: Semantic.Types.Type, spiller: COMPOSITE_VALUE_SPILLER) is
674
let brancher = get_brancher_for_block()
675
let result = loop.result_label!
676
677
spiller.emit_value(IR.Values.DEFAULT(result_type))
678
brancher.branch(result)
679
680
if loop.cross_try_used then
681
brancher.label(loop.cross_try_join!)
682
add(loop.cross_try_temp!.load())
683
fi
684
685
brancher.label(result)
686
si
687
688
_visit_fused_for(`for: Statements.FOR) is
689
let fusion = `for.fusion!
690
let source = fusion.source
691
let variable = `for.variable!
692
let body = `for.body!
693
694
let brancher = get_brancher_for_block()
695
let loop = _loops.enter_loop(`for)
696
697
// Iterate the source directly - the wrapping pipe objects are
698
// never built. read_iterator is null when the source is its
699
// own iterator (e.g. a range).
700
let iterator: Value mut
701
702
if fusion.source_read_iterator? then
703
iterator = fusion.source_read_iterator.call(source.location, source.value!, Collections.LIST[Value](0), null, _function_caller)
704
else
705
iterator = source.value!
706
fi
707
708
variable.walk(self)
709
710
let temp = TEMP(current_block, "fused_iterator", iterator)
711
712
// Per-stage setup: an inlined stage needs a slot for its
713
// parameter local; a delegate stage hoists its function value
714
// into a local so the delegate is built once, not per element;
715
// an index stage needs a running counter, initialised to its
716
// start.
717
let stage_delegates = Collections.LIST[TEMP?]()
718
let stage_counters = Collections.LIST[TEMP?]()
719
720
_setup_fused_stages(fusion, stage_delegates, stage_counters)
721
722
// A spent take leaves the loop with the cursor part way
723
// through, so it lands on its own label to rewind it before
724
// falling into the loop exit. Nothing else needs it: an
725
// exhausted source has already rewound itself, and a `break`
726
// in the body abandons the chain exactly as the unfused
727
// lowering does.
728
let pipe_reset = fusion.pipe_reset
729
let take_exit = if pipe_reset? then LABEL() else loop.end fi
730
731
brancher.label(loop.start)
732
733
_gen_fused_take_guards(fusion, stage_counters, brancher, take_exit)
734
735
let has_next = fusion.source_move_next!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller)
736
737
brancher.branch(BRANCH.Z, has_next, loop.end)
738
739
let element = fusion.source_read_current!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller)
740
741
let current_value = _apply_fused_stages(fusion, element, stage_delegates, stage_counters, brancher, loop.start, loop.end, take_exit)
742
743
gen_destructuring_initialize(variable.left, current_value)
744
745
// Emit any anonymous-function bodies in the chain (the
746
// map/filter lambdas, plus anything inside the source).
747
// Walking these nodes emits the nested closure method bodies
748
// without emitting the pipe-construction IL itself.
749
source.walk(self)
750
751
for stage in fusion.stages_outermost_first do
752
if stage.argument? then
753
stage.argument.walk(self)
754
fi
755
od
756
757
body.walk(self)
758
759
brancher.branch(loop.start)
760
761
// Rewinding asks the cursor at run time whether it is a
762
// `Pipe[E]`, exactly as a stage pipe's own rewind does: one is
763
// reset in place so every holder sees it start over, and
764
// anything else is left where it stopped.
765
if let reset = pipe_reset, pipe_type = fusion.pipe_type then
766
brancher.label(take_exit)
767
768
let as_pipe = TEMP(current_block, "fused_pipe", cast Value(CAST(pipe_type, temp.load(), false)))
769
770
brancher.branch(BRANCH.Z, as_pipe.load(), loop.end)
771
772
add(reset.call(source.location, as_pipe.load(), Collections.LIST[Value](0), null, _function_caller))
773
fi
774
775
brancher.label(loop.end)
776
777
super.visit(`for)
778
779
_loops.leave_loop()
780
si
781
782
si
783
si