Skip to content
← Back

src/syntax/process/generate-il/generate_il.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
// Holds a generator function whose state-machine class hasn't
17
// been emitted yet — its MoveNext block was filled in while
18
// walking the function's body, but the wrapping `.class` lives
19
// outside the enclosing user class in IL. Flushed by
20
// `emit_pending_state_machines` at the tail of the enclosing
21
// class / namespace visit.
22
class PENDING_STATE_MACHINE is
23
state_machine: Semantic.Symbols.STATE_MACHINE
24
move_next_block: IR.Values.BLOCK
25
26
init(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: IR.Values.BLOCK) is
27
self.state_machine = state_machine
28
self.move_next_block = move_next_block
29
si
30
si
31
32
// Async sibling of PENDING_STATE_MACHINE. Flushed by
33
// `emit_pending_state_machines` alongside the generator list.
34
class PENDING_ASYNC_STATE_MACHINE is
35
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE
36
move_next_block: IR.Values.BLOCK
37
success_label: IR.LABEL
38
end_label: IR.LABEL
39
40
init(
41
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE,
42
move_next_block: IR.Values.BLOCK,
43
success_label: IR.LABEL,
44
end_label: IR.LABEL
45
) is
46
self.async_state_machine = async_state_machine
47
self.move_next_block = move_next_block
48
self.success_label = success_label
49
self.end_label = end_label
50
si
51
si
52
53
// One entry in a dispatch placeholder — the pair an
54
// AWAIT_SUSPEND contributes when it registers with its
55
// enclosing protected region. `state` is the suspension state
56
// number; `cold_resume_label` is the IR.LABEL inside the same
57
// region's `.try` body that GET_RESULT routes through.
58
class ASYNC_DISPATCH_ENTRY is
59
state: int
60
cold_resume_label: IR.LABEL
61
62
init(state: int, cold_resume_label: IR.LABEL) is
63
self.state = state
64
self.cold_resume_label = cold_resume_label
65
si
66
si
67
68
// IL-emission frame for a `val ... lav` block currently in
69
// scope. Returns inside the body whose val_block_target is this
70
// block push their value and `br` to `end_label`; the natural
71
// fall-through emits the tail value and falls into the same
72
// label. All paths converge at end_label with exactly one value
73
// on the evaluation stack (or empty for a void block). No value
74
// ever lives in a CLR local or frame field at a suspension
75
// point, so async-await and generator-yield compose without
76
// bespoke handling.
77
//
78
// result_type is the resolved block value type from compile-
79
// expressions; void means no value-push. is_void is cached to
80
// skip the lookup at every return emission. enclosing_try_count
81
// is the number of `.try` entries on the loop-label stack at
82
// val-block entry — a return whose current try count exceeds
83
// this opened a try INSIDE the val-block body and needs `leave`
84
// instead of `br` to exit the protected region. value_block is
85
// the captured IR BLOCK used in capture mode; pre(VAL_BLOCK)
86
// eagerly allocates the cross-try TEMP on it before walking
87
// the body, so the `.locals init` lands at the head of value_
88
// block's IL stream rather than inside an inner `.try {}`.
89
class VAL_BLOCK_IL is
90
block: Trees.Expressions.VAL_BLOCK
91
end_label: IR.LABEL
92
result_type: Semantic.Types.Type?
93
is_void: bool
94
enclosing_try_count: int
95
value_block: IR.Values.BLOCK?
96
97
// Non-null when generate-il is spilling this val-block to a
98
// frame field (body contains a state-machine suspend). Val-
99
// targeted returns then `stfld $spill_field` instead of
100
// leaving the value on the stack, so all paths converge at
101
// end_label with the value in the field and an empty stack.
102
spill_field: Semantic.Symbols.Field? public
103
104
// Capture-mode cross-try state. The TEMP and label must be
105
// allocated BEFORE the body walk, so the declaration is
106
// encoded ahead of the load at the post-body join site
107
// rather than somewhere inside the body. VAL_BLOCK.pre
108
// pre-allocates both eagerly when value_block has a
109
// non-void result type and is not spilling.
110
//
111
// A val-targeted `return E` inside an inner `.try` can't
112
// `br end_label` (invalid IL across a protected region)
113
// and can't carry its value across `leave` (empty-stack
114
// precondition). Mirrors the function-return-from-try
115
// pattern: stash the value into `cross_try_temp`, `leave
116
// cross_try_join_label`, and at the join `ldloc` the temp
117
// back onto the stack so it falls through into end_label.
118
// `cross_try_used` flags whether the join block needs to
119
// be emitted at all — otherwise the unconditional `br
120
// end_label; join: ldloc; end_label` would push
121
// uninitialised data onto the stack on the natural path's
122
// divergent variants.
123
cross_try_temp: IR.TEMP? public
124
cross_try_join_label: IR.LABEL? public
125
cross_try_used: bool public
126
127
init(
128
block: Trees.Expressions.VAL_BLOCK,
129
end_label: IR.LABEL,
130
result_type: Semantic.Types.Type?,
131
is_void: bool,
132
enclosing_try_count: int,
133
value_block: IR.Values.BLOCK?
134
) is
135
self.block = block
136
self.end_label = end_label
137
self.result_type = result_type
138
self.is_void = is_void
139
self.enclosing_try_count = enclosing_try_count
140
self.value_block = value_block
141
si
142
si
143
144
// Helper for the four composites whose AST value sits in an
145
// `IR.Values.BLOCK` and whose body may suspend (`val ... lav`,
146
// a `Statements.LIST` in expression position, `if` / `case` in
147
// expression position). The natural emission captures body IL
148
// inside the value BLOCK; a consumer later replays it. When the
149
// captured IL contains a state-machine suspend, the replay traps
150
// the suspend's `leave` / state register inside the consumer's
151
// stack setup — invalid IL plus state-loss across MoveNext
152
// re-entry.
153
//
154
// Spill mode breaks the capture: the body emits inline in outer
155
// current_block alongside the dispatcher, each value-producing
156
// path lands in an anonymous frame field via `ldarg.0; v; stfld`,
157
// and the value BLOCK is rewritten to hold just a `ldfld` of the
158
// field. Consumers see a clean load.
159
//
160
// One spiller per composite. Which mode it is in follows from
161
// whether compile-expressions recorded a frame field for the
162
// node, which is where the spill-vs-capture decision is made.
163
// `enter`
164
// opens the BLOCK for direct emission when capturing; `emit
165
// _value` emits each tail/branch value (spill or forward);
166
// `leave` either rewrites the BLOCK's contents to a field load
167
// (spill) or closes it with `leave_block` (capture). VAL_BLOCK
168
// also reads `spill_field` to make val-targeted returns store
169
// into the same field.
170
class COMPOSITE_VALUE_SPILLER is
171
_gen: GENERATE_IL
172
_value_block: IR.Values.BLOCK?
173
_frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE?
174
_spill_field: Semantic.Symbols.Field?
175
176
init(
177
gen: GENERATE_IL,
178
node: Trees.Node,
179
value: IR.Values.Value?,
180
frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE?
181
) is
182
_gen = gen
183
184
if !value? \/ !isa IR.Values.BLOCK(value) then
185
return
186
fi
187
188
_value_block = cast IR.Values.BLOCK(value)
189
190
if !frame? then
191
return
192
fi
193
194
// Compile-expressions makes the spill-vs-capture decision
195
// and allocates the field, because a frame field first
196
// declared here would be numbered by nothing. An entry
197
// means spill; none means capture.
198
let spill_field = gen.composite_spill_state.get(node)
199
200
if !spill_field? then
201
return
202
fi
203
204
_frame = frame
205
_spill_field = spill_field
206
si
207
208
is_spilling: bool => _spill_field?
209
210
spill_field: Semantic.Symbols.Field? => _spill_field
211
212
enter() is
213
if !_value_block? \/ _spill_field? then
214
return
215
fi
216
_gen.enter_block(_value_block)
217
si
218
219
emit_value(value: IR.Values.Value) is
220
let spill = _spill_field
221
222
if spill? then
223
_gen.add(_gen._build_frame_field_store(_frame!, spill, value))
224
else
225
_gen.add(value)
226
fi
227
si
228
229
leave() is
230
if _spill_field? then
231
_value_block!.add(_gen._build_frame_field_load(_frame!, _spill_field))
232
elif _value_block? then
233
_gen.leave_block()
234
fi
235
si
236
si
237
238
// Placeholder block sitting at the top of one protected region's
239
// body (MoveNext's outer try, or any inner let-use / user `.try`).
240
// While the region's body walks, AWAIT_SUSPENDs / YIELDs that
241
// fire inside it append their state-label pair via `register`.
242
// Region close populates `block` with `ldloc V_state; ldc.i4 N;
243
// beq cold_N` for each entry — keeping the dispatch branches
244
// inside the same `.try` as the targets they reach — and hands
245
// those states to the enclosing holder, retargeted to
246
// `top_label`: from outside a protected region, control may only
247
// enter at its first instruction, so the enclosing dispatch
248
// routes a resuming MoveNext straight past any statements between
249
// the regions (hoisted-variable initializers, loop iterators)
250
// that must not re-run on re-entry.
251
//
252
// `state_local_il` is the IL spelling of the CLR local cached at
253
// MoveNext entry — `'.async_state'` for async state machines,
254
// `'.gen_state'` for generators. Both nest under the same
255
// dispatch-holder machinery; only the local name differs.
256
class ASYNC_DISPATCH_HOLDER is
257
block: IR.Values.BLOCK
258
state_local_il: string
259
top_label: IR.LABEL
260
is_state_machine_entry: bool
261
_entries: Collections.LIST[ASYNC_DISPATCH_ENTRY]
262
263
init(
264
block: IR.Values.BLOCK,
265
state_local_il: string,
266
top_label: IR.LABEL,
267
is_state_machine_entry: bool
268
) is
269
self.block = block
270
self.state_local_il = state_local_il
271
self.top_label = top_label
272
self.is_state_machine_entry = is_state_machine_entry
273
_entries = Collections.LIST[ASYNC_DISPATCH_ENTRY]()
274
si
275
276
register(state: int, cold_resume_label: IR.LABEL) is
277
_entries.add(ASYNC_DISPATCH_ENTRY(state, cold_resume_label))
278
si
279
280
entries: Collections.Iterable[ASYNC_DISPATCH_ENTRY] => _entries
281
si
282
283
class GENERATE_IL: ScopedVisitor is
284
_logger: Logger
285
_symbol_table: Semantic.SYMBOL_TABLE
286
_symbol_loader: Semantic.SYMBOL_LOADER
287
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup
288
_function_caller: Semantic.FUNCTION_CALLER
289
_type_caster: Semantic.TYPE_CASTER
290
_overload_resolver: Semantic.OVERLOAD_RESOLVER
291
_symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS
292
_build_flags: Compiler.GLOBAL_BUILD_FLAGS
293
294
// Read by COMPOSITE_VALUE_SPILLER: an entry is compile-
295
// expressions telling this composite to spill.
296
composite_spill_state: COMPOSITE_SPILL_STORE
297
298
// Read for a literal leaf's value-equality test; see
299
// VARIABLE_LEFT_STATE.
300
_variable_left_state: VARIABLE_LEFT_STATE_STORE
301
302
_context: CONTEXT
303
_brancher: BRANCHER
304
_boxer: VALUE_BOXER
305
_loops: LOOP_LABEL_STACK
306
_block_context: BlockContext
307
308
// Asked which return types complete without a state machine;
309
// depends only on the innate lookup, so it is built here rather
310
// than threaded through the constructor.
311
_task_conversion: Semantic.TASK_CONVERSION
312
313
// The `.try` block each catch clause currently being walked is
314
// attached to. A catch is visited on its own, after the block
315
// it guards has closed, so it has no other route to the extent
316
// its region needs.
317
_try_extents: Collections.STACK[Values.TRY_EXTENT]
318
319
_il_output_depth: int
320
321
// Set by an `@entry` pragma, and cleared by the next function the
322
// walk enters - the one the pragma annotates.
323
324
_in_catch_variable: bool
325
326
_indent: int
327
_depth: int
328
_run_on: bool
329
_indent_needed: bool
330
331
_interpolation_handler: Semantic.Types.Type?
332
_constructor: Semantic.Symbols.Function
333
_append_literal: Semantic.Symbols.Function
334
_append_formatted_generic: Semantic.Symbols.Function
335
_interpolated_value_classifier: Semantic.INTERPOLATED_VALUE_CLASSIFIER
336
_display_function: Semantic.Symbols.Function?
337
_append_formatted_generic_alignment: Semantic.Symbols.Function
338
_append_formatted_generic_format: Semantic.Symbols.Function
339
_append_formatted_generic_alignment_format: Semantic.Symbols.Function
340
_to_string_and_clear: Semantic.Symbols.Function
341
_dispose: Semantic.Symbols.Function
342
343
// Generators whose state-machine class IL emission has been
344
// deferred until the enclosing class/namespace closes. The
345
// outer function method is emitted inline; the state-machine
346
// class needs to land as a SIBLING of the user's class, not
347
// inside it (CIL has no nested-class-in-method form). Mirrors
348
// the existing closure-FRAME emission pattern (see
349
// gen_closures called from visit(`class)).
350
_pending_state_machines: Collections.LIST[PENDING_STATE_MACHINE]
351
_pending_async_state_machines: Collections.LIST[PENDING_ASYNC_STATE_MACHINE]
352
353
// Per-async-function emission context, set/cleared by
354
// `_pre_async_function`. Visit handlers for AWAIT / RETURN
355
// check `current_function == _current_async_state_machine
356
// .function` so a nested closure's own visit doesn't fire
357
// the outer function's async paths.
358
_current_async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE?
359
_current_async_success_label: IR.LABEL?
360
_current_async_end_label: IR.LABEL?
361
362
// Leave target for yields inside a `.try` body. CIL forbids
363
// `ret` from inside a protected region; yields that suspend
364
// from within an enclosing `.try` `leave` to this label, which
365
// sits outside any try and just emits `ldc.i4.1; ret`.
366
// Lazy: yield's emission sets this on first use; the generator
367
// trailer emits the label + ret only if it was actually used.
368
_current_generator_yield_return_label: IR.LABEL?
369
370
// Stack of dispatch placeholders for the currently-open
371
// protected regions (outermost is the MoveNext body itself).
372
// Pushed when entering a `.try` body, popped on close. Each
373
// AWAIT_SUSPEND that fires while a holder is active registers
374
// its (state, cold_resume_label) pair with the top holder.
375
// At close-of-region the holder's BLOCK is populated with
376
// `ldloc V_state; ldc.i4 N; beq cold_resume_N` for each
377
// registered await — so the `beq` and the target label live
378
// in the same `.try`, never crossing a region boundary — and
379
// the states are propagated to the enclosing holder against
380
// this region's entry, so the MoveNext-entry dispatch reaches
381
// resume points nested in `.try`s without re-running the code
382
// between the regions.
383
_async_dispatch_stack: Collections.LIST[ASYNC_DISPATCH_HOLDER]
384
385
// Stack of `val ... lav` IL frames, parallel to the
386
// val_block_target stamped on RETURN nodes by compile-
387
// expressions. Each entry holds the block's end_label, the
388
// resolved result_type, the open-`.try` count cached at
389
// val-block entry, and (in capture mode) the eagerly-
390
// allocated cross-try TEMP and join label. visit(RETURN)
391
// reads the top via the node's val_block_target identity
392
// and dispatches to one of three shapes: spill mode stfld's
393
// into the frame field then leave/br end_label; capture
394
// mode with no inner try pushes the value and `br`s
395
// end_label; capture mode crossing an inner try stashes the
396
// value into the TEMP and `leave`s the join label, where
397
// a post-body `ldloc` joins it back onto end_label's stack.
398
_val_block_il_stack: Collections.LIST[VAL_BLOCK_IL]
399
400
current_block: Values.BLOCK => _block_context.current_block
401
402
// The interpolation-handler and disposal symbols are
403
// materialized by ensure_runtime_symbols_are_materialized,
404
// since the runtime types are not loaded yet at construction.
405
@suppress("field-definite-assignment")
406
init(
407
logger: Logger,
408
symbol_table: Semantic.SYMBOL_TABLE,
409
namespaces: Semantic.NAMESPACES,
410
symbol_loader: Semantic.SYMBOL_LOADER,
411
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
412
function_caller: Semantic.FUNCTION_CALLER,
413
type_caster: Semantic.TYPE_CASTER,
414
overload_resolver: Semantic.OVERLOAD_RESOLVER,
415
symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS,
416
context: CONTEXT,
417
block_context: BlockContext,
418
brancher: BRANCHER,
419
boxer: VALUE_BOXER,
420
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
421
composite_spill_state: COMPOSITE_SPILL_STORE,
422
variable_left_state: VARIABLE_LEFT_STATE_STORE
423
)
424
is
425
super.init(logger, symbol_table, namespaces)
426
427
_logger = logger
428
_symbol_table = symbol_table
429
_symbol_loader = symbol_loader
430
_innate_symbol_lookup = innate_symbol_lookup
431
_task_conversion = Semantic.TASK_CONVERSION(innate_symbol_lookup)
432
_function_caller = function_caller
433
_type_caster = type_caster
434
_overload_resolver = overload_resolver
435
_symbol_use_locations = symbol_use_locations
436
_context = context
437
_block_context = block_context
438
_brancher = brancher
439
_boxer = boxer
440
_build_flags = build_flags
441
self.composite_spill_state = composite_spill_state
442
_variable_left_state = variable_left_state
443
444
_loops = LOOP_LABEL_STACK()
445
_try_extents = Collections.STACK[Values.TRY_EXTENT]()
446
_pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE]()
447
_pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE]()
448
_async_dispatch_stack = Collections.LIST[ASYNC_DISPATCH_HOLDER]()
449
_val_block_il_stack = Collections.LIST[VAL_BLOCK_IL]()
450
si
451
452
ensure_runtime_symbols_are_materialized() is
453
if _interpolation_handler? then
454
return
455
fi
456
457
let string_type = _innate_symbol_lookup.get_string_type()
458
let int_type = _innate_symbol_lookup.get_int_type()
459
460
461
// Materialize IDisposable.Dispose()
462
let idisposable_type = _innate_symbol_lookup.get_idisposable_type()
463
464
// there is only one overload of dispose:
465
_dispose = cast Semantic.Symbols.FUNCTION_GROUP?(idisposable_type.find_member("dispose"))!.functions[0]
466
467
_interpolated_value_classifier = Semantic.INTERPOLATED_VALUE_CLASSIFIER(_innate_symbol_lookup)
468
_display_function = _interpolated_value_classifier.display_function
469
470
// Materialize the various methods of the string interpolation handler:
471
let interpolation_handler = _innate_symbol_lookup.get_interpolated_string_handler_type()
472
_interpolation_handler = interpolation_handler
473
474
// the constructor overload we want is the only one with 2 arguments:
475
_constructor = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("init"))!.functions |>
476
filter(f => f.arguments.count == 2) |>
477
only()
478
479
// there is only one overload of append_literal:
480
_append_literal = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_literal"))!.functions[0]
481
482
// the first overload of append_formatted we want is the only one that is both generic and with 1 argument:
483
_append_formatted_generic = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
484
filter(f => f.is_generic /\ f.arguments.count == 1) |>
485
only()
486
487
// there are two overloads of append_formatted with 2
488
// arguments, we want the one where the second argument is
489
// an int:
490
_append_formatted_generic_alignment = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
491
filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(int_type) == Semantic.Types.MATCH.SAME) |>
492
only()
493
494
// there are two overloads of append_formatted with 2
495
// arguments, we want the one where the second argument is
496
// a string:
497
// (BCL declares the `format` parameter as `string?`, so SAME no longer matches under directional compare;
498
// != DIFFERENT accepts both `string` and `string?` shapes.)
499
_append_formatted_generic_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
500
filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(string_type) != Semantic.Types.MATCH.DIFFERENT) |>
501
only()
502
503
// there is only one overload of append_formatted with 3 arguments:
504
_append_formatted_generic_alignment_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
505
filter(f => f.is_generic /\ f.arguments.count == 3) |>
506
only()
507
508
// there is only one overload of to_string_and_clear:
509
_to_string_and_clear = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("to_string_and_clear"))!.functions[0]
510
si
511
512
apply(root: Trees.Node) is
513
if _logger.any_errors then
514
return
515
fi
516
517
LABEL.set_pass("_")
518
519
root.walk(self)
520
si
521
522
// Mirrors compile_expressions.ghul's enter_node/leave_node.
523
// pre(Statements.LIST) takes over the statement walk, so the
524
// location bracketing has to be re-applied by hand around
525
// each statement.
526
enter_node(node: Trees.Node) is
527
if !_build_flags.want_debug then
528
return
529
fi
530
IoC.CONTAINER.instance.location_stack.push(node.debug_location)
531
si
532
533
leave_node(node: Trees.Node) is
534
if !_build_flags.want_debug then
535
return
536
fi
537
IoC.CONTAINER.instance.location_stack.pop()
538
si
539
540
enter_block(block: Values.BLOCK) is
541
_block_context.enter_block(block)
542
si
543
544
enter_block() is
545
_block_context.enter_block()
546
si
547
548
leave_block() is
549
_block_context.leave_block()
550
si
551
552
get_brancher_for_block() -> BLOCK_BRANCHER =>
553
_brancher.get_for(_block_context.current_block)
554
555
add(value: Values.Value) is
556
current_block.add(value)
557
si
558
si
559
560
561
// A loop has no `middle`: only the try shape branches through one,
562
// and only the try constructor makes it.
563
@suppress("field-definite-assignment")
564
class LOOP_LABELS is
565
is_try: bool
566
is_in_finally: bool public
567
is_loop: bool => !is_try
568
569
name: string?
570
start: LABEL
571
middle: LABEL
572
end: LABEL
573
574
return_needed: TEMP?
575
return_value: TEMP?
576
577
// Loop-as-expression state. `node` correlates the entry with
578
// the AST loop, so a `break` whose target compile-expressions
579
// resolved by symbol finds this entry without a name search.
580
// The rest mirror the val-block's cross-try handling: a
581
// value-carrying exit from inside a `.try` cannot `br` with a
582
// value on the stack (`leave` requires it empty), so it stashes
583
// into cross_try_temp and `leave`s to cross_try_join, which
584
// reloads before result_label.
585
node: Trees.Statements.Statement? public
586
wants_value: bool public
587
result_type: Semantic.Types.Type? public
588
result_label: LABEL? public
589
cross_try_temp: TEMP? public
590
cross_try_join: LABEL? public
591
cross_try_used: bool public
592
spill_field: Semantic.Symbols.Field? public
593
enclosing_try_count: int public
594
595
init(name: string?) is
596
is_try = false
597
self.name = name
598
start = LABEL()
599
end = LABEL()
600
si
601
602
init(return_needed: TEMP, return_value: TEMP?) is
603
is_try = true
604
self.return_needed = return_needed
605
self.return_value = return_value
606
start = LABEL()
607
middle = LABEL()
608
end = LABEL()
609
si
610
611
matches(name: string) -> bool => self.name? /\ self.name! =~ name
612
si
613
614
class LOOP_LABEL_STACK is
615
_next_name: string?
616
617
loops: Collections.MutableList[LOOP_LABELS]
618
is_in_loop: bool => get_current_loop() != null
619
is_in_try: bool => get_current_try() != null
620
621
get_current_try() -> LOOP_LABELS? is
622
let index: int mut = loops.count - 1
623
624
while index >= 0 do
625
let result = loops[index]
626
627
if result.is_try then
628
return result
629
fi
630
631
index = index - 1
632
od
633
return null
634
si
635
636
// Number of `is_try` entries currently on the stack. Cached
637
// at val-block entry; a return inside the body whose count
638
// exceeds the cached value opened a try INSIDE the block,
639
// so `br end_label` would cross the try's boundary.
640
open_try_count: int is
641
let count mut = 0
642
for l in loops do
643
if l.is_try then
644
count = count + 1
645
fi
646
od
647
return count
648
si
649
650
get_current_loop() -> LOOP_LABELS? is
651
let index: int mut = loops.count - 1
652
653
while index >= 0 do
654
let result = loops[index]
655
656
if result.is_loop then
657
return result
658
fi
659
660
index = index - 1
661
od
662
return null
663
si
664
665
init() is
666
loops = Collections.LIST[LOOP_LABELS]()
667
si
668
669
next_name(name: string) is
670
_next_name = name
671
si
672
673
find(name: string) -> LOOP_LABELS? is
674
let index: int mut = loops.count - 1
675
676
while index >= 0 do
677
let result = loops[index]
678
679
if result.matches(name) then
680
return result
681
fi
682
683
index = index - 1
684
od
685
return null
686
si
687
688
enter_try(return_needed: TEMP, return_value: TEMP?) -> LOOP_LABELS is
689
let result = LOOP_LABELS(return_needed, return_value)
690
691
loops.add(result)
692
693
return result
694
si
695
696
enter_loop(node: Trees.Statements.Statement?) -> LOOP_LABELS is
697
let result = LOOP_LABELS(_next_name)
698
699
result.node = node
700
result.enclosing_try_count = open_try_count
701
702
_next_name = null
703
704
loops.add(result)
705
706
return result
707
si
708
709
find_by_node(node: Trees.Statements.Statement) -> LOOP_LABELS? is
710
let index: int mut = loops.count - 1
711
712
while index >= 0 do
713
let candidate = loops[index]
714
715
if candidate.is_loop /\ candidate.node == node then
716
return candidate
717
fi
718
719
index = index - 1
720
od
721
return null
722
si
723
724
// The innermost enclosing loop that consumes a value — where a
725
// valued `break` delivers its result. Intermediate non-expression
726
// loops are exited through.
727
find_value_loop() -> LOOP_LABELS? is
728
let index: int mut = loops.count - 1
729
730
while index >= 0 do
731
let candidate = loops[index]
732
733
if candidate.is_loop /\ candidate.wants_value then
734
return candidate
735
fi
736
737
index = index - 1
738
od
739
return null
740
si
741
742
leave_loop() is
743
loops.remove_at(loops.count - 1)
744
si
745
si
746
si