Skip to content
← Back

src/syntax/process/generate_il.ghul

1
namespace Syntax.Process is
2
use IO.Std;
3
4
use System.Text.StringBuilder;
5
6
use Logging;
7
use Trees;
8
use Source;
9
10
use IR;
11
use IR.Values;
12
13
use Ghul.Pipes;
14
15
// Holds a generator function whose state-machine class hasn't
16
// been emitted yet — its MoveNext block was filled in while
17
// walking the function's body, but the wrapping `.class` lives
18
// outside the enclosing user class in IL. Flushed by
19
// `emit_pending_state_machines` at the tail of the enclosing
20
// class / namespace visit.
21
class PENDING_STATE_MACHINE is
22
state_machine: Semantic.Symbols.STATE_MACHINE;
23
move_next_block: IR.Values.BLOCK;
24
25
init(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: IR.Values.BLOCK) is
26
self.state_machine = state_machine;
27
self.move_next_block = move_next_block;
28
si
29
si
30
31
// Async sibling of PENDING_STATE_MACHINE. Flushed by
32
// `emit_pending_state_machines` alongside the generator list.
33
class PENDING_ASYNC_STATE_MACHINE is
34
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE;
35
move_next_block: IR.Values.BLOCK;
36
success_label: IR.LABEL;
37
end_label: IR.LABEL;
38
39
init(
40
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE,
41
move_next_block: IR.Values.BLOCK,
42
success_label: IR.LABEL,
43
end_label: IR.LABEL
44
) is
45
self.async_state_machine = async_state_machine;
46
self.move_next_block = move_next_block;
47
self.success_label = success_label;
48
self.end_label = end_label;
49
si
50
si
51
52
// One entry in a dispatch placeholder — the pair an
53
// AWAIT_SUSPEND contributes when it registers with its
54
// enclosing protected region. `state` is the suspension state
55
// number; `cold_resume_label` is the IR.LABEL inside the same
56
// region's `.try` body that GET_RESULT routes through.
57
class ASYNC_DISPATCH_ENTRY is
58
state: int;
59
cold_resume_label: IR.LABEL;
60
61
init(state: int, cold_resume_label: IR.LABEL) is
62
self.state = state;
63
self.cold_resume_label = cold_resume_label;
64
si
65
si
66
67
// IL-emission frame for a `val ... lav` block currently in
68
// scope. Returns inside the body whose val_block_target is this
69
// block push their value and `br` to `end_label`; the natural
70
// fall-through emits the tail value and falls into the same
71
// label. All paths converge at end_label with exactly one value
72
// on the evaluation stack (or empty for a void block). No value
73
// ever lives in a CLR local or frame field at a suspension
74
// point, so async-await and generator-yield compose without
75
// bespoke handling.
76
//
77
// result_type is the resolved block value type from compile-
78
// expressions; void means no value-push. is_void is cached to
79
// skip the lookup at every return emission. enclosing_try_count
80
// is the number of `.try` entries on the loop-label stack at
81
// val-block entry — a return whose current try count exceeds
82
// this opened a try INSIDE the val-block body and needs `leave`
83
// instead of `br` to exit the protected region. value_block is
84
// the captured IR BLOCK used in capture mode; pre(VAL_BLOCK)
85
// eagerly allocates the cross-try TEMP on it before walking
86
// the body, so the `.locals init` lands at the head of value_
87
// block's IL stream rather than inside an inner `.try {}`.
88
class VAL_BLOCK_IL is
89
block: Trees.Expressions.VAL_BLOCK;
90
end_label: IR.LABEL;
91
result_type: Semantic.Types.Type?;
92
is_void: bool;
93
enclosing_try_count: int;
94
value_block: IR.Values.BLOCK?;
95
96
// Non-null when generate-il is spilling this val-block to a
97
// frame field (body contains a state-machine suspend). Val-
98
// targeted returns then `stfld $spill_field` instead of
99
// leaving the value on the stack, so all paths converge at
100
// end_label with the value in the field and an empty stack.
101
spill_field: Semantic.Symbols.Field? public;
102
103
// Capture-mode cross-try state. The TEMP and label must be
104
// allocated BEFORE the body walk so the `.locals init`
105
// appears in value_block's IL stream OUTSIDE any inner
106
// `.try` the body opens — ilasm scopes locals declared
107
// inside `.try {}` to that block, so a load at the post-
108
// body join site can't find them. VAL_BLOCK.pre pre-
109
// allocates both eagerly when value_block has a non-void
110
// result type and is not spilling.
111
//
112
// A val-targeted `return E` inside an inner `.try` can't
113
// `br end_label` (invalid IL across a protected region)
114
// and can't carry its value across `leave` (empty-stack
115
// precondition). Mirrors the function-return-from-try
116
// pattern: stash the value into `cross_try_temp`, `leave
117
// cross_try_join_label`, and at the join `ldloc` the temp
118
// back onto the stack so it falls through into end_label.
119
// `cross_try_used` flags whether the join block needs to
120
// be emitted at all — otherwise the unconditional `br
121
// end_label; join: ldloc; end_label` would push
122
// uninitialised data onto the stack on the natural path's
123
// divergent variants.
124
cross_try_temp: IR.TEMP? public;
125
cross_try_join_label: IR.LABEL? public;
126
cross_try_used: bool public;
127
128
init(
129
block: Trees.Expressions.VAL_BLOCK,
130
end_label: IR.LABEL,
131
result_type: Semantic.Types.Type?,
132
is_void: bool,
133
enclosing_try_count: int,
134
value_block: IR.Values.BLOCK?
135
) is
136
self.block = block;
137
self.end_label = end_label;
138
self.result_type = result_type;
139
self.is_void = is_void;
140
self.enclosing_try_count = enclosing_try_count;
141
self.value_block = value_block;
142
si
143
si
144
145
// Helper for the four composites whose AST value sits in an
146
// `IR.Values.BLOCK` and whose body may suspend (`val ... lav`,
147
// a `Statements.LIST` in expression position, `if` / `case` in
148
// expression position). The natural emission captures body IL
149
// inside the value BLOCK; a consumer later replays it. When the
150
// captured IL contains a state-machine suspend, the replay traps
151
// the suspend's `leave` / state register inside the consumer's
152
// stack setup — invalid IL plus state-loss across MoveNext
153
// re-entry.
154
//
155
// Spill mode breaks the capture: the body emits inline in outer
156
// current_block alongside the dispatcher, each value-producing
157
// path lands in an anonymous frame field via `ldarg.0; v; stfld`,
158
// and the value BLOCK is rewritten to hold just a `ldfld` of the
159
// field. Consumers see a clean load.
160
//
161
// One spiller per composite. `init` decides spill-vs-capture
162
// from the value's BLOCK type, the SM frame, and whether body
163
// contains a suspend outside nested function literals. `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
value: IR.Values.Value?,
179
frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE?,
180
contains_suspend: bool
181
) is
182
_gen = gen;
183
184
if !value? \/ !isa IR.Values.BLOCK(value) then
185
return;
186
fi
187
188
let value_block = cast IR.Values.BLOCK(value);
189
_value_block = value_block;
190
191
if !frame? \/ !contains_suspend then
192
return;
193
fi
194
195
// Void-typed value BLOCK: declare_anonymous_field on a
196
// void type produces invalid IL, and the ldarg.0/v/stfld
197
// triple would itself be bogus. Stay in capture mode —
198
// the void value carries no data across the join.
199
let value_type = value_block.type;
200
201
if value_type.is_void then
202
return;
203
fi
204
205
_frame = frame;
206
_spill_field = frame.declare_anonymous_field("spill", value_type);
207
si
208
209
is_spilling: bool => _spill_field?;
210
211
spill_field: Semantic.Symbols.Field? => _spill_field;
212
213
enter() is
214
if !_value_block? \/ _spill_field? then
215
return;
216
fi
217
_gen.enter_block(_value_block);
218
si
219
220
emit_value(value: IR.Values.Value) is
221
let spill = _spill_field;
222
223
if spill? then
224
_gen.add(IR.RAW("ldarg.0"));
225
_gen.add(value);
226
_gen.add("stfld {spill.get_il_reference()}");
227
else
228
_gen.add(value);
229
fi
230
si
231
232
leave() is
233
if _spill_field? then
234
_value_block!.add(_gen._build_frame_field_load(_frame!, _spill_field));
235
elif _value_block? then
236
_gen.leave_block();
237
fi
238
si
239
si
240
241
// Placeholder block sitting at the top of one protected region's
242
// body (MoveNext's outer try, or any inner let-use / user `.try`).
243
// While the region's body walks, AWAIT_SUSPENDs / YIELDs that
244
// fire inside it append their state-label pair via `register`.
245
// Region close populates `block` with `ldloc V_state; ldc.i4 N;
246
// beq cold_N` for each entry — keeping the dispatch branches
247
// inside the same `.try` as the targets they reach.
248
//
249
// `state_local_il` is the IL spelling of the CLR local cached at
250
// MoveNext entry — `'.async_state'` for async state machines,
251
// `'.gen_state'` for generators. Both nest under the same
252
// dispatch-holder machinery; only the local name differs.
253
class ASYNC_DISPATCH_HOLDER is
254
block: IR.Values.BLOCK;
255
state_local_il: string;
256
_entries: Collections.LIST[ASYNC_DISPATCH_ENTRY];
257
258
init(block: IR.Values.BLOCK, state_local_il: string) is
259
self.block = block;
260
self.state_local_il = state_local_il;
261
_entries = Collections.LIST[ASYNC_DISPATCH_ENTRY]();
262
si
263
264
register(state: int, cold_resume_label: IR.LABEL) is
265
_entries.add(ASYNC_DISPATCH_ENTRY(state, cold_resume_label));
266
si
267
268
entries: Collections.Iterable[ASYNC_DISPATCH_ENTRY] => _entries;
269
si
270
271
class GENERATE_IL: ScopedVisitor is
272
_logger: Logger;
273
_symbol_table: Semantic.SYMBOL_TABLE;
274
_int_type: Semantic.Types.NAMED;
275
_symbol_loader: Semantic.SYMBOL_LOADER;
276
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup;
277
_function_caller: Semantic.FUNCTION_CALLER;
278
_type_caster: Semantic.TYPE_CASTER;
279
_overload_resolver: Semantic.OVERLOAD_RESOLVER;
280
_symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS;
281
_build_flags: Compiler.GLOBAL_BUILD_FLAGS;
282
283
_context: CONTEXT;
284
_brancher: BRANCHER;
285
_boilerplate_generator: BOILERPLATE_GENERATOR;
286
_boxer: VALUE_BOXER;
287
_loops: LOOP_LABEL_STACK;
288
_block_context: BlockContext;
289
290
_il_output_depth: int;
291
_in_catch_variable: bool;
292
293
_indent: int;
294
_depth: int;
295
_run_on: bool;
296
_indent_needed: bool;
297
298
_interpolation_handler: Semantic.Types.Type?;
299
_constructor: Semantic.Symbols.Function;
300
_append_literal: Semantic.Symbols.Function;
301
_append_formatted_generic: Semantic.Symbols.Function;
302
_append_formatted_generic_alignment: Semantic.Symbols.Function;
303
_append_formatted_generic_format: Semantic.Symbols.Function;
304
_append_formatted_generic_alignment_format: Semantic.Symbols.Function;
305
_to_string_and_clear: Semantic.Symbols.Function;
306
_dispose: Semantic.Symbols.Function;
307
308
// Generators whose state-machine class IL emission has been
309
// deferred until the enclosing class/namespace closes. The
310
// outer function method is emitted inline; the state-machine
311
// class needs to land as a SIBLING of the user's class, not
312
// inside it (CIL has no nested-class-in-method form). Mirrors
313
// the existing closure-FRAME emission pattern (see
314
// gen_closures called from visit(`class)).
315
_pending_state_machines: Collections.LIST[PENDING_STATE_MACHINE];
316
_pending_async_state_machines: Collections.LIST[PENDING_ASYNC_STATE_MACHINE];
317
318
// Per-async-function emission context, set/cleared by
319
// `_pre_async_function`. Visit handlers for AWAIT / RETURN
320
// check `current_function == _current_async_state_machine
321
// .function` so a nested closure's own visit doesn't fire
322
// the outer function's async paths.
323
_current_async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE?;
324
_current_async_success_label: IR.LABEL?;
325
_current_async_end_label: IR.LABEL?;
326
327
// Leave target for yields inside a `.try` body. CIL forbids
328
// `ret` from inside a protected region; yields that suspend
329
// from within an enclosing `.try` `leave` to this label, which
330
// sits outside any try and just emits `ldc.i4.1; ret`.
331
// Lazy: yield's emission sets this on first use; the generator
332
// trailer emits the label + ret only if it was actually used.
333
_current_generator_yield_return_label: IR.LABEL?;
334
335
// Stack of dispatch placeholders for the currently-open
336
// protected regions (outermost is the MoveNext body itself).
337
// Pushed when entering a `.try` body, popped on close. Each
338
// AWAIT_SUSPEND that fires while a holder is active registers
339
// its (state, cold_resume_label) pair with the top holder.
340
// At close-of-region the holder's BLOCK is populated with
341
// `ldloc V_state; ldc.i4 N; beq cold_resume_N` for each
342
// registered await — so the `beq` and the target label live
343
// in the same `.try`, never crossing a region boundary.
344
_async_dispatch_stack: Collections.LIST[ASYNC_DISPATCH_HOLDER];
345
346
// Stack of `val ... lav` IL frames, parallel to the
347
// val_block_target stamped on RETURN nodes by compile-
348
// expressions. Each entry holds the block's end_label, the
349
// resolved result_type, the open-`.try` count cached at
350
// val-block entry, and (in capture mode) the eagerly-
351
// allocated cross-try TEMP and join label. visit(RETURN)
352
// reads the top via the node's val_block_target identity
353
// and dispatches to one of three shapes: spill mode stfld's
354
// into the frame field then leave/br end_label; capture
355
// mode with no inner try pushes the value and `br`s
356
// end_label; capture mode crossing an inner try stashes the
357
// value into the TEMP and `leave`s the join label, where
358
// a post-body `ldloc` joins it back onto end_label's stack.
359
_val_block_il_stack: Collections.LIST[VAL_BLOCK_IL];
360
361
current_block: Values.BLOCK => _block_context.current_block;
362
363
init(
364
logger: Logger,
365
symbol_table: Semantic.SYMBOL_TABLE,
366
namespaces: Semantic.NAMESPACES,
367
symbol_loader: Semantic.SYMBOL_LOADER,
368
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
369
function_caller: Semantic.FUNCTION_CALLER,
370
type_caster: Semantic.TYPE_CASTER,
371
overload_resolver: Semantic.OVERLOAD_RESOLVER,
372
symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS,
373
context: CONTEXT,
374
block_context: BlockContext,
375
brancher: BRANCHER,
376
boilerplate_generator: BOILERPLATE_GENERATOR,
377
boxer: VALUE_BOXER,
378
build_flags: Compiler.GLOBAL_BUILD_FLAGS
379
)
380
is
381
super.init(logger, symbol_table, namespaces);
382
383
_globals_carriers_marked = Collections.SET[string]();
384
385
_logger = logger;
386
_symbol_table = symbol_table;
387
_symbol_loader = symbol_loader;
388
_innate_symbol_lookup = innate_symbol_lookup;
389
_function_caller = function_caller;
390
_type_caster = type_caster;
391
_overload_resolver = overload_resolver;
392
_symbol_use_locations = symbol_use_locations;
393
_context = context;
394
_block_context = block_context;
395
_brancher = brancher;
396
_boilerplate_generator = boilerplate_generator;
397
_boxer = boxer;
398
_build_flags = build_flags;
399
400
_loops = LOOP_LABEL_STACK();
401
_pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE]();
402
_pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE]();
403
_async_dispatch_stack = Collections.LIST[ASYNC_DISPATCH_HOLDER]();
404
_val_block_il_stack = Collections.LIST[VAL_BLOCK_IL]();
405
si
406
407
ensure_runtime_symbols_are_materialized() is
408
if _interpolation_handler? then
409
return;
410
fi
411
412
let string_type = _innate_symbol_lookup.get_string_type();
413
let int_type = _innate_symbol_lookup.get_int_type();
414
415
416
// Materialize IDisposable.Dispose()
417
let idisposable_type = _innate_symbol_lookup.get_idisposable_type();
418
419
// there is only one overload of dispose:
420
_dispose = cast Semantic.Symbols.FUNCTION_GROUP?(idisposable_type.find_member("dispose"))!.functions[0];
421
422
// Materialize the various methods of the string interpolation handler:
423
let interpolation_handler = _innate_symbol_lookup.get_interpolated_string_handler_type();
424
_interpolation_handler = interpolation_handler;
425
426
// the constructor overload we want is the only one with 2 arguments:
427
_constructor = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("init"))!.functions |>
428
filter(f => f.arguments.count == 2) |>
429
only();
430
431
// there is only one overload of append_literal:
432
_append_literal = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_literal"))!.functions[0];
433
434
// the first overload of append_formatted we want is the only one that is both generic and with 1 argument:
435
_append_formatted_generic = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
436
filter(f => f.is_generic /\ f.arguments.count == 1) |>
437
only();
438
439
// there are two overloads of append_formatted with 2 arguments, we want the one where the second argument is an int:
440
_append_formatted_generic_alignment = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
441
filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(int_type) == Semantic.Types.MATCH.SAME) |>
442
only();
443
444
// there are two overloads of append_formatted with 2 arguments, we want the one where the second argument is a string:
445
// (BCL declares the `format` parameter as `string?`, so SAME no longer matches under directional compare;
446
// != DIFFERENT accepts both `string` and `string?` shapes.)
447
_append_formatted_generic_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
448
filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(string_type) != Semantic.Types.MATCH.DIFFERENT) |>
449
only();
450
451
// there is only one overload of append_formatted with 3 arguments:
452
_append_formatted_generic_alignment_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |>
453
filter(f => f.is_generic /\ f.arguments.count == 3) |>
454
only();
455
456
// there is only one overload of to_string_and_clear:
457
_to_string_and_clear = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("to_string_and_clear"))!.functions[0];
458
si
459
460
apply(root: Trees.Node) is
461
if _logger.any_errors then
462
return;
463
fi
464
465
LABEL.set_pass("_");
466
467
root.walk(self);
468
si
469
470
// Mirrors compile_expressions.ghul's enter_node/leave_node.
471
// pre(Statements.LIST) takes over the statement walk, so the
472
// location bracketing has to be re-applied by hand around
473
// each statement.
474
enter_node(node: Trees.Node) is
475
if !_build_flags.want_debug then
476
return;
477
fi
478
IoC.CONTAINER.instance.location_stack.push(node.location);
479
si
480
481
leave_node(node: Trees.Node) is
482
if !_build_flags.want_debug then
483
return;
484
fi
485
IoC.CONTAINER.instance.location_stack.pop();
486
si
487
488
enter_block(block: Values.BLOCK) is
489
_block_context.enter_block(block);
490
si
491
492
enter_block() is
493
_block_context.enter_block();
494
si
495
496
leave_block() is
497
_block_context.leave_block();
498
si
499
500
get_brancher_for_block() -> BLOCK_BRANCHER =>
501
_brancher.get_for(_block_context.current_block);
502
503
add(value: Values.Value) is
504
current_block.add(value);
505
si
506
507
add(raw: string) is
508
current_block.add(raw);
509
si
510
511
add(type: Semantic.Types.Type, raw: string) is
512
current_block.add(type, raw);
513
si
514
515
println(value: object) is
516
_context.write_line(value);
517
si
518
519
println(value: object, comment: object) is
520
_context.write_line(value, comment);
521
si
522
523
println_comment(value: object) is
524
_context.write_comment_line(value);
525
si
526
527
enter_class(symbol: Semantic.Symbols.Classy) is
528
ADDRESS.reset_id();
529
TEMP.reset_id();
530
LABEL.reset_id();
531
si
532
533
leave_class(symbol: Semantic.Symbols.Classy) is
534
si
535
536
print_class_def(symbol: Semantic.Symbols.Symbol) is
537
println_comment("define {symbol.short_description}");
538
539
let buffer = System.Text.StringBuilder();
540
symbol.gen_definition_header(buffer);
541
542
println(buffer);
543
544
println("{{");
545
si
546
547
// Namespaces whose carrier has already carried the marker attribute in
548
// this compilation. ilasm merges the repeated `.class` blocks for one
549
// name, so the attribute is emitted on the first block only.
550
_globals_carriers_marked: Collections.SET[string];
551
552
// Open/close a `.class 'NS'.'$globals' { ... }` wrapper around a single
553
// global function/property/variable definition. Multiple wrappers for
554
// the same class get merged by ilasm (partial class definitions).
555
gen_globals_class_open(`namespace: Semantic.Symbols.NAMESPACE) is
556
let header = System.Text.StringBuilder();
557
header.append(".class public abstract auto ansi sealed ");
558
`namespace.gen_globals_class_reference(header, null);
559
header.append(" extends [System.Runtime]System.Object");
560
561
println(header);
562
println("{{");
563
_context.indent();
564
565
if !_globals_carriers_marked.contains(`namespace.qualified_name) then
566
_globals_carriers_marked.add(`namespace.qualified_name);
567
568
println(".custom instance void ['ghul-runtime']{Semantic.DotNet.GLOBALS_CARRIER.attribute_name}::.ctor() = ( 01 00 00 00 )");
569
fi
570
si
571
572
gen_globals_class_close() is
573
_context.outdent();
574
println("}}");
575
si
576
577
pre(`namespace: Definitions.NAMESPACE) -> bool => super.pre(`namespace);
578
579
visit(`namespace: Definitions.NAMESPACE) is
580
let context = _symbol_table.current_closure_context;
581
582
gen_anon_functions(context.get_closures());
583
gen_closures(context.get_closures());
584
585
emit_pending_state_machines();
586
587
super.visit(`namespace);
588
si
589
590
pre(`class: Definitions.CLASS) -> bool is
591
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`class))!;
592
593
enter_class(symbol);
594
595
let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!;
596
597
print_class_def(symbol);
598
599
enter_scope(`class);
600
601
_context.indent();
602
603
// Closed-to-assembly ghūl classes carry a CLOSED_ATTRIBUTE
604
// marker so that consumers in other assemblies can recognise
605
// the class as closed during import — the type system uses
606
// this to refuse cross-assembly extension and to treat the
607
// subclass set as enumerable for narrowing.
608
if let class_symbol: Semantic.Symbols.CLASS = symbol then
609
if !class_symbol.is_open then
610
println(".custom instance void ['ghul-runtime']Ghul.Internal.CLOSED_ATTRIBUTE::.ctor() = ( 01 00 00 00 )");
611
fi
612
fi
613
614
gen_custom_attributes(symbol);
615
616
return false;
617
si
618
619
// Every attribute a function definition carries, in the one
620
// order ILASM accepts: a `.custom` binds to the target named by
621
// the most recent `.param [N]` directive, so each method-level
622
// attribute has to be written before any per-slot directive or
623
// it silently lands on that parameter instead of on the method.
624
gen_function_attributes(symbol: Semantic.Symbols.Symbol) is
625
gen_purity_attributes(symbol);
626
gen_custom_attributes(symbol);
627
628
gen_tuple_element_name_attributes(symbol);
629
gen_nullability_attributes(symbol);
630
gen_purity_param_attributes(symbol);
631
632
if let function: Semantic.Symbols.Function = symbol then
633
function.gen_argument_custom_attributes(_context);
634
fi
635
si
636
637
// Emit a `.custom` directive for each attribute pragma resolved
638
// onto this symbol. The caller has already opened the type / method
639
// and indented, so the directive lands inside it.
640
gen_custom_attributes(symbol: Semantic.Symbols.Symbol) is
641
if !symbol.custom_attributes? then
642
return;
643
fi
644
645
for attribute in symbol.custom_attributes do
646
println(attribute.gen_il_line());
647
od
648
si
649
650
visit(`class: Definitions.CLASS) is
651
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`class))!;
652
653
let closures = symbol.get_closures();
654
655
gen_delegates(closures);
656
657
gen_ienumerable_boilerplate(symbol);
658
gen_ienumerator_boilerplate(symbol);
659
660
_context.outdent();
661
println("}}");
662
663
leave_class(symbol);
664
665
leave_scope(`class);
666
667
gen_anon_functions(closures);
668
gen_closures(closures);
669
670
emit_pending_state_machines();
671
si
672
673
// A `partial` block emits its members inside a fresh `.class Target { … }`
674
// wrapper; ilasm merges same-named class blocks into one type. The class
675
// header (extends/implements/attributes) is the primary definition's job,
676
// so this only re-opens the type for its members; closures declared here
677
// are emitted by visit(partial), after their bodies are walked.
678
pre(`partial: Definitions.PARTIAL) -> bool is
679
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`partial))!;
680
681
enter_class(symbol);
682
683
print_class_def(symbol);
684
685
enter_scope(`partial);
686
687
_context.indent();
688
689
return false;
690
si
691
692
visit(`partial: Definitions.PARTIAL) is
693
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`partial))!;
694
695
// Closures declared in this block are tracked on its injection
696
// scope and emitted here, after the member walk has generated
697
// their bodies - the target's primary definition runs before
698
// this block is walked and would emit them bodyless.
699
let injection = cast Semantic.INJECTION_SCOPE?(scope_for(`partial));
700
let closures = injection?.get_closures();
701
702
gen_delegates(closures);
703
704
_context.outdent();
705
println("}}");
706
707
leave_class(symbol);
708
709
leave_scope(`partial);
710
711
gen_anon_functions(closures);
712
gen_closures(closures);
713
714
emit_pending_state_machines();
715
si
716
717
// An `impl` block emits its members inside a fresh `.class Target { … }`
718
// wrapper, exactly like `partial`; ilasm merges same-named class blocks.
719
// The interface the target now implements is carried on the target
720
// symbol, so its `implements` clause is emitted by the primary definition.
721
pre(`impl: Definitions.IMPL) -> bool is
722
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`impl))!;
723
724
enter_class(symbol);
725
726
print_class_def(symbol);
727
728
enter_scope(`impl);
729
730
_context.indent();
731
732
return false;
733
si
734
735
visit(`impl: Definitions.IMPL) is
736
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`impl))!;
737
738
// As visit(partial): closures declared in this block are tracked
739
// on its injection scope and emitted here, after the member walk
740
// has generated their bodies.
741
let injection = cast Semantic.INJECTION_SCOPE?(scope_for(`impl));
742
let closures = injection?.get_closures();
743
744
gen_delegates(closures);
745
746
_context.outdent();
747
println("}}");
748
749
leave_class(symbol);
750
751
leave_scope(`impl);
752
753
gen_anon_functions(closures);
754
gen_closures(closures);
755
756
emit_pending_state_machines();
757
si
758
759
pre(`trait: Definitions.TRAIT) -> bool is
760
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`trait))!;
761
let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!;
762
763
enter_class(symbol);
764
765
print_class_def(symbol);
766
767
enter_scope(`trait);
768
769
_context.indent();
770
771
gen_custom_attributes(symbol);
772
773
return false;
774
si
775
776
visit(`trait: Definitions.TRAIT) is
777
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`trait))!;
778
779
let closures = symbol.get_closures();
780
781
gen_delegates(closures);
782
783
_context.outdent();
784
println("}}");
785
786
leave_class(symbol);
787
788
leave_scope(`trait);
789
790
gen_anon_functions(closures);
791
gen_closures(closures);
792
793
emit_pending_state_machines();
794
si
795
796
pre(`struct: Definitions.STRUCT) -> bool is
797
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`struct))!;
798
let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!;
799
800
enter_class(symbol);
801
802
print_class_def(symbol);
803
804
_context.indent();
805
806
enter_scope(`struct);
807
808
gen_custom_attributes(symbol);
809
810
return false;
811
si
812
813
visit(`struct: Definitions.STRUCT) is
814
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`struct))!;
815
816
let closures = symbol.get_closures();
817
818
gen_ienumerable_boilerplate(symbol);
819
gen_ienumerator_boilerplate(symbol);
820
gen_anon_functions(closures);
821
822
_context.outdent();
823
println("}}");
824
825
leave_class(symbol);
826
827
leave_scope(`struct);
828
829
gen_closures(closures);
830
si
831
832
pre(`union: Definitions.UNION) -> bool is
833
let symbol = cast Semantic.Symbols.Classy?(symbol_for(`union))!;
834
835
enter_class(symbol);
836
837
let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!;
838
839
print_class_def(symbol);
840
841
enter_scope(`union);
842
_context.indent();
843
844
println(".custom instance void ['ghul-runtime']Ghul.Internal.UNION_ATTRIBUTE::.ctor() = ( 01 00 00 00 )");
845
846
gen_custom_attributes(symbol);
847
848
`union.name.walk(self);
849
850
if `union.arguments? then
851
`union.arguments.walk(self);
852
fi
853
854
`union.modifiers.walk(self);
855
856
for member in `union.body |> filter(m => !isa Definitions.VARIANT(m)) do
857
member.walk(self);
858
od
859
860
_context.indent();
861
862
gen_ienumerable_boilerplate(symbol);
863
gen_ienumerator_boilerplate(symbol);
864
865
_context.outdent();
866
println("}}");
867
868
println("// variants for {`union.name}");
869
870
_context.indent();
871
872
leave_class(symbol);
873
874
leave_scope(`union);
875
876
for member in `union.body |> filter(m => isa Definitions.VARIANT(m)) do
877
member.walk(self);
878
od
879
880
_context.outdent();
881
882
return true;
883
si
884
885
visit(`union: Definitions.UNION) is
886
// all done in pre
887
si
888
889
pre(variant: Definitions.VARIANT) -> bool is
890
let symbol = cast Semantic.Symbols.Classy?(symbol_for(variant))!;
891
892
enter_class(symbol);
893
894
let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!;
895
896
print_class_def(symbol);
897
898
enter_scope(variant);
899
900
_context.indent();
901
902
println(".custom instance void ['ghul-runtime']Ghul.Internal.VARIANT_ATTRIBUTE::.ctor() = ( 01 00 00 00 )");
903
904
// Mark the union's default variant so cross-assembly
905
// consumers can lower `?`/`!` to the same isa/cast as
906
// source-defined unions. `SYMBOL_FACTORY.materialize_variant`
907
// reads this back on import and sets `default_variant`
908
// on the parent union symbol. Uses the union's
909
// `default_variant` pointer (set in declare_symbols),
910
// so implicit defaults — the sole-non-unit-variant
911
// fallback — get marked too.
912
if let variant_symbol: Semantic.Symbols.VARIANT = symbol then
913
if let owner_union: Semantic.Symbols.UNION = variant_symbol.owner then
914
if owner_union.default_variant == variant_symbol then
915
println(".custom instance void ['ghul-runtime']Ghul.Internal.DEFAULT_VARIANT_ATTRIBUTE::.ctor() = ( 01 00 00 00 )");
916
fi
917
fi
918
fi
919
920
gen_custom_attributes(symbol);
921
922
return false;
923
si
924
925
visit(variant: Definitions.VARIANT) is
926
let symbol = cast Semantic.Symbols.Classy?(symbol_for(variant))!;
927
928
let closures = symbol.get_closures();
929
930
gen_ienumerable_boilerplate(symbol);
931
gen_ienumerator_boilerplate(symbol);
932
gen_unit_variant_singleton(symbol);
933
gen_anon_functions(closures);
934
935
_context.outdent();
936
println("}}");
937
938
leave_class(symbol);
939
940
leave_scope(variant);
941
942
gen_closures(closures);
943
si
944
945
// For a unit variant (no constructor parameters / no
946
// instance fields), emit a static `_instance` field plus a
947
// `.cctor` that allocates exactly one instance. `IR.Values.NEW`
948
// redirects construction sites for unit variants to a `ldsfld`
949
// of this field, so `RED()` (or `NONE[int]()`) returns the
950
// cached singleton instead of allocating each call.
951
gen_unit_variant_singleton(symbol: Semantic.Symbols.Classy) is
952
if let variant_symbol: Semantic.Symbols.VARIANT = symbol then
953
if !variant_symbol.is_unit_variant then
954
return;
955
fi
956
957
let ref_buffer = StringBuilder();
958
variant_symbol.gen_reference(ref_buffer);
959
let variant_ref = ref_buffer.to_string();
960
961
println(".field public static {variant_ref}'_instance'");
962
println(".method private hidebysig specialname rtspecialname static default void '.cctor'() cil managed");
963
println("{{");
964
_context.indent();
965
println(".maxstack 1");
966
println("newobj instance void {variant_ref}::'.ctor'()");
967
println("stsfld {variant_ref}{variant_ref}::'_instance'");
968
println("ret");
969
_context.outdent();
970
println("}}");
971
fi
972
si
973
974
gen_ienumerable_boilerplate(symbol: Semantic.Symbols.Classy) is
975
if symbol.is_derived_from_iterable_trait then
976
// FIXME: check if class implements these methods and don't emit them if they're not needed:
977
978
_boilerplate_generator.gen("ienumerable-boilerplate");
979
fi
980
si
981
982
gen_ienumerator_boilerplate(symbol: Semantic.Symbols.Classy) is
983
if symbol.is_derived_from_iterator_trait then
984
// FIXME: check if class implements these methods and don't emit them if they're not needed:
985
986
_boilerplate_generator.gen("ienumerator-boilerplate");
987
fi
988
si
989
990
gen_delegates(closures: Collections.Iterable[Semantic.Symbols.Closure]?) is
991
if !closures? then
992
return;
993
fi
994
995
for anon_function in closures |> filter(c => c.is_delegate) do
996
let buffer = StringBuilder();
997
anon_function.gen_definition_header(buffer);
998
_context.write_line(buffer);
999
1000
_context.write_line("{{");
1001
_context.indent();
1002
1003
anon_function.gen_argument_custom_attributes(_context);
1004
1005
_context.write_line(anon_function.il_body);
1006
1007
_context.outdent();
1008
_context.write_line("}}");
1009
1010
gen_delegate_cache_field(anon_function);
1011
od
1012
si
1013
1014
1015
gen_anon_functions(closures: Collections.Iterable[Semantic.Symbols.Closure]?) is
1016
if !closures? then
1017
return;
1018
fi
1019
1020
for anon_function in closures |> filter(c => c.is_anon_func) do
1021
let buffer = StringBuilder();
1022
anon_function.gen_definition_header(buffer);
1023
_context.write_line(buffer);
1024
1025
_context.write_line("{{");
1026
_context.indent();
1027
1028
anon_function.gen_argument_custom_attributes(_context);
1029
1030
_context.write_line(anon_function.il_body);
1031
1032
_context.outdent();
1033
_context.write_line("}}");
1034
1035
gen_delegate_cache_field(anon_function);
1036
od
1037
si
1038
1039
// Emit the static cache field backing a memoized stateless
1040
// delegate. A static/global anonymous function caches on the
1041
// enclosing namespace's `$globals` class, emitted in its own
1042
// `$globals` wrapper (ilasm merges them); a stateless instance-
1043
// context delegate caches on its enclosing class, whose block is
1044
// already open at the call site, so the field is emitted directly.
1045
// Keyed on the load site having actually referenced the field, so
1046
// definition and frozen load-site IL can't disagree.
1047
gen_delegate_cache_field(closure: Semantic.Symbols.Closure) is
1048
if !closure.has_delegate_cache_field then
1049
return;
1050
fi
1051
1052
let `field = closure.delegate_cache_field;
1053
1054
let field_definition = StringBuilder();
1055
`field.gen_definition_header(field_definition);
1056
1057
if let owner_namespace: Semantic.Symbols.NAMESPACE = `field.owner then
1058
gen_globals_class_open(owner_namespace);
1059
_context.write_line(field_definition);
1060
gen_globals_class_close();
1061
else
1062
_context.write_line(field_definition);
1063
fi
1064
si
1065
1066
gen_closures(closures: Collections.Iterable[Semantic.Symbols.Closure]?) is
1067
if !closures? then
1068
return;
1069
fi
1070
1071
for closure in closures |> filter(c => !c.is_anon_func /\ !c.is_delegate) do
1072
closure.gen_frame(_context, _symbol_loader);
1073
od
1074
si
1075
1076
pre(`enum: Definitions.ENUM) -> bool is
1077
let symbol = cast Semantic.Symbols.Symbol?(symbol_for(`enum))!;
1078
1079
let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!;
1080
1081
print_class_def(symbol);
1082
1083
enter_scope(`enum);
1084
1085
_context.indent();
1086
1087
gen_custom_attributes(symbol);
1088
1089
_context.write_line(".field public specialname rtspecialname int32 value__ ");
1090
return false;
1091
si
1092
1093
visit(`enum: Definitions.ENUM) is
1094
_context.outdent();
1095
println("}}");
1096
1097
leave_scope(`enum);
1098
si
1099
1100
pre(enum_member: Definitions.ENUM_MEMBER) -> bool is
1101
let symbol = current_scope.find_member(enum_member.name.name);
1102
1103
let buffer = System.Text.StringBuilder();
1104
symbol!.gen_definition_header(buffer);
1105
println(buffer);
1106
return false;
1107
si
1108
1109
pre(function: Definitions.FUNCTION) -> bool is
1110
let symbol = function_for(function)!;
1111
1112
// Generator branch: a `*_GENERATOR_*` function expands
1113
// into TWO IL methods (the outer stub returning a fresh
1114
// state-machine instance + the state machine class with
1115
// its MoveNext / get_Current / GetEnumerator / Dispose /
1116
// Reset implementations). The split is handled by the
1117
// gen-generator-function path so the rest of generate_il
1118
// doesn't need to know.
1119
let state_machine = Semantic.Symbols.state_machine_for(cast Semantic.Symbols.Function?(symbol)!);
1120
1121
if state_machine? then
1122
return _pre_generator_function(function, cast Semantic.Symbols.Function?(symbol)!, state_machine);
1123
fi
1124
1125
// Async branch (parallel to the generator branch above).
1126
let async_sm = Semantic.Symbols.async_state_machine_for(cast Semantic.Symbols.Function?(symbol)!);
1127
1128
if async_sm? then
1129
return _pre_async_function(function, cast Semantic.Symbols.Function?(symbol)!, async_sm);
1130
fi
1131
1132
if let global_function: Semantic.Symbols.GLOBAL_FUNCTION = symbol then
1133
gen_globals_class_open(cast Semantic.Symbols.NAMESPACE?(global_function.owner)!);
1134
fi
1135
1136
println_comment("define {symbol.description}");
1137
1138
let buffer = System.Text.StringBuilder();
1139
symbol.gen_definition_header(buffer);
1140
println(buffer);
1141
1142
println("{{");
1143
_context.indent();
1144
1145
gen_function_attributes(symbol);
1146
1147
enter_scope(function);
1148
1149
symbol.gen_body_header(_context);
1150
1151
enter_block();
1152
return false;
1153
si
1154
1155
// Emit TupleElementNamesAttribute on the return and on any
1156
// parameter whose type is a flat named tuple, so a C# or ghūl
1157
// consumer recovers the element names — the ValueTuple type
1158
// alone carries none.
1159
gen_tuple_element_name_attributes(symbol: Semantic.Symbols.Symbol) is
1160
let function = cast Semantic.Symbols.Function?(symbol);
1161
1162
if !function? then
1163
return;
1164
fi
1165
1166
gen_tuple_element_name_attribute(function.return_type!, 0);
1167
1168
let arguments = function.arguments;
1169
1170
for i in 0..arguments.count do
1171
gen_tuple_element_name_attribute(arguments[i], i + 1);
1172
od
1173
si
1174
1175
gen_tuple_element_name_attribute(type: Semantic.Types.Type, parameter_index: int) is
1176
let line = Semantic.DotNet.TUPLE_ELEMENT_NAMES.gen_attribute_line_for_type(type);
1177
1178
if !line? then
1179
return;
1180
fi
1181
1182
_context.write_line(".param [{parameter_index}]");
1183
_context.write_line(line);
1184
si
1185
1186
// Emit `NullableAttribute` on the return slot and on any
1187
// parameter whose tree carries a reference-`?` position, so
1188
// the annotation survives cross-assembly reflection. The CLR
1189
// has no native carrier for reference nullability — `Foo`
1190
// and `Foo?` are the same IL type — so the marker rides on a
1191
// per-slot custom attribute. ILASM's `.param [N]` attaches
1192
// the attribute to parameter `N`; `[0]` is the return. The
1193
// attribute's argument is a single byte for a one-position
1194
// slot, or a byte[] walking the type tree pre-order for
1195
// nested cases (`List[string?]` -> `[1, 2]`).
1196
gen_nullability_attributes(symbol: Semantic.Symbols.Symbol) is
1197
let function = cast Semantic.Symbols.Function?(symbol);
1198
1199
if !function? then
1200
return;
1201
fi
1202
1203
gen_nullability_param_attribute(function.return_type!, 0);
1204
1205
let arguments = function.arguments;
1206
1207
for i in 0..arguments.count do
1208
gen_nullability_param_attribute(arguments[i], i + 1);
1209
od
1210
si
1211
1212
gen_nullability_param_attribute(type: Semantic.Types.Type, parameter_index: int) is
1213
let line = Semantic.DotNet.NULLABILITY.gen_attribute_line_for_type(type);
1214
1215
if !line? then
1216
return;
1217
fi
1218
1219
_context.write_line(".param [{parameter_index}]");
1220
_context.write_line(" {line}");
1221
si
1222
1223
// Purity round-trip: the marker on the method carries the
1224
// declared-pure contract to importing assemblies; the marker
1225
// on a parameter or return slot marks that slot's top-level
1226
// function type pure. Nested pure function types do not
1227
// round-trip — a slot whose pureness sits inside a generic
1228
// argument imports as the plain shape, which is conservative
1229
// on both sides.
1230
gen_purity_attributes(symbol: Semantic.Symbols.Symbol) is
1231
let function = cast Semantic.Symbols.Function?(symbol);
1232
1233
if !function? then
1234
return;
1235
fi
1236
1237
if function.is_declared_pure then
1238
_context.write_line(".custom instance void ['ghul-runtime']Ghul.Internal.PURE_ATTRIBUTE::.ctor() = ( 01 00 00 00 )");
1239
fi
1240
si
1241
1242
gen_purity_param_attributes(symbol: Semantic.Symbols.Symbol) is
1243
let function = cast Semantic.Symbols.Function?(symbol);
1244
1245
if !function? then
1246
return;
1247
fi
1248
1249
gen_pure_param_attribute(function.return_type, 0);
1250
1251
let arguments = function.arguments;
1252
1253
for i in 0..arguments.count do
1254
gen_pure_param_attribute(arguments[i], i + 1);
1255
od
1256
si
1257
1258
gen_pure_param_attribute(type: Semantic.Types.Type?, parameter_index: int) is
1259
if !type? \/ !type.is_pure_function then
1260
return;
1261
fi
1262
1263
_context.write_line(".param [{parameter_index}]");
1264
_context.write_line(".custom instance void ['ghul-runtime']Ghul.Internal.PURE_ATTRIBUTE::.ctor() = ( 01 00 00 00 )");
1265
si
1266
1267
visit(function: Definitions.FUNCTION) is
1268
let symbol = symbol_for(function);
1269
1270
let state_machine = Semantic.Symbols.state_machine_for(cast Semantic.Symbols.Function?(symbol)!);
1271
1272
if state_machine? then
1273
_visit_generator_function(function, cast Semantic.Symbols.Function?(symbol)!, state_machine);
1274
1275
return;
1276
fi
1277
1278
let async_sm = Semantic.Symbols.async_state_machine_for(cast Semantic.Symbols.Function?(symbol)!);
1279
1280
if async_sm? then
1281
// _pre_async_function already emitted the outer
1282
// method and stashed the move_next block onto
1283
// _pending_async_state_machines. Nothing else to
1284
// do at AST visit time.
1285
return;
1286
fi
1287
1288
_context.reset_line_tracking();
1289
current_block.gen(_context);
1290
leave_block();
1291
1292
if symbol? /\ isa Semantic.Symbols.Function(symbol) then
1293
let function_symbol = symbol;
1294
1295
if !function_symbol.is_abstract then
1296
if function.body? /\ isa Bodies.NULL(function.body) then
1297
_context.write_line("newobj instance void class [System.Runtime]System.NotImplementedException::'.ctor'()");
1298
_context.write_line("throw");
1299
elif
1300
function_symbol.return_type? /\
1301
function_symbol.return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME
1302
then
1303
_context.write_line(".locals init ({function_symbol.return_type!.get_il_type()} '.default')");
1304
_context.write_line("ldloc '.default'");
1305
fi
1306
1307
_context.write_line("ret");
1308
fi
1309
fi
1310
1311
leave_scope(function);
1312
1313
_context.outdent();
1314
println("}}");
1315
1316
if isa Semantic.Symbols.GLOBAL_FUNCTION(symbol) then
1317
gen_globals_class_close();
1318
fi
1319
si
1320
1321
// Generator IL emission:
1322
//
1323
// 1. The outer user-facing method gets a stub body
1324
// (`newobj $StateMachine.ctor(); ret`) emitted inline,
1325
// so the AST visit order is unchanged for the caller.
1326
// 2. The function body is walked here (recursively, via
1327
// self) into a fresh IR.Values.BLOCK that represents
1328
// MoveNext's body. Yield statements emit their state-
1329
// machine IL into this block as they are walked.
1330
// 3. The state-machine class itself is deferred — captured
1331
// in `_pending_state_machines` and emitted at the SAME
1332
// level as the user's class (after `visit(`class)`
1333
// closes the user class), since CIL doesn't allow
1334
// classes nested inside method bodies.
1335
_pre_generator_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, state_machine: Semantic.Symbols.STATE_MACHINE) -> bool is
1336
let frame = state_machine.frame;
1337
1338
if !frame? then
1339
_logger.error(function.location, "generator must return Pipe[T]");
1340
1341
return true;
1342
fi
1343
1344
// Realise the frame's symbol-level declarations (fields
1345
// + ctor) once. Required for both the outer method's
1346
// newobj reference and the state-machine class itself.
1347
frame.declare();
1348
1349
let owner_is_global = isa Semantic.Symbols.GLOBAL_FUNCTION(symbol);
1350
1351
if owner_is_global then
1352
gen_globals_class_open(cast Semantic.Symbols.NAMESPACE?(symbol.owner)!);
1353
fi
1354
1355
// --- Outer user-facing method ---
1356
println_comment("define {symbol.description}");
1357
1358
let outer_buffer = StringBuilder();
1359
symbol.gen_definition_header(outer_buffer);
1360
println(outer_buffer);
1361
1362
println("{{");
1363
_context.indent();
1364
1365
gen_function_attributes(symbol);
1366
1367
symbol.gen_body_header(_context);
1368
1369
_context.write_line(".maxstack 64");
1370
1371
// Instance generators pass `this` as the first ctor
1372
// argument; the .ctor stores it into _outer_self so the
1373
// body's `self`/instance-member access can read through
1374
// the frame.
1375
if frame.outer_self_field? then
1376
_context.write_line("ldarg.0");
1377
fi
1378
1379
// Load each user-declared parameter and pass it through
1380
// to the state-machine .ctor. The frame's declare() ran
1381
// above so the ctor's signature already matches.
1382
for arg_name in symbol.argument_names do
1383
_context.write_line("ldarg '{arg_name}'");
1384
od
1385
1386
let ctor_reference = _build_specialized_ctor_reference(
1387
frame,
1388
frame.constructor,
1389
state_machine.get_construction_type_arguments(),
1390
symbol.location,
1391
"while building ctor reference for generator outer method"
1392
);
1393
1394
_context.write_line("newobj {ctor_reference}");
1395
_context.write_line("ret");
1396
1397
_context.outdent();
1398
println("}}");
1399
1400
if owner_is_global then
1401
gen_globals_class_close();
1402
fi
1403
1404
// --- Walk the body into MoveNext's block ---
1405
//
1406
// The block lands captured on PENDING_STATE_MACHINE
1407
// alongside the state machine; `_emit_pending_state_
1408
// machines` writes it out inside the class definition
1409
// once the enclosing user class has closed.
1410
//
1411
// For a generic generator, install gen_type_override on
1412
// function-T symbols so any IR.Values inside MoveNext
1413
// that baked in function-T (callvirt owner types on the
1414
// for-each dispatch, locals init for the iterator
1415
// current, etc.) emit `!N` (class-level — MoveNext has
1416
// no own type params) rather than `!!N`.
1417
state_machine.install_body_emission_overrides();
1418
1419
enter_scope(function);
1420
1421
let move_next_block = Values.BLOCK();
1422
1423
enter_block(move_next_block);
1424
1425
// `.maxstack` must precede any IL opcodes in the
1426
// method body. Add it before the dispatch placeholder
1427
// so the populated dispatch IL lands after the
1428
// directive at the start of MoveNext.
1429
add(".maxstack 64");
1430
1431
// Cache the state field to a CLR local at MoveNext
1432
// entry. Every yield keeps it in sync via `dup; stloc;
1433
// stfld` so per-region dispatches and state-guarded
1434
// finally bodies read a stable value. Same pattern as
1435
// the async path — needed to allow yield inside `.try`
1436
// without branching into a protected region from
1437
// outside (ECMA-335 forbids that, and the previous
1438
// centralized-dispatch shape had exactly that bug).
1439
let state_field_ref = frame.state_field.get_il_reference();
1440
let outer_dispatch_holder = _open_state_machine_entry(state_field_ref, "'.gen_state'");
1441
1442
// Stash the previous generator-yield-return label so
1443
// nested generator functions (rare) don't trip over
1444
// each other. visit(YIELD) lazily allocates a label the
1445
// first time it needs to `leave` from inside a `.try`.
1446
let prev_yield_return = _current_generator_yield_return_label;
1447
_current_generator_yield_return_label = null;
1448
1449
// Walk the body. Visitors fire pre/visit on every
1450
// node; visit(YIELD) is the interesting one — it
1451
// appends state-machine IL into current_block
1452
// (which IS move_next_block).
1453
if function.body? then
1454
function.body.walk(self);
1455
fi
1456
1457
// Fell-off-end trailer: mark done, return false. Plain
1458
// `ret` since we're at the MoveNext outer scope (no
1459
// open `.try`).
1460
add("ldarg.0");
1461
add("ldc.i4.m1");
1462
add("stfld {state_field_ref}");
1463
add("ldc.i4.0");
1464
add("ret");
1465
1466
// If any yield inside an inner `.try` used the
1467
// leave-return-true label, emit it now (outside any try)
1468
// followed by `ldc.i4.1; ret`. Reached only via `leave
1469
// L:` from inside a protected region.
1470
if _current_generator_yield_return_label? then
1471
add("{_current_generator_yield_return_label}:");
1472
add("ldc.i4.1");
1473
add("ret");
1474
fi
1475
1476
_current_generator_yield_return_label = prev_yield_return;
1477
1478
_pop_async_dispatch_holder(outer_dispatch_holder);
1479
1480
leave_block();
1481
leave_scope(function);
1482
1483
// Uninstall — re-installed by _emit_state_machine_class
1484
// around move_next_block.gen() so deferred IR.Value.gen()
1485
// calls inside MoveNext still see the override. Between
1486
// here and there, other class members emit normally.
1487
state_machine.uninstall_body_emission_overrides();
1488
1489
_pending_state_machines.add(PENDING_STATE_MACHINE(state_machine, move_next_block));
1490
1491
// Skip the framework's default body walk + the framework's
1492
// visit(function) close-out path. Our visit(function)
1493
// sees the state-machine and returns early.
1494
return true;
1495
si
1496
1497
_visit_generator_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, state_machine: Semantic.Symbols.STATE_MACHINE) is
1498
// _pre_generator_function emitted the outer method and
1499
// captured the move_next block onto _pending_state_machines.
1500
// Nothing more to do at AST visit time.
1501
si
1502
1503
// `newobj` operand for the SM frame's constructor. For a
1504
// generic frame, constructs a specialized type so the
1505
// class-T's appear in the signature; for a non-generic frame,
1506
// just the default ctor's IL reference. Exceptions in lookup
1507
// are logged and a sentinel returned.
1508
_build_specialized_ctor_reference(
1509
frame: Semantic.Symbols.Classy,
1510
default_ctor: Semantic.Symbols.Method,
1511
construction_type_args: Collections.List[Semantic.Types.Type]?,
1512
location: Source.LOCATION,
1513
error_context: string
1514
) -> string is
1515
let ctor_reference: string? mut = null;
1516
1517
try
1518
if let specialized_frame_type = _specialized_frame_type(frame, construction_type_args, location) then
1519
let specialized_ctor_symbol = specialized_frame_type.find_member("init");
1520
1521
if let function: Semantic.Symbols.Function = specialized_ctor_symbol then
1522
ctor_reference = function.get_il_reference();
1523
fi
1524
fi
1525
1526
if !ctor_reference? then
1527
ctor_reference = default_ctor.get_il_reference();
1528
fi
1529
catch e: System.Exception
1530
_logger.exception(location, e, error_context);
1531
ctor_reference = "/* error */";
1532
yrt
1533
1534
return ctor_reference!;
1535
si
1536
1537
// The frame type as it must be named in IL: constructed over the
1538
// type arguments visible inside the owning function, so a frame
1539
// declared in a generic owner is never referred to by its open
1540
// form. Null when the frame is not generic.
1541
_specialized_frame_type(
1542
frame: Semantic.Symbols.Classy,
1543
construction_type_args: Collections.List[Semantic.Types.Type]?,
1544
location: Source.LOCATION
1545
) -> Semantic.Types.Type? is
1546
if !construction_type_args? then
1547
return null;
1548
fi
1549
1550
return Semantic.Types.GENERIC(location, frame, construction_type_args);
1551
si
1552
1553
// Async-function IL emission. Parallels
1554
// `_pre_generator_function`: emits the outer method (newobj
1555
// SM, builder.Start, return Task) and stashes a MoveNext
1556
// BLOCK to be flushed when the enclosing class closes.
1557
// MoveNext wraps the body in a try/catch routing exceptions
1558
// through `builder.SetException` and success through
1559
// `builder.SetResult`.
1560
_pre_async_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE) -> bool is
1561
let frame = async_state_machine.frame;
1562
1563
if !frame? then
1564
_logger.error(function.location, "async function must return Tasks.TASK or Tasks.TASK[T]");
1565
return true;
1566
fi
1567
1568
frame.declare();
1569
1570
let owner_is_global = isa Semantic.Symbols.GLOBAL_FUNCTION(symbol);
1571
1572
if owner_is_global then
1573
gen_globals_class_open(cast Semantic.Symbols.NAMESPACE?(symbol.owner)!);
1574
fi
1575
1576
// --- Outer method ---
1577
println_comment("define {symbol.description}");
1578
1579
let outer_buffer = StringBuilder();
1580
symbol.gen_definition_header(outer_buffer);
1581
println(outer_buffer);
1582
1583
println("{{");
1584
_context.indent();
1585
1586
gen_function_attributes(symbol);
1587
1588
symbol.gen_body_header(_context);
1589
1590
_context.write_line(".maxstack 64");
1591
1592
let construction_type_args = async_state_machine.get_construction_type_arguments();
1593
1594
let ctor_reference = _build_specialized_ctor_reference(
1595
frame,
1596
frame.constructor,
1597
construction_type_args,
1598
symbol.location,
1599
"while building ctor reference for async outer method"
1600
);
1601
1602
// The local holding the state machine, and the type argument
1603
// to builder.Start, both name the frame type. A frame in a
1604
// generic owner is generic too, so both need the constructed
1605
// form — the open form does not resolve at run time.
1606
let state_machine_type =
1607
_specialized_frame_type(frame, construction_type_args, symbol.location) ?? frame.type!;
1608
1609
// ctor args: instance methods prepend `ldarg.0` for
1610
// _outer_self, then each user-declared parameter via
1611
// `ldarg '<name>'`. Build them as a sequence Value so
1612
// ASYNC_METHOD_LAUNCH composes them in the right order.
1613
let ctor_args_block = Values.BLOCK();
1614
if frame.outer_self_field? then
1615
ctor_args_block.add(IR.RAW("ldarg.0"));
1616
fi
1617
for arg_name in symbol.argument_names do
1618
ctor_args_block.add(IR.RAW("ldarg '{arg_name}'"));
1619
od
1620
ctor_args_block.close();
1621
1622
let task_type = symbol.return_type!;
1623
1624
// Open-T forms — only meaningful for the generic
1625
// (value-async) builder/Task. For void-async, the
1626
// builder type is non-generic and Create / get_Task
1627
// signatures have no class-T to resolve, so pass null
1628
// (ASYNC_METHOD_LAUNCH falls back to the constructed
1629
// owner type for the signature).
1630
let unspec_builder: Semantic.Types.Type? mut = null;
1631
let unspec_task: Semantic.Types.Type? mut = null;
1632
if !frame.is_void then
1633
unspec_builder = _innate_symbol_lookup.get_unspecialized_async_task_method_builder_type();
1634
unspec_task = _innate_symbol_lookup.get_unspecialized_task_type();
1635
fi
1636
1637
let launch = IR.Values.ASYNC_METHOD_LAUNCH(
1638
task_type,
1639
state_machine_type,
1640
ctor_reference,
1641
ctor_args_block,
1642
frame.state_field,
1643
frame.builder_field!,
1644
unspec_builder,
1645
unspec_task,
1646
"'.sm'"
1647
);
1648
1649
launch.gen(_context);
1650
_context.write_line("ret");
1651
1652
_context.outdent();
1653
println("}}");
1654
1655
if owner_is_global then
1656
gen_globals_class_close();
1657
fi
1658
1659
// --- Walk the body into MoveNext's block ---
1660
async_state_machine.install_body_emission_overrides();
1661
1662
enter_scope(function);
1663
1664
let move_next_block = Values.BLOCK();
1665
1666
enter_block(move_next_block);
1667
1668
add(".maxstack 64");
1669
1670
// Cache the state field to a CLR local at MoveNext entry.
1671
// Every AWAIT_SUSPEND keeps this local in sync via
1672
// `dup; stloc; stfld` so the per-region dispatch (below)
1673
// and the finally-body state guards read a stable value.
1674
//
1675
// Finally bodies (let-use auto-dispose and user-written
1676
// `finally`) state-guard their work on the same local:
1677
// when V_state >= 0 the function is mid-suspend (the
1678
// `leave` from the AwaitUnsafeOnCompleted path fires the
1679
// finally before exiting), and side effects must be
1680
// skipped.
1681
let state_field_ref = frame.state_field.get_il_reference();
1682
let outer_dispatch_holder = _open_state_machine_entry(state_field_ref, "'.async_state'");
1683
1684
// Labels used by the trailer: success fall-through and
1685
// the end of the wrapping try/catch. The entire MoveNext
1686
// body lives inside a `.try` whose handler routes
1687
// exceptions through `builder.SetException(e)`.
1688
let success_label = IR.LABEL();
1689
let end_label = IR.LABEL();
1690
1691
// Stash the emission context so visit(LET-await),
1692
// visit(AWAIT), and visit(RETURN) inside the body know
1693
// they're inside this state machine and can reach the
1694
// frame / labels they need to emit. Cleared after the
1695
// body walk so anything else emitted at this nesting
1696
// level (other functions, nested classes' visits)
1697
// goes through the standard paths.
1698
let prev_async_sm = _current_async_state_machine;
1699
let prev_success = _current_async_success_label;
1700
let prev_end = _current_async_end_label;
1701
_current_async_state_machine = async_state_machine;
1702
_current_async_success_label = success_label;
1703
_current_async_end_label = end_label;
1704
1705
// Walk the body. visit(AWAIT) appends AWAIT_SUSPEND IR
1706
// Values into move_next_block; other statements emit
1707
// normally.
1708
if function.body? then
1709
function.body.walk(self);
1710
fi
1711
1712
// Body fell off the end: leave success_label so the
1713
// trailer fires.
1714
add("leave {success_label}");
1715
1716
_current_async_state_machine = prev_async_sm;
1717
_current_async_success_label = prev_success;
1718
_current_async_end_label = prev_end;
1719
1720
// Populate the outer dispatch from the holder that
1721
// accumulated awaits not enclosed in any inner `.try`.
1722
_pop_async_dispatch_holder(outer_dispatch_holder);
1723
1724
leave_block();
1725
leave_scope(function);
1726
1727
async_state_machine.uninstall_body_emission_overrides();
1728
1729
_pending_async_state_machines.add(
1730
PENDING_ASYNC_STATE_MACHINE(
1731
async_state_machine,
1732
move_next_block,
1733
success_label,
1734
end_label
1735
)
1736
);
1737
1738
return true;
1739
si
1740
1741
// The state-local IL spelling of the innermost open
1742
// dispatch holder — `'.async_state'` for async state
1743
// machines, `'.gen_state'` for generators, null when no
1744
// state machine is open. Used by `.try` emission and the
1745
// finally-body state guard so the same code paths emit the
1746
// right local name without each caller having to know.
1747
_current_state_local_il() -> string? is
1748
if _async_dispatch_stack.count == 0 then
1749
return null;
1750
fi
1751
return _async_dispatch_stack[_async_dispatch_stack.count - 1].state_local_il;
1752
si
1753
1754
// Declare a CLR `int32` local in the MoveNext frame, copy
1755
// the state field into it, and push the outer dispatch
1756
// holder bound to that local. Returns the pushed holder so
1757
// the caller can pop it at the end of the body walk via
1758
// `_pop_async_dispatch_holder`. Used by every state-machine
1759
// entry point (named function, lambda closure, generator).
1760
// `state_local_il` is the local's IL spelling —
1761
// `'.async_state'` for async, `'.gen_state'` for
1762
// generators — and travels with the holder so inner
1763
// dispatch sites pick it up.
1764
_open_state_machine_entry(
1765
state_field_ref: string,
1766
state_local_il: string
1767
) -> ASYNC_DISPATCH_HOLDER is
1768
add(".locals init (int32 {state_local_il})");
1769
add("ldarg.0");
1770
add("ldfld {state_field_ref}");
1771
add("stloc {state_local_il}");
1772
1773
let block = Values.BLOCK();
1774
add(block);
1775
let holder = ASYNC_DISPATCH_HOLDER(block, state_local_il);
1776
_async_dispatch_stack.add(holder);
1777
return holder;
1778
si
1779
1780
// Push an inner dispatch holder at the top of a `.try` body
1781
// if we're inside a state-machine method. Returns the
1782
// holder (to pop later) or null when no state machine is
1783
// open — the caller can pass that null back to
1784
// `_maybe_pop_dispatch_holder` for symmetric cleanup
1785
// without an `if` at every call site.
1786
_maybe_push_dispatch_holder() -> ASYNC_DISPATCH_HOLDER? is
1787
if !_current_state_local_il()? then
1788
return null;
1789
fi
1790
return _push_async_dispatch_holder();
1791
si
1792
1793
_maybe_pop_dispatch_holder(holder: ASYNC_DISPATCH_HOLDER?) is
1794
if holder? then
1795
_pop_async_dispatch_holder(holder);
1796
fi
1797
si
1798
1799
// The state-machine frame for the current function — async
1800
// or generator, whichever applies. Used by the BLOCK-with-
1801
// suspend spill in `LIST.visit` / `visit(IF)` / `visit(CASE)`
1802
// (in expression position) to allocate spill fields that
1803
// survive across MoveNext re-entries.
1804
_current_state_machine_frame() -> Semantic.Symbols.STATE_MACHINE_FRAME_BASE? is
1805
let casm = _current_async_state_machine;
1806
1807
if casm? /\ casm.frame? then
1808
return casm.frame;
1809
fi
1810
1811
let cf = current_function;
1812
if cf? then
1813
let sm = Semantic.Symbols.state_machine_for(cf);
1814
if sm? /\ sm.frame? then
1815
return sm.frame;
1816
fi
1817
fi
1818
1819
return null;
1820
si
1821
1822
// Does `node` contain a state-machine suspend (`await` or
1823
// `yield`) outside any nested function literal? Drives the
1824
// decision in `LIST.visit` / IF / CASE composite handlers
1825
// whether to spill their BLOCK value to a frame field.
1826
_contains_suspend(node: Trees.Node) -> bool =>
1827
val
1828
let scanner = Syntax.Process.CONTAINS_SUSPEND_SCANNER();
1829
node.walk(scanner);
1830
scanner.found
1831
lav;
1832
1833
// Build the IR Value that loads from `field` on the current
1834
// state-machine frame — used as the spilled BLOCK's stand-in
1835
// value. The consumer that does `add(block)` reads this
1836
// load when the BLOCK gens, in place of the original body
1837
// IL.
1838
_build_frame_field_load(frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE, spill_field: Semantic.Symbols.Field) -> IR.Values.Value =>
1839
IR.Values.Load.INSTANCE_FIELD(
1840
IR.Values.Load.REFERENCE_SELF(frame, frame.type),
1841
spill_field
1842
);
1843
1844
// BLOCK-with-suspend spill path for `Statements.LIST` in
1845
// expression position. The capture-mode LIST.visit wraps
1846
// its body in lots of state-machine plumbing (dispatch
1847
// holders, exception-handler temps, `.try` for `want
1848
// _dispose`) that the spill path doesn't need — the body
1849
// emits inline in outer current_block, the result lands
1850
// in a frame field, and the BLOCK ends up holding just a
1851
// load. Returns true if the spill path ran (caller skips
1852
// the ordinary capture path); false otherwise.
1853
_try_spill_block_with_suspend(list: Statements.LIST) -> bool is
1854
// `want_dispose` lists need their `.try` body inside
1855
// the protected region — bypass spill and let the
1856
// capture path handle it.
1857
if list.want_dispose then
1858
return false;
1859
fi
1860
1861
let spiller = COMPOSITE_VALUE_SPILLER(
1862
self,
1863
list.value,
1864
_current_state_machine_frame(),
1865
_contains_suspend(list)
1866
);
1867
1868
if !spiller.is_spilling then
1869
return false;
1870
fi
1871
1872
// Walk body in outer current_block. Statement IL —
1873
// including visit(AWAIT) / visit(YIELD) suspend
1874
// emissions and nested composites' own spills — lands
1875
// in outer, in the same flat stream as the SM
1876
// dispatcher.
1877
for s in list.statements do
1878
enter_node(s);
1879
try
1880
s.walk(self);
1881
finally
1882
leave_node(s);
1883
yrt
1884
od
1885
1886
// Tail-evaluate-and-spill. By now the tail's value is a
1887
// clean load (any tail-internal suspends already
1888
// hoisted by visit(AWAIT) / visit(YIELD), and any
1889
// tail-internal composites already spilled themselves),
1890
// so the ldarg.0 / value-IL / stfld sequence has no
1891
// suspend between receiver push and stfld.
1892
if let list.last?, last.value? then
1893
spiller.emit_value(value);
1894
fi
1895
1896
spiller.leave();
1897
1898
return true;
1899
si
1900
1901
1902
// Record a (state, resume_label) pair with the innermost
1903
// open dispatch holder, so its dispatch block (at the top
1904
// of its `.try` body OR at MoveNext outer scope) routes
1905
// cold-resume entries to {resume_label}. No-op outside a
1906
// state machine.
1907
_register_resume_with_dispatch(state: int, resume_label: IR.LABEL) is
1908
if _async_dispatch_stack.count == 0 then
1909
return;
1910
fi
1911
let holder = _async_dispatch_stack[_async_dispatch_stack.count - 1];
1912
holder.register(state, resume_label);
1913
si
1914
1915
// Emit the `if V_state >= 0 → skip` guard at the top of a
1916
// finally body in a state-machine method. The `leave` that
1917
// ends a suspend (AwaitUnsafeOnCompleted or yield) fires the
1918
// surrounding finally with state >= 0; the user's code must
1919
// not run then. Real exits (state == -1) and exceptional
1920
// unwinds run the finally normally.
1921
//
1922
// Returns the skip label so the caller can plant it at the
1923
// end of the guarded code via `_close_finally_state_guard`.
1924
// Returns null when not inside a state machine — caller
1925
// emits the finally body unguarded.
1926
_open_finally_state_guard() -> LABEL? is
1927
let state_local_il = _current_state_local_il();
1928
1929
if !state_local_il? then
1930
return null;
1931
fi
1932
1933
let skip = LABEL();
1934
add("ldloc {state_local_il}");
1935
add("ldc.i4.0");
1936
add("bge {skip}");
1937
1938
return skip;
1939
si
1940
1941
_close_finally_state_guard(skip: LABEL?) is
1942
if skip? then
1943
get_brancher_for_block().label(skip);
1944
fi
1945
si
1946
1947
// Push a new dispatch holder onto the stack and emit its
1948
// placeholder block at the current emission point. Returns
1949
// the holder so the caller can pop it once the protected
1950
// region's body has been walked. Use when opening a `.try`
1951
// body inside an async function or generator so the
1952
// AWAIT_SUSPENDs / YIELDs that fire in the body register
1953
// here and the dispatch ends up inside the same region as
1954
// the targets it reaches. The new holder inherits its
1955
// state-local IL spelling from the top of the stack — the
1956
// outer holder (pushed at MoveNext entry) chose the name
1957
// (`'.async_state'` for async, `'.gen_state'` for
1958
// generators) so inner holders just match.
1959
_push_async_dispatch_holder() -> ASYNC_DISPATCH_HOLDER is
1960
assert _async_dispatch_stack.count > 0 else "no outer dispatch holder to inherit state-local name from";
1961
let outer = _async_dispatch_stack[_async_dispatch_stack.count - 1];
1962
let block = Values.BLOCK();
1963
add(block);
1964
let holder = ASYNC_DISPATCH_HOLDER(block, outer.state_local_il);
1965
_async_dispatch_stack.add(holder);
1966
return holder;
1967
si
1968
1969
// Populate the holder's dispatch block from registered
1970
// (state, cold_resume_label) entries and pop. No-op if no
1971
// entries — keeps emitted IL minimal when a `.try` body
1972
// contains no awaits.
1973
_pop_async_dispatch_holder(holder: ASYNC_DISPATCH_HOLDER) is
1974
_populate_async_dispatch(holder);
1975
assert _async_dispatch_stack.count > 0 else "dispatch holder stack underflow";
1976
_async_dispatch_stack.remove_at(_async_dispatch_stack.count - 1);
1977
si
1978
1979
// Fill the holder's placeholder block with one
1980
// `ldloc V_state; ldc.i4 N; beq cold_resume_N` entry per
1981
// registered await. The placeholder was inserted into the
1982
// surrounding emission stream when the holder was pushed —
1983
// these instructions now sit at the top of the protected
1984
// region's body, before any user code.
1985
_populate_async_dispatch(holder: ASYNC_DISPATCH_HOLDER) is
1986
for entry in holder.entries do
1987
holder.block.add("ldloc {holder.state_local_il}");
1988
holder.block.add("ldc.i4 {entry.state}");
1989
holder.block.add("beq {entry.cold_resume_label}");
1990
od
1991
si
1992
1993
// Emit all pending state-machine classes accumulated during
1994
// the walk of an enclosing class or namespace. Called at the
1995
// tail of visit(`class) / visit(`namespace) so the classes
1996
// land at namespace scope, parallel to the user's class.
1997
emit_pending_state_machines() is
1998
if _pending_state_machines.count > 0 then
1999
let pending = _pending_state_machines;
2000
_pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE]();
2001
2002
for psm in pending do
2003
_emit_state_machine_class(psm.state_machine, psm.move_next_block);
2004
od
2005
fi
2006
2007
if _pending_async_state_machines.count > 0 then
2008
let pending = _pending_async_state_machines;
2009
_pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE]();
2010
2011
for pasm in pending do
2012
_emit_async_state_machine_class(
2013
pasm.async_state_machine,
2014
pasm.move_next_block,
2015
pasm.success_label,
2016
pasm.end_label
2017
);
2018
od
2019
fi
2020
si
2021
2022
// Emits the synthesised IAsyncStateMachine class — parallels
2023
// `_emit_state_machine_class` for generators. Class members:
2024
// `$state`, `$builder`, `$result` (value-async), `$outer_self`
2025
// (instance), `$arg_*`, `$local_*`, `$awaiter_*` fields; ctor;
2026
// MoveNext (with entry-dispatch and try/catch routing through
2027
// the builder); trivial SetStateMachine.
2028
_emit_async_state_machine_class(
2029
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE,
2030
move_next_block: Values.BLOCK,
2031
success_label: IR.LABEL,
2032
end_label: IR.LABEL
2033
) is
2034
let frame = async_state_machine.frame;
2035
2036
assert frame? else "async state machine has no frame at IL emission";
2037
2038
async_state_machine.install_body_emission_overrides();
2039
2040
println_comment("define async state-machine class for {async_state_machine.function.description}");
2041
2042
let class_buffer = StringBuilder();
2043
frame.gen_definition_header(class_buffer);
2044
println(class_buffer);
2045
2046
println("{{");
2047
_context.indent();
2048
2049
// Fields.
2050
let state_buf = StringBuilder();
2051
frame.state_field.gen_definition_header(state_buf);
2052
println(state_buf);
2053
2054
let builder_field = frame.builder_field;
2055
if builder_field? then
2056
let builder_buf = StringBuilder();
2057
builder_field.gen_definition_header(builder_buf);
2058
println(builder_buf);
2059
fi
2060
2061
let result_field = frame.result_field;
2062
if result_field? then
2063
let result_buf = StringBuilder();
2064
result_field.gen_definition_header(result_buf);
2065
println(result_buf);
2066
fi
2067
2068
let outer_self_field = frame.outer_self_field;
2069
if outer_self_field? then
2070
let outer_buf = StringBuilder();
2071
outer_self_field.gen_definition_header(outer_buf);
2072
println(outer_buf);
2073
fi
2074
2075
for arg_field in frame.argument_fields do
2076
let arg_buf = StringBuilder();
2077
arg_field.gen_definition_header(arg_buf);
2078
println(arg_buf);
2079
od
2080
2081
for local_field in frame.local_fields do
2082
let local_buf = StringBuilder();
2083
local_field.gen_definition_header(local_buf);
2084
println(local_buf);
2085
od
2086
2087
for awaiter_field in frame.awaiter_fields do
2088
let awaiter_buf = StringBuilder();
2089
awaiter_field.gen_definition_header(awaiter_buf);
2090
println(awaiter_buf);
2091
od
2092
2093
// .ctor — same shape as the generator's: chain to
2094
// Object::.ctor, then store each ctor arg into its frame
2095
// field. Builder isn't initialised here; the outer
2096
// method's body calls AsyncTaskMethodBuilder.Create()
2097
// and stfld's it after newobj.
2098
_gen_async_state_machine_ctor(frame);
2099
2100
// MoveNext — wraps the user's body in try/catch so
2101
// exceptions route through builder.SetException.
2102
_gen_async_move_next(async_state_machine, move_next_block, success_label, end_label);
2103
2104
// SetStateMachine — trivial no-op for class-based SM.
2105
_boilerplate_generator.gen("async-set-state-machine");
2106
2107
_context.outdent();
2108
println("}}");
2109
2110
async_state_machine.uninstall_body_emission_overrides();
2111
si
2112
2113
_gen_async_state_machine_ctor(frame: Semantic.Symbols.ASYNC_STATE_MACHINE_FRAME) is
2114
let ctor_buf = StringBuilder();
2115
frame.constructor.gen_definition_header(ctor_buf);
2116
println(ctor_buf);
2117
2118
println("{{");
2119
_context.indent();
2120
_context.write_line(".maxstack 8");
2121
_context.write_line("ldarg.0");
2122
_context.write_line("call instance void ['System.Runtime']'System'.'Object'::'.ctor'()");
2123
2124
let arg_names = frame.constructor.argument_names;
2125
let i mut = 0;
2126
2127
let outer_self_field = frame.outer_self_field;
2128
if outer_self_field? then
2129
_context.write_line("ldarg.0");
2130
_context.write_line("ldarg '{arg_names[i]}'");
2131
_context.write_line("stfld {outer_self_field.get_il_reference()}");
2132
i = i + 1;
2133
fi
2134
2135
for arg_field in frame.argument_fields do
2136
_context.write_line("ldarg.0");
2137
_context.write_line("ldarg '{arg_names[i]}'");
2138
_context.write_line("stfld {arg_field.get_il_reference()}");
2139
i = i + 1;
2140
od
2141
2142
_context.write_line("ret");
2143
_context.outdent();
2144
println("}}");
2145
si
2146
2147
_gen_async_move_next(
2148
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE,
2149
move_next_block: Values.BLOCK,
2150
success_label: IR.LABEL,
2151
end_label: IR.LABEL
2152
) is
2153
let frame = async_state_machine.frame;
2154
2155
assert frame? else "async state machine has no frame at IL emission";
2156
2157
let builder_field = frame.builder_field!;
2158
2159
println(".method public final hidebysig newslot virtual instance default void 'MoveNext'() cil managed");
2160
println("{{");
2161
_context.indent();
2162
_context.write_line(".override class ['System.Runtime']System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext");
2163
2164
// Wrap the move_next_block in a try/catch that routes
2165
// exceptions through builder.SetException. The success
2166
// fall-through path (leave success_label) calls
2167
// builder.SetResult and falls through to end_label/ret.
2168
_context.write_line(".try {{");
2169
_context.indent();
2170
2171
_context.reset_line_tracking();
2172
move_next_block.gen(_context);
2173
2174
_context.outdent();
2175
_context.write_line("}}");
2176
2177
let exception_il = "class ['System.Runtime']System.Exception";
2178
_context.write_line("catch {exception_il} {{");
2179
_context.indent();
2180
_context.write_line(".locals init ({exception_il} '.exception')");
2181
_context.write_line("stloc '.exception'");
2182
_context.write_line("ldarg.0");
2183
_context.write_line("ldc.i4.m1");
2184
_context.write_line("stfld {frame.state_field.get_il_reference()}");
2185
_context.write_line("ldarg.0");
2186
_context.write_line("ldflda {builder_field.get_il_reference()}");
2187
_context.write_line("ldloc '.exception'");
2188
let builder_il = builder_field.type!.get_il_type();
2189
_context.write_line("call instance void {builder_il}::'SetException'({exception_il})");
2190
_context.write_line("leave {end_label}");
2191
_context.outdent();
2192
_context.write_line("}}");
2193
2194
// Success: state = -1; builder.SetResult(<result>).
2195
//
2196
// `SetResult(T)` on `AsyncTaskMethodBuilder<T>` takes
2197
// class-T; the methodref's arg type slot must be `!0`
2198
// (open class-T), not the substituted concrete T. The
2199
// non-generic `AsyncTaskMethodBuilder`'s SetResult takes
2200
// no value-async result and has no class-T issue.
2201
_context.write_line("{success_label}:");
2202
_context.write_line("ldarg.0");
2203
_context.write_line("ldc.i4.m1");
2204
_context.write_line("stfld {frame.state_field.get_il_reference()}");
2205
_context.write_line("ldarg.0");
2206
_context.write_line("ldflda {builder_field.get_il_reference()}");
2207
let result_field = frame.result_field;
2208
if result_field? then
2209
_context.write_line("ldarg.0");
2210
_context.write_line("ldfld {result_field.get_il_reference()}");
2211
_context.write_line("call instance void {builder_il}::'SetResult'(!0)");
2212
else
2213
_context.write_line("call instance void {builder_il}::'SetResult'()");
2214
fi
2215
2216
_context.write_line("{end_label}:");
2217
_context.write_line("ret");
2218
2219
_context.outdent();
2220
println("}}");
2221
si
2222
2223
_emit_state_machine_class(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: Values.BLOCK) is
2224
let frame = state_machine.frame;
2225
2226
assert frame? else "state machine has no frame at IL emission";
2227
2228
// Re-install the body-emission override so deferred
2229
// IR.Value.gen() inside MoveNext renders function-T as
2230
// class-level `!N`. Uninstalled at the bottom of this
2231
// method.
2232
state_machine.install_body_emission_overrides();
2233
2234
println_comment("define state-machine class for {state_machine.function.description}");
2235
2236
let class_buffer = StringBuilder();
2237
frame.gen_definition_header(class_buffer);
2238
println(class_buffer);
2239
2240
println("{{");
2241
_context.indent();
2242
2243
// Fields.
2244
let state_buf = StringBuilder();
2245
frame.state_field.gen_definition_header(state_buf);
2246
println(state_buf);
2247
2248
let current_buf = StringBuilder();
2249
frame.current_field.gen_definition_header(current_buf);
2250
println(current_buf);
2251
2252
let outer_self_field = frame.outer_self_field;
2253
if outer_self_field? then
2254
let outer_buf = StringBuilder();
2255
outer_self_field.gen_definition_header(outer_buf);
2256
println(outer_buf);
2257
fi
2258
2259
for arg_field in frame.argument_fields do
2260
let arg_buf = StringBuilder();
2261
arg_field.gen_definition_header(arg_buf);
2262
println(arg_buf);
2263
od
2264
2265
for local_field in frame.local_fields do
2266
let local_buf = StringBuilder();
2267
local_field.gen_definition_header(local_buf);
2268
println(local_buf);
2269
od
2270
2271
// .ctor — parameterless, chains to Object::.ctor.
2272
_gen_state_machine_ctor(frame);
2273
2274
// MoveNext — populated during the body walk.
2275
println(".method public final hidebysig newslot virtual instance default bool 'MoveNext'() cil managed");
2276
println("{{");
2277
_context.indent();
2278
_context.reset_line_tracking();
2279
move_next_block.gen(_context);
2280
_context.outdent();
2281
println("}}");
2282
2283
// Other Iterator[T] / IDisposable members.
2284
_gen_state_machine_accessors(frame);
2285
2286
gen_ienumerable_boilerplate(frame);
2287
gen_ienumerator_boilerplate(frame);
2288
2289
_context.outdent();
2290
println("}}");
2291
2292
state_machine.uninstall_body_emission_overrides();
2293
si
2294
2295
_gen_state_machine_ctor(frame: Semantic.Symbols.STATE_MACHINE_FRAME) is
2296
let ctor_buf = StringBuilder();
2297
frame.constructor.gen_definition_header(ctor_buf);
2298
println(ctor_buf);
2299
2300
println("{{");
2301
_context.indent();
2302
_context.write_line(".maxstack 8");
2303
_context.write_line("ldarg.0");
2304
_context.write_line("call instance void ['System.Runtime']'System'.'Object'::'.ctor'()");
2305
2306
// Constructor parameter layout (mirrors what
2307
// STATE_MACHINE_FRAME.declare set up):
2308
// [$outer_self,] arg0, arg1, ...
2309
// The instance-generator outer method passes `this` as
2310
// the leading argument; static / global generators
2311
// skip that prefix.
2312
let arg_names = frame.constructor.argument_names;
2313
let i mut = 0;
2314
2315
let outer_self_field = frame.outer_self_field;
2316
if outer_self_field? then
2317
_context.write_line("ldarg.0");
2318
_context.write_line("ldarg '{arg_names[i]}'");
2319
_context.write_line("stfld {outer_self_field.get_il_reference()}");
2320
i = i + 1;
2321
fi
2322
2323
// Copy each user-parameter ctor argument into its
2324
// frame field. Parameter order matches argument_fields
2325
// by construction.
2326
for arg_field in frame.argument_fields do
2327
_context.write_line("ldarg.0");
2328
_context.write_line("ldarg '{arg_names[i]}'");
2329
_context.write_line("stfld {arg_field.get_il_reference()}");
2330
2331
i = i + 1;
2332
od
2333
2334
_context.write_line("ret");
2335
_context.outdent();
2336
println("}}");
2337
si
2338
2339
_gen_state_machine_accessors(frame: Semantic.Symbols.STATE_MACHINE_FRAME) is
2340
let element_il = StringBuilder();
2341
frame.class_element_type.gen_type(element_il);
2342
let element_il_text = element_il.to_string();
2343
2344
let current_field_ref = frame.current_field.get_il_reference();
2345
2346
// get_Current — IEnumerator<T>.get_Current
2347
println(".method public final hidebysig newslot virtual specialname instance default {element_il_text} 'get_Current'() cil managed");
2348
println("{{");
2349
_context.indent();
2350
_context.write_line(".maxstack 1");
2351
_context.write_line("ldarg.0");
2352
_context.write_line("ldfld {current_field_ref}");
2353
_context.write_line("ret");
2354
_context.outdent();
2355
println("}}");
2356
2357
// GetEnumerator — IEnumerable<T>.GetEnumerator returns
2358
// self (we implement IEnumerator<T> directly).
2359
println(".method public final hidebysig newslot virtual specialname instance default class ['System.Runtime']'System'.'Collections'.'Generic'.'IEnumerator`1'<{element_il_text}> 'GetEnumerator'() cil managed");
2360
println("{{");
2361
_context.indent();
2362
_context.write_line(".maxstack 1");
2363
_context.write_line("ldarg.0");
2364
_context.write_line("ret");
2365
_context.outdent();
2366
println("}}");
2367
2368
// Dispose — no-op. Known gap: if a generator with
2369
// yield inside a try-finally is abandoned mid-flight
2370
// (the consumer stops calling MoveNext before
2371
// exhaustion), the user's finally body never runs. C#
2372
// handles this via per-state finally tables walked by
2373
// Dispose(); ghūl would need the same wiring. Until
2374
// then, generators that own resources should be
2375
// consumed to completion or with an explicit
2376
// try/finally at the call site.
2377
println(".method public final hidebysig newslot virtual instance default void 'Dispose'() cil managed");
2378
println("{{");
2379
_context.indent();
2380
_context.write_line(".maxstack 1");
2381
_context.write_line("ret");
2382
_context.outdent();
2383
println("}}");
2384
2385
// Reset — rewind to the initial state. The next MoveNext
2386
// re-enters at state 0 and re-runs the body from the top,
2387
// which re-initializes the body locals (only the captured
2388
// parameters and $outer_self persist). C# throws here
2389
// because its consumers re-iterate via a fresh GetEnumerator;
2390
// ghūl's pipes rewind via reset(), so a resettable generator
2391
// iterator is the consistent choice.
2392
println(".method public final hidebysig newslot virtual instance default void 'Reset'() cil managed");
2393
println("{{");
2394
_context.indent();
2395
_context.write_line(".maxstack 2");
2396
_context.write_line("ldarg.0");
2397
_context.write_line("ldc.i4.0");
2398
_context.write_line("stfld {frame.state_field.get_il_reference()}");
2399
_context.write_line("ret");
2400
_context.outdent();
2401
println("}}");
2402
2403
// to_string — a generator is a Pipe[T], so stringify it the
2404
// way every other Pipe does: join the elements. The Pipe
2405
// trait's default to_string is a default interface method,
2406
// which does not override Object.ToString, so each
2407
// implementing class (here, the state machine) must provide
2408
// its own ToString — same as ADAPTOR_PIPE / MAP_PIPE / etc.
2409
println(".method public virtual hidebysig instance default class ['System.Runtime']'System'.'String' 'ToString'() cil managed");
2410
println("{{");
2411
_context.indent();
2412
_context.write_line(".maxstack 8");
2413
_context.write_line("ldarg.0");
2414
_context.write_line("ldstr \", \"");
2415
_context.write_line("callvirt instance class ['System.Runtime']'System'.'String' class ['ghul-runtime']'Ghul'.'Pipes'.'Pipe'<{element_il_text}>::'join'(class ['System.Runtime']'System'.'String')");
2416
_context.write_line("ret");
2417
_context.outdent();
2418
println("}}");
2419
si
2420
2421
pre(body: Bodies.BLOCK) -> bool is
2422
add(".maxstack 64");
2423
2424
return super.pre(body);
2425
si
2426
2427
pre(expression_body: Bodies.EXPRESSION) -> bool is
2428
add(".maxstack 64");
2429
2430
return super.pre(expression_body);
2431
si
2432
2433
visit(expression_body: Bodies.EXPRESSION) is
2434
let body_value = expression_body.expression.value;
2435
if body_value? /\ body_value.type? then
2436
let value =
2437
_boxer.box_if_needed(
2438
body_value,
2439
current_function!.return_type!
2440
);
2441
2442
add(value);
2443
elif let
2444
self.current_function? /\
2445
current_function.return_type? /\
2446
!current_function.return_type.is_void
2447
then
2448
// Diverging body (`=> throw E`): the body's own IL
2449
// already terminates control flow. Emit a default-value
2450
// trailer so the unreachable fall-off is verifiable,
2451
// matching statement-bodied functions that end in throw.
2452
add(".locals init ({current_function.return_type!.get_il_type()} '.default')");
2453
add("ldloc '.default'");
2454
fi
2455
2456
add("ret");
2457
2458
super.visit(expression_body);
2459
si
2460
2461
pre(property: Definitions.PROPERTY) -> bool is
2462
let symbol = symbol_for(property);
2463
2464
if !symbol? then
2465
return true;
2466
fi
2467
2468
// Property AST node carries either a Property symbol or a Variable
2469
// symbol (declare_symbols collapses `_name: T;` to a variable). Both
2470
// need wrapping when at namespace scope.
2471
let globals_namespace: Semantic.Symbols.NAMESPACE? mut = null;
2472
2473
if let global_property: Semantic.Symbols.GLOBAL_PROPERTY = symbol then
2474
globals_namespace = cast Semantic.Symbols.NAMESPACE?(global_property.owner)!;
2475
else
2476
if let global_variable: Semantic.Symbols.GLOBAL_VARIABLE = symbol then
2477
globals_namespace = cast Semantic.Symbols.NAMESPACE?(global_variable.owner)!;
2478
fi
2479
fi
2480
2481
if globals_namespace? then
2482
gen_globals_class_open(globals_namespace);
2483
fi
2484
2485
let buffer = System.Text.StringBuilder();
2486
symbol.gen_definition_header(buffer);
2487
2488
// Attribute directives that must associate with the
2489
// just-emitted declaration: NullableAttribute for a slot whose
2490
// tree carries a reference-`?` position, plus any attribute
2491
// pragmas resolved onto the symbol. Two emission shapes hit
2492
// this path:
2493
// - `.property ... { .get .set }` — splice the .custom
2494
// inside the braces (where C# puts it, and where ILASM
2495
// associates it).
2496
// - `.field ... 'name'` (declare_symbols collapsed a
2497
// `_name: T;` declaration to a Variable) — append the
2498
// .custom on a following line; ILASM associates a
2499
// `.custom` immediately after a `.field` with it.
2500
let attribute_lines = Collections.LIST[string]();
2501
2502
let slot_type: Semantic.Types.Type? mut = null;
2503
2504
if let property_symbol: Semantic.Symbols.Property = symbol then
2505
slot_type = property_symbol.type;
2506
else
2507
if let variable_symbol: Semantic.Symbols.Variable = symbol then
2508
slot_type = variable_symbol.type;
2509
fi
2510
fi
2511
2512
if let type = slot_type then
2513
let nullable_line = Semantic.DotNet.NULLABILITY.gen_attribute_line_for_type(type);
2514
2515
if nullable_line? then
2516
attribute_lines.add(nullable_line);
2517
fi
2518
2519
let tuple_names_line = Semantic.DotNet.TUPLE_ELEMENT_NAMES.gen_attribute_line_for_type(type);
2520
2521
if tuple_names_line? then
2522
attribute_lines.add(tuple_names_line);
2523
fi
2524
fi
2525
2526
if symbol.custom_attributes? then
2527
for attribute in symbol.custom_attributes do
2528
attribute_lines.add(attribute.gen_il_line());
2529
od
2530
fi
2531
2532
if attribute_lines.count > 0 then
2533
if isa Semantic.Symbols.Property(symbol) then
2534
let rendered = buffer.to_string();
2535
let close = rendered.last_index_of('}');
2536
2537
if close >= 0 then
2538
buffer.clear();
2539
buffer.append(rendered.substring(0, close));
2540
2541
for line in attribute_lines do
2542
buffer.append(' ');
2543
buffer.append(line);
2544
od
2545
2546
buffer.append(" }}");
2547
fi
2548
else
2549
for line in attribute_lines do
2550
buffer.append('\n');
2551
buffer.append(line);
2552
od
2553
fi
2554
fi
2555
2556
println(buffer.to_string());
2557
2558
if globals_namespace? then
2559
gen_globals_class_close();
2560
fi
2561
2562
return true;
2563
si
2564
2565
pre(pragma: Definitions.PRAGMA) -> bool is
2566
process_pragma(pragma.pragma, true);
2567
return false;
2568
si
2569
2570
visit(pragma: Definitions.PRAGMA) is
2571
process_pragma(pragma.pragma, false);
2572
si
2573
2574
process_pragma(pragma: Pragmas.PRAGMA, is_enter: bool) is
2575
let name = pragma.name.to_string();
2576
2577
if name =~ "IL.output" then
2578
if pragma.arguments.expressions.count != 1 then
2579
_logger.error(pragma.arguments.location, "expected 1 argument");
2580
return;
2581
fi
2582
2583
let argument = pragma.arguments.expressions[0];
2584
2585
if !isa Expressions.Literals.STRING(argument) then
2586
_logger.error(pragma.arguments.location, "expected a string literal argument");
2587
return;
2588
fi
2589
2590
let file_name = argument.value_string;
2591
2592
if is_enter then
2593
_il_output_depth = _il_output_depth + 1;
2594
2595
if _block_context.is_in_block then
2596
current_block.add(Values.ENTER_FILE(file_name));
2597
else
2598
_context.enter_file(file_name, false);
2599
fi
2600
else
2601
_il_output_depth = _il_output_depth - 1;
2602
2603
if _block_context.is_in_block then
2604
current_block.add(Values.LEAVE_FILE(file_name));
2605
else
2606
_context.leave_file(file_name);
2607
fi
2608
fi
2609
elif name =~ "IL.entrypoint" \/ name =~ "entry" then
2610
if is_enter then
2611
if _context.seen_entrypoint then
2612
_logger.error(pragma.location, "duplicate entrypoint");
2613
else
2614
_context.seen_entrypoint = true;
2615
_context.write_line(".entrypoint");
2616
fi
2617
fi
2618
elif name =~ "test" then
2619
if is_enter then
2620
if current_function? then
2621
_boilerplate_generator.gen("test-method");
2622
else
2623
_boilerplate_generator.gen("test-class");
2624
fi
2625
fi
2626
elif name =~ "test_class" then
2627
if is_enter then
2628
_boilerplate_generator.gen("test-class");
2629
fi
2630
elif name =~ "test_method" then
2631
if is_enter then
2632
_boilerplate_generator.gen("test-method");
2633
fi
2634
fi
2635
si
2636
2637
pre(function: Trees.Expressions.FUNCTION) -> bool is
2638
super.pre(function);
2639
2640
let closure = cast Semantic.Symbols.Closure?(scope_for(function));
2641
2642
if !closure? then
2643
return true;
2644
fi
2645
2646
closure.map_type_arguments();
2647
2648
return true;
2649
si
2650
2651
visit(function: Trees.Expressions.FUNCTION) is
2652
let closure = cast Semantic.Symbols.Closure?(scope_for(function))!;
2653
2654
// Async closure dispatch (parallel to the named-function
2655
// path in pre(Definitions.FUNCTION)). The lambda's $anon
2656
// method body becomes a state-machine launch sequence
2657
// (Create + MoveNext + return Task); the lambda body
2658
// walks into a separate move_next_block; the SM class is
2659
// queued for emission at class/namespace tail.
2660
let async_sm = Semantic.Symbols.async_state_machine_for(closure);
2661
if async_sm? then
2662
_emit_async_closure(function, closure, async_sm);
2663
super.visit(function);
2664
closure.unmap_type_arguments();
2665
return;
2666
fi
2667
2668
enter_block();
2669
2670
// Safety net: if a closure's return type resolved to ERROR
2671
// (e.g. its body referenced a variable that itself never got
2672
// a type), the consume-side check that should have surfaced
2673
// a diagnostic missed this path — but `get_il_type()` will
2674
// throw NotImplementedException as soon as we ask it to emit
2675
// ERROR. Report a located diagnostic at the function's site
2676
// and skip IL emission for this body. The function symbol
2677
// itself remains valid; any caller will already have its
2678
// own ERROR-typed result and will be handled by its own
2679
// visit. (See error.ghul:30-34 for the underlying rule.)
2680
if closure.return_type? /\ closure.return_type.is_error then
2681
_logger.error(function.location, "cannot infer return type here");
2682
add("ret");
2683
_context.enter_buffer(false);
2684
_context.reset_line_tracking();
2685
current_block.gen(_context);
2686
leave_block();
2687
closure.il_body = _context.leave_buffer();
2688
super.visit(function);
2689
closure.unmap_type_arguments();
2690
return;
2691
fi
2692
2693
_gen_closure_destructure_prologue(function, closure);
2694
2695
function.body.walk(self);
2696
2697
if
2698
closure.return_type? /\
2699
closure.return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME
2700
then
2701
add(".locals init ({closure.return_type!.get_il_type()} '.default')");
2702
add("ldloc '.default'");
2703
fi
2704
2705
add("// end IL body {closure}");
2706
2707
add("ret");
2708
2709
_context.enter_buffer(false);
2710
2711
_context.reset_line_tracking();
2712
current_block.gen(_context);
2713
2714
leave_block();
2715
2716
let body_assembler = _context.leave_buffer();
2717
2718
closure.il_body = body_assembler;
2719
2720
super.visit(function);
2721
2722
closure.unmap_type_arguments();
2723
si
2724
2725
// Declare and fill the names bound by any destructured lambda
2726
// parameter, before the body that reads them runs. The
2727
// parameter itself is one physical argument holding the
2728
// aggregate; its leaves are ordinary locals, so they need both
2729
// a `.locals init` slot and the unpacking stores - the same
2730
// pair `pre(Variables.VARIABLE)` and `visit(Variables.VARIABLE)`
2731
// emit for a named function's destructured formal argument.
2732
_gen_closure_destructure_prologue(
2733
function: Trees.Expressions.FUNCTION,
2734
closure: Semantic.Symbols.Closure
2735
) is
2736
for a in function.arguments.expressions do
2737
let argument = cast Trees.Expressions.VARIABLE?(a);
2738
2739
if !argument? \/ !argument.left? then
2740
continue;
2741
fi
2742
2743
let left = argument.left!;
2744
2745
let buffer = System.Text.StringBuilder();
2746
2747
for name in left.names! do
2748
let leaf = closure.find_direct(name.name);
2749
2750
if leaf? then
2751
leaf.gen_definition_header(buffer);
2752
fi
2753
od
2754
2755
if buffer.length > 0 then
2756
add(buffer.to_string());
2757
fi
2758
2759
let group_symbol = closure.find_direct(argument.name.name);
2760
2761
if group_symbol? then
2762
gen_destructuring_initialize(left, group_symbol.load(argument.location, null, _symbol_loader));
2763
fi
2764
od
2765
si
2766
2767
// Async closure: the lambda's $anon body becomes a builder-
2768
// create / Start / return-Task launch sequence stashed in
2769
// `closure.il_body`; the user body walks into a separate
2770
// move_next_block; the SM class is queued for class-tail
2771
// emission alongside named-function SMs.
2772
_emit_async_closure(
2773
function: Trees.Expressions.FUNCTION,
2774
closure: Semantic.Symbols.Closure,
2775
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE
2776
) is
2777
let frame = async_state_machine.frame;
2778
2779
if !frame? then
2780
_logger.error(function.location, "async function must return Tasks.TASK or Tasks.TASK[T]");
2781
return;
2782
fi
2783
2784
frame.declare();
2785
2786
// Body walk before launch IL — populates the frame's
2787
// await labels / locals / awaiters before launch
2788
// references any of them.
2789
async_state_machine.install_body_emission_overrides();
2790
2791
enter_scope(function);
2792
2793
let move_next_block = Values.BLOCK();
2794
2795
enter_block(move_next_block);
2796
2797
add(".maxstack 64");
2798
2799
// Same V_state caching + per-region dispatch pattern as
2800
// `_pre_async_function` (see its commentary). Awaits in
2801
// the closure body register with the outer holder unless
2802
// an inner `.try` pushes its own.
2803
let state_field_ref = frame.state_field.get_il_reference();
2804
let outer_dispatch_holder = _open_state_machine_entry(state_field_ref, "'.async_state'");
2805
2806
let success_label = IR.LABEL();
2807
let end_label = IR.LABEL();
2808
2809
let prev_async_sm = _current_async_state_machine;
2810
let prev_success = _current_async_success_label;
2811
let prev_end = _current_async_end_label;
2812
_current_async_state_machine = async_state_machine;
2813
_current_async_success_label = success_label;
2814
_current_async_end_label = end_label;
2815
2816
function.body.walk(self);
2817
2818
add("leave {success_label}");
2819
2820
_current_async_state_machine = prev_async_sm;
2821
_current_async_success_label = prev_success;
2822
_current_async_end_label = prev_end;
2823
2824
_pop_async_dispatch_holder(outer_dispatch_holder);
2825
2826
leave_block();
2827
leave_scope(function);
2828
2829
async_state_machine.uninstall_body_emission_overrides();
2830
2831
// --- Build launch IL into closure.il_body ---
2832
enter_block();
2833
add(".maxstack 64");
2834
2835
let construction_type_args = async_state_machine.get_construction_type_arguments();
2836
2837
let ctor_reference = _build_specialized_ctor_reference(
2838
frame,
2839
frame.constructor,
2840
construction_type_args,
2841
function.location,
2842
"while building ctor reference for async closure"
2843
);
2844
2845
let state_machine_type =
2846
_specialized_frame_type(frame, construction_type_args, function.location) ?? frame.type!;
2847
2848
// Ctor args: instance closures prepend ldarg.0 (the
2849
// closure-frame instance, which holds captures), then
2850
// each user-declared lambda parameter via ldarg '<name>'.
2851
let ctor_args_block = Values.BLOCK();
2852
if frame.outer_self_field? then
2853
ctor_args_block.add(IR.RAW("ldarg.0"));
2854
fi
2855
for arg_name in closure.argument_names do
2856
ctor_args_block.add(IR.RAW("ldarg '{arg_name}'"));
2857
od
2858
ctor_args_block.close();
2859
2860
let task_type = closure.return_type!;
2861
2862
let unspec_builder: Semantic.Types.Type? mut = null;
2863
let unspec_task: Semantic.Types.Type? mut = null;
2864
if !frame.is_void then
2865
unspec_builder = _innate_symbol_lookup.get_unspecialized_async_task_method_builder_type();
2866
unspec_task = _innate_symbol_lookup.get_unspecialized_task_type();
2867
fi
2868
2869
let launch = IR.Values.ASYNC_METHOD_LAUNCH(
2870
task_type,
2871
state_machine_type,
2872
ctor_reference,
2873
ctor_args_block,
2874
frame.state_field,
2875
frame.builder_field!,
2876
unspec_builder,
2877
unspec_task,
2878
"'.sm'"
2879
);
2880
2881
add(launch);
2882
add("ret");
2883
2884
_context.enter_buffer(false);
2885
_context.reset_line_tracking();
2886
current_block.gen(_context);
2887
leave_block();
2888
closure.il_body = _context.leave_buffer();
2889
2890
_pending_async_state_machines.add(
2891
PENDING_ASYNC_STATE_MACHINE(
2892
async_state_machine,
2893
move_next_block,
2894
success_label,
2895
end_label
2896
)
2897
);
2898
si
2899
2900
pre(statement: Expressions.STATEMENT) -> bool is
2901
super.pre(statement);
2902
2903
return false;
2904
si
2905
2906
visit(statement: Expressions.STATEMENT) is
2907
si
2908
2909
// `val ... lav` emits as the IL contained in block.value
2910
// (the BLOCK Value set by compile-expressions). The shape
2911
// mirrors how `if`/`case`-in-expression emit:
2912
//
2913
// <body statements' IL>
2914
// <tail value's IL> ; pushes value (or empty for void)
2915
// <return-targeted return inside body>: push value; br end
2916
// end: ; value (or empty) on the stack
2917
//
2918
// The natural fall-through emits the tail's value at the end
2919
// of the body and falls into the label. Return-targeted
2920
// returns push their value and `br` to the same label. No
2921
// CLR local or frame field carries the value across the
2922
// join — so an async/yield suspend point inside the body
2923
// composes with the existing state-machine rewriting
2924
// without bespoke handling.
2925
pre(block: Trees.Expressions.VAL_BLOCK) -> bool is
2926
super.pre(block);
2927
2928
if !block.value? \/ !isa Values.BLOCK(block.value) then
2929
// No usable value from compile-expressions (a
2930
// poisoned block, or a void-tolerant block whose
2931
// BLOCK has been set with void type — both still
2932
// need their body's IL to run for side effects).
2933
// Fall back to a plain walk; returns inside will
2934
// dispatch via val_block_target either way.
2935
return false;
2936
fi
2937
2938
let value_block = cast Values.BLOCK(block.value);
2939
2940
// Capture-vs-spill decision: in a state machine whose
2941
// body contains a suspend, the val-block's result
2942
// routes through a frame field instead of being
2943
// captured inline in value_block. Body IL (including
2944
// suspend emission and val-targeted-return stores)
2945
// flows to outer current_block; value_block ends up
2946
// holding just a `ldfld` so consumers see a clean load.
2947
let spiller = COMPOSITE_VALUE_SPILLER(
2948
self,
2949
block.value,
2950
_current_state_machine_frame(),
2951
_contains_suspend(block)
2952
);
2953
2954
spiller.enter();
2955
2956
try
2957
let end_label = LABEL();
2958
block.end_label = end_label;
2959
2960
let result_type = value_block.type;
2961
let is_void = result_type.matches(_innate_symbol_lookup.get_void_type());
2962
2963
let frame_il = VAL_BLOCK_IL(
2964
block,
2965
end_label,
2966
result_type,
2967
is_void,
2968
_loops.open_try_count,
2969
value_block
2970
);
2971
frame_il.spill_field = spiller.spill_field;
2972
2973
// Eagerly allocate the capture-mode cross-try TEMP
2974
// so its `.locals init` appears at the head of
2975
// value_block's IL stream, before any inner `.try`
2976
// the body might open. ilasm scopes locals
2977
// declared inside `.try {}` to that block; lazy
2978
// allocation mid-body would leave the post-body
2979
// ldloc without a visible declaration. The TEMP is
2980
// unused (and the join block un-emitted) when no
2981
// val-targeted return crosses an inner try — set
2982
// `cross_try_used` then drives the post-body
2983
// emission. Spill mode doesn't need either: the
2984
// frame field handles cross-try directly.
2985
if !is_void /\ !spiller.is_spilling then
2986
frame_il.cross_try_temp = IR.TEMP(value_block, "val_cross_try", value_block.type);
2987
frame_il.cross_try_join_label = LABEL();
2988
fi
2989
2990
_val_block_il_stack.add(frame_il);
2991
2992
try
2993
for s in block.body.statements do
2994
self.enter_node(s);
2995
try
2996
s.walk(self);
2997
finally
2998
self.leave_node(s);
2999
yrt
3000
od
3001
3002
// Natural fall-through. The tail statement is
3003
// the last EXPRESSION in the body; in want_value
3004
// mode visit(Statements.EXPRESSION) suppresses
3005
// its own emission so the consuming context can
3006
// emit the tail value. Val-block bypasses LIST,
3007
// so we emit it here — the spiller picks spill-
3008
// to-field or push-on-stack based on the mode
3009
// chosen above. Val-targeted returns use the
3010
// same emit_value path (see _gen_val_block_
3011
// targeted_return), so all paths converge at
3012
// end_label with the value on the stack (or in
3013
// the spill field) consistently.
3014
if let block.body.last?, last.value? then
3015
spiller.emit_value(value);
3016
fi
3017
finally
3018
assert _val_block_il_stack.count > 0 else "val_block_il_stack underflow";
3019
_val_block_il_stack.remove_at(_val_block_il_stack.count - 1);
3020
yrt
3021
3022
// Capture-mode cross-try paths stashed their value
3023
// into cross_try_temp and `leave`d to cross_try_
3024
// join_label. Skip over the join block on the
3025
// natural path (tail value already on the stack)
3026
// and emit it before end_label so the join paths
3027
// ldloc the value back on and fall through. Only
3028
// emit when the join was actually targeted —
3029
// otherwise the unconditional ldloc would push an
3030
// uninitialised value onto end_label's stack on
3031
// divergent natural paths.
3032
if frame_il.cross_try_used then
3033
get_brancher_for_block().branch(end_label);
3034
get_brancher_for_block().label(frame_il.cross_try_join_label!);
3035
add(frame_il.cross_try_temp!.load());
3036
fi
3037
3038
get_brancher_for_block().label(end_label);
3039
finally
3040
spiller.leave();
3041
yrt
3042
3043
return true;
3044
si
3045
3046
visit(block: Trees.Expressions.VAL_BLOCK) is
3047
si
3048
3049
// Override CALL.walk's default args-first order so SPILLs in
3050
// the receiver position emit before AWAIT_SUSPENDs in the
3051
// argument position — needed for source-order side effects
3052
// across instance-call receivers.
3053
pre(call: Trees.Expressions.CALL) -> bool is
3054
super.pre(call);
3055
3056
call.function.walk(self);
3057
call.arguments.walk(self);
3058
3059
return true;
3060
si
3061
3062
visit(`await: Trees.Expressions.AWAIT) is
3063
// Emit AWAIT_SUSPEND eagerly, stash the result into a
3064
// fresh frame field (NOT a CLR local — CLR locals reset
3065
// on every MoveNext re-entry), and rebind the wrapper to
3066
// a `ldfld <field>` load. The CLR-local TEMP is a
3067
// one-MoveNext bridge: we can't `ldarg.0` before
3068
// AWAIT_SUSPEND (would push across its internal labels),
3069
// so the suspend's result goes to a tmp first, then
3070
// `ldarg.0 / ldloc / stfld`.
3071
let await_wrapper = cast Values.WRAPPER?(`await.value);
3072
3073
if !await_wrapper? then
3074
return;
3075
fi
3076
3077
let casm = _current_async_state_machine;
3078
3079
if !casm? \/ current_function != casm.function then
3080
_logger.error(`await.location, "await outside async state-machine context");
3081
return;
3082
fi
3083
3084
let operand_value = `await.operand.value;
3085
if !operand_value? then return; fi
3086
let operand_type = operand_value.type;
3087
if !operand_type? then return; fi
3088
3089
let suspend =
3090
_build_await_suspend(operand_value, operand_type);
3091
3092
if !suspend? then
3093
return;
3094
fi
3095
3096
let frame = casm.frame;
3097
3098
assert frame? else "async state machine has no frame at await emission";
3099
3100
let result_type = await_wrapper.type;
3101
3102
assert result_type? else "await wrapper has no result type at IL generation";
3103
3104
let suspend_type = suspend.type;
3105
3106
if !suspend_type.is_void then
3107
let result_temp = TEMP(current_block, "await_bridge", suspend);
3108
let result_field = frame.declare_spill_field(result_type);
3109
add(IR.RAW("ldarg.0"));
3110
add(result_temp.load());
3111
add("stfld {result_field.get_il_reference()}");
3112
await_wrapper.value =
3113
IR.Values.Load.INSTANCE_FIELD(
3114
IR.Values.Load.REFERENCE_SELF(frame, frame.type),
3115
result_field
3116
);
3117
else
3118
// void-async — emit the suspend IL eagerly. The wrapper
3119
// already holds a void-typed DUMMY which surrounding
3120
// emit paths must skip (the type check in
3121
// visit(Statements.EXPRESSION) handles the only place
3122
// this matters in practice — bare `await E;`).
3123
add(suspend);
3124
fi
3125
si
3126
3127
visit(spill: Trees.Expressions.SPILL) is
3128
// Eagerly emit the operand's IL, then `stfld` to a fresh
3129
// frame field. The wrapper's value becomes a
3130
// `ldarg.0; ldfld <field>` load that surrounding IR
3131
// consumes lazily. Used by the SPILL_AWAITS pass to
3132
// persist intermediate values evaluated to the left of an
3133
// `await` so they survive the suspend.
3134
let spill_wrapper = cast Values.WRAPPER?(spill.value);
3135
3136
if !spill_wrapper? then
3137
return;
3138
fi
3139
3140
let casm = _current_async_state_machine;
3141
3142
if !casm? \/ current_function != casm.function then
3143
_logger.error(spill.location, "SPILL outside async state-machine context");
3144
return;
3145
fi
3146
3147
let operand_value = spill.operand.value;
3148
if !operand_value? then return; fi
3149
let spill_type = operand_value.type;
3150
if !spill_type? then return; fi
3151
3152
let frame = casm.frame;
3153
3154
assert frame? else "async state machine has no frame at spill emission";
3155
3156
let spill_field = frame.declare_spill_field(spill_type);
3157
3158
add(IR.RAW("ldarg.0"));
3159
add(operand_value);
3160
add("stfld {spill_field.get_il_reference()}");
3161
3162
spill_wrapper.value =
3163
IR.Values.Load.INSTANCE_FIELD(
3164
IR.Values.Load.REFERENCE_SELF(frame, frame.type),
3165
spill_field
3166
);
3167
si
3168
3169
visit(`cast: Trees.Expressions.CAST) is
3170
let cast_wrapper = cast Values.WRAPPER?(`cast.value);
3171
3172
if !cast_wrapper? then return; fi
3173
3174
let right_value = `cast.right.value;
3175
if !right_value? then return; fi
3176
let cast_type = cast_wrapper.type;
3177
if !cast_type? then return; fi
3178
3179
// Use the WRAPPER's type, not `cast.type_expression.type`
3180
// — for a bare-variant cast on a generic union (a `cast V(x)`
3181
// or the `cast` produced by `if let v: V = x` lowering),
3182
// compile_expressions.visit(CAST) already substituted the
3183
// receiver-specialized variant type, but the user's
3184
// type_expression still reads as the open generic.
3185
cast_wrapper.value =
3186
_type_caster
3187
.cast_value(
3188
`cast.location,
3189
right_value,
3190
cast_type,
3191
true
3192
);
3193
si
3194
3195
visit(assign: Statements.ASSIGNMENT) is
3196
super.visit(assign);
3197
3198
let v = assign.value;
3199
if v? then
3200
add(v);
3201
fi
3202
si
3203
3204
visit(expression: Statements.EXPRESSION) is
3205
super.visit(expression);
3206
3207
try
3208
if !expression.want_value /\ expression.expression.value? then
3209
let value = expression.expression.value;
3210
3211
let is_void = value.type? /\ value.type!.matches(_innate_symbol_lookup.get_void_type());
3212
3213
// Bare `await E;` with void result: visit(AWAIT)
3214
// emitted the suspend eagerly; re-emitting the
3215
// wrapper would call gen on its unreplaced DUMMY.
3216
if !is_void \/ !isa Values.WRAPPER(value) then
3217
add(value);
3218
fi
3219
3220
if !is_void then
3221
add("pop");
3222
fi
3223
fi
3224
catch e: System.Exception
3225
_logger.exception(expression.location, e, "caught exception generating IL for expression");
3226
yrt
3227
si
3228
3229
pre(variable: Variables.VARIABLE) -> bool is
3230
super.pre(variable);
3231
3232
if variable.want_dispose then
3233
// variable will be declared outside the enclosing .try
3234
// so don't declare it here
3235
return false;
3236
fi
3237
3238
// Generator / async body locals: `state_machine_field`
3239
// was wired up in compile-expressions (so closures
3240
// freezing inside the body capture the right
3241
// ldarg.0/ldfld IL). Skip emitting `.locals init` here;
3242
// load/store routes through the frame field via
3243
// state_machine_field.
3244
let cf = current_function;
3245
if cf? then
3246
let sm_for_function = Semantic.Symbols.state_machine_for(cf);
3247
3248
if sm_for_function? /\ sm_for_function.frame? then
3249
return false;
3250
fi
3251
3252
let async_sm = Semantic.Symbols.async_state_machine_for(cf);
3253
3254
if async_sm? /\ async_sm.frame? then
3255
return false;
3256
fi
3257
fi
3258
3259
let buffer = System.Text.StringBuilder();
3260
let globals_namespace: Semantic.Symbols.NAMESPACE? mut = null;
3261
3262
for name in variable.names do
3263
let symbol = find(name);
3264
3265
if !symbol? then
3266
continue;
3267
fi
3268
3269
let global_variable = cast Semantic.Symbols.GLOBAL_VARIABLE?(symbol);
3270
3271
if global_variable? /\ !globals_namespace? then
3272
globals_namespace = cast Semantic.Symbols.NAMESPACE?(global_variable.owner)!;
3273
fi
3274
3275
symbol.gen_definition_header(buffer);
3276
3277
// For a field whose tree carries a reference-`?`
3278
// position, attach NullableAttribute to the just-
3279
// emitted .field directive so reflection recovers the
3280
// annotation. ILASM associates a `.custom` line that
3281
// immediately follows a `.field` with that field; the
3282
// leading `\n` keeps the directive on its own line.
3283
// Local variables and parameters don't reach here
3284
// (the AST node is only used at namespace/class scope
3285
// for field-shaped declarations).
3286
let field_symbol = cast Semantic.Symbols.Field?(symbol);
3287
3288
if field_symbol? then
3289
let line = Semantic.DotNet.NULLABILITY.gen_attribute_line_for_type(field_symbol.type!);
3290
3291
if line? then
3292
buffer.append('\n');
3293
buffer.append(line);
3294
fi
3295
fi
3296
od
3297
3298
if buffer.length > 0 then
3299
if _block_context.is_in_block then
3300
current_block.add(buffer.to_string());
3301
else
3302
if globals_namespace? then
3303
gen_globals_class_open(globals_namespace);
3304
fi
3305
3306
println(buffer.to_string());
3307
3308
if globals_namespace? then
3309
gen_globals_class_close();
3310
fi
3311
fi
3312
fi
3313
return false;
3314
si
3315
3316
visit(v: Variables.VARIABLE) is
3317
let initializer = v.initializer;
3318
if initializer? then
3319
let init_value = initializer.value;
3320
if init_value? then
3321
gen_destructuring_initialize(v.left, init_value);
3322
return;
3323
fi
3324
fi
3325
3326
// Destructured formal argument: no initializer expression -
3327
// the "source" to destructure is the synthesised physical
3328
// parameter declare-members attached to this VARIABLE.
3329
if v.is_argument /\ !v.left.is_simple_name then
3330
let group_symbol = symbol_for(v);
3331
3332
if group_symbol? then
3333
gen_destructuring_initialize(v.left, group_symbol.load(v.location, null, _symbol_loader));
3334
fi
3335
3336
return;
3337
fi
3338
3339
// No initializer, but the local is captured by a closure
3340
// and reassigned, so `mark-boxed-locals` promoted its slot
3341
// to `Ghul.BOX[T]`. Allocate the empty box at declaration —
3342
// `Variable.store` routes through `_store_boxed_local`,
3343
// which emits `newobj Ghul.BOX[T]::.ctor()` when the value
3344
// is null. Without this the slot stays null, then the
3345
// closure frame's ctor stashes a null box reference and
3346
// every later read or write NREs.
3347
if v.left.is_simple_name then
3348
// is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null
3349
let name = v.left.name!;
3350
let symbol = find(name);
3351
3352
if
3353
symbol? /\
3354
isa Semantic.Symbols.Variable(symbol) /\
3355
(cast Semantic.Symbols.Variable(symbol)).is_boxed
3356
then
3357
let store = symbol.store(v.left.location, null, null, _symbol_loader, true);
3358
add(store);
3359
return;
3360
fi
3361
fi
3362
3363
// Loop-scoped `let x: T;` (no initializer) needs an
3364
// explicit default store: .NET only initialises locals
3365
// on method entry, so without re-zeroing each iteration
3366
// the variable carries its previous iteration's value
3367
// (issue #483). Outside a loop, the method-entry
3368
// initialisation is correct and we leave the existing
3369
// behaviour alone. Catch handlers receive their value
3370
// from the runtime — skip the default-store there too.
3371
if _loops.is_in_loop /\ !_in_catch_variable then
3372
gen_destructuring_default(v.left);
3373
fi
3374
si
3375
3376
// Drill a Type to its underlying Classy (peeling
3377
// Symbols.GENERIC wrapping for specialised generics) — same
3378
// shape as CONDITION_ANALYZER.get_classy_for_narrowing but
3379
// inlined here so generate_il doesn't need a back-reference
3380
// to compile-phase services. Returns null when `t` isn't
3381
// a NAMED of a Classy.
3382
_classy_of_type(t: Semantic.Types.Type) -> Semantic.Symbols.Classy? is
3383
if !isa Semantic.Types.NAMED(t) then
3384
return null;
3385
fi
3386
3387
let named = cast Semantic.Types.NAMED(t);
3388
let symbol mut = named.symbol;
3389
3390
if isa Semantic.Symbols.GENERIC(symbol) then
3391
symbol = symbol.symbol;
3392
fi
3393
3394
return cast Semantic.Symbols.Classy?(symbol)!;
3395
si
3396
3397
// `if let` clauses' bound names are declared as CLR locals
3398
// here — same as `pre(VARIABLE)` does for a plain let. The
3399
// store IL itself is emitted later by
3400
// `gen_destructuring_initialize` in the if-branch handler.
3401
pre(rb: Statements.REFUTABLE_BINDING) -> bool is
3402
let cf = current_function;
3403
if cf? then
3404
let sm_for_function = Semantic.Symbols.state_machine_for(cf);
3405
3406
if sm_for_function? /\ sm_for_function.frame? then
3407
return false;
3408
fi
3409
3410
let async_sm = Semantic.Symbols.async_state_machine_for(cf);
3411
3412
if async_sm? /\ async_sm.frame? then
3413
return false;
3414
fi
3415
fi
3416
3417
let buffer = System.Text.StringBuilder();
3418
3419
for c in rb.clauses do
3420
let pattern = c.pattern;
3421
3422
for name in pattern.names! do
3423
let symbol = find(name);
3424
3425
if !symbol? then
3426
continue;
3427
fi
3428
3429
symbol.gen_definition_header(buffer);
3430
od
3431
od
3432
3433
if buffer.length > 0 /\ _block_context.is_in_block then
3434
current_block.add(buffer.to_string());
3435
fi
3436
3437
return false;
3438
si
3439
3440
visit(rb: Statements.REFUTABLE_BINDING) is
3441
si
3442
3443
gen_destructuring_default(left: Variables.VariableLeft) is
3444
if left.is_simple_name then
3445
// is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null
3446
let name = left.name!;
3447
let symbol = find(name);
3448
3449
if !symbol? \/ symbol.is_argument then
3450
return;
3451
fi
3452
let symbol_type = symbol.type;
3453
if !symbol_type? then return; fi
3454
3455
let default_value = IR.Values.DEFAULT(symbol_type);
3456
3457
let store = symbol.store(left.location, null, default_value, _symbol_loader, true);
3458
3459
add(store);
3460
else
3461
for e in left.elements! do
3462
gen_destructuring_default(e);
3463
od
3464
fi
3465
si
3466
3467
// TODO use Pre/Visit instead
3468
gen_destructuring_initialize(left: Variables.VariableLeft, value: Value) is
3469
gen_destructuring_initialize(left, value, null);
3470
si
3471
3472
// Per-element narrowing: when `narrow_branch_label` is set
3473
// (the `if let` path), an element carrying a `type_expression`
3474
// emits `isinst T; brfalse next` before binding. The cast
3475
// result is what gets stored / recursively destructured, so
3476
// the bound slot is typed T. Plain `let` and `for` pass null
3477
// for the label and just store the raw value (the declared
3478
// element type is a compile-time assertion only, not a runtime
3479
// test).
3480
gen_destructuring_initialize(
3481
left: Variables.VariableLeft,
3482
value: Value,
3483
narrow_branch_label: LABEL?
3484
) is
3485
// Literal-leaf inside a destructure pattern — runtime
3486
// value-equality test against the source position. Only
3487
// valid in refutable contexts (`if let` / `case`-when);
3488
// a plain `let` with a literal leaf is rejected during
3489
// semantic analysis, so by the time we get here a literal
3490
// leaf without a narrow_branch_label is a no-op (the
3491
// emit-nothing path).
3492
if isa Variables.LITERAL_VARIABLE_LEFT(left) then
3493
if narrow_branch_label? then
3494
let literal_leaf = cast Variables.LITERAL_VARIABLE_LEFT(left);
3495
3496
literal_leaf.expression.walk(self);
3497
3498
let leaf_value = literal_leaf.expression.value;
3499
if leaf_value? then
3500
get_brancher_for_block().branch(
3501
BRANCH.NE,
3502
value,
3503
leaf_value,
3504
narrow_branch_label,
3505
"literal-leaf"
3506
);
3507
fi
3508
fi
3509
3510
return;
3511
fi
3512
3513
let narrowed mut = value;
3514
3515
let type_expression = left.type_expression;
3516
if
3517
type_expression? /\
3518
type_expression.type? /\
3519
narrow_branch_label?
3520
then
3521
let cast_value = IR.Values.CAST(type_expression.type!, value);
3522
let temp = TEMP(current_block, "narrow", cast_value);
3523
3524
get_brancher_for_block().branch(BRANCH.Z, temp.load(), narrow_branch_label, "narrow");
3525
3526
narrowed = temp.load();
3527
fi
3528
3529
if left.is_simple_name then
3530
// is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null
3531
let name = left.name!;
3532
let symbol = find(name);
3533
3534
if !symbol? then
3535
return;
3536
fi
3537
3538
let store = symbol.store(left.location, null, narrowed, _symbol_loader, true);
3539
3540
add(store);
3541
else
3542
let from_type = narrowed.type;
3543
3544
if !from_type? then return; fi
3545
3546
// !is_simple_name => a destructure group, whose elements are non-null
3547
let elements = left.elements!;
3548
3549
let get_from =
3550
if narrowed.is_lightweight_pure then
3551
() => narrowed
3552
else
3553
let temp = TEMP(current_block, "destructure", narrowed);
3554
() => temp.load();
3555
fi;
3556
3557
let is_named_group = elements.count > 0 /\ elements[0].source_field_name?;
3558
3559
let strategy =
3560
if is_named_group then
3561
let field_names = Collections.LIST[string?]();
3562
for e in elements do
3563
let sfn = e.source_field_name;
3564
field_names.add(if sfn? then sfn.name else null fi);
3565
od
3566
DESTRUCTURE_RESOLVER.resolve_strategy_by_name(from_type, field_names);
3567
else
3568
DESTRUCTURE_RESOLVER.resolve_strategy(from_type, elements.count);
3569
fi;
3570
3571
if strategy.is_deconstruct then
3572
// is_deconstruct ⇔ deconstruct_function? per its definition
3573
let deconstruct = strategy.deconstruct_function!;
3574
let arg_temps = Collections.LIST[IR.TEMP]();
3575
let call_args = Collections.LIST[Value]();
3576
3577
for i in 0..deconstruct.arguments.count do
3578
let ref_type = deconstruct.arguments[i];
3579
let element_type = ref_type.get_element_type();
3580
assert element_type? else "deconstruct arg type has no element type";
3581
let arg_temp = IR.TEMP(current_block, "destructure_arg", i, element_type);
3582
3583
arg_temps.add(arg_temp);
3584
call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type));
3585
od
3586
3587
let call_value =
3588
deconstruct.call(
3589
left.location,
3590
get_from(),
3591
call_args,
3592
null,
3593
_function_caller
3594
);
3595
3596
add(call_value);
3597
3598
for (i, e) in elements |> index() do
3599
gen_destructuring_initialize(e, arg_temps[i].load(), narrow_branch_label);
3600
od
3601
else
3602
let members = strategy.members;
3603
3604
for (i, e) in elements |> index() do
3605
let member = members[i];
3606
3607
if member? then
3608
let member_value = member.load(LOCATION.internal, get_from(), _symbol_loader);
3609
3610
gen_destructuring_initialize(e, member_value, narrow_branch_label);
3611
fi
3612
od
3613
fi
3614
fi
3615
si
3616
3617
visit(r: Statements.RETURN) is
3618
super.visit(r);
3619
3620
// Val-block-targeted return: push the value onto the
3621
// evaluation stack and `br` to the block's end_label
3622
// rather than emitting `ret`. The val-block frame is on
3623
// the IL stack with the same AST node identity stamped
3624
// on r.val_block_target.
3625
if let target = r.val_block_target then
3626
_gen_val_block_targeted_return(r, target);
3627
return;
3628
fi
3629
3630
let function = current_function;
3631
3632
// State-machine async return: stash the value into
3633
// `_result` (value-async only) and `leave` to the
3634
// success label so the MoveNext trailer fires
3635
// `builder.SetResult` and returns. The standard
3636
// `ret` path below would unwind MoveNext without
3637
// signalling completion to the builder.
3638
let casm = _current_async_state_machine;
3639
let success_label = _current_async_success_label;
3640
3641
if
3642
casm? /\
3643
success_label? /\
3644
function == casm.function
3645
then
3646
let frame = casm.frame;
3647
3648
assert frame? else "async state machine has no frame at return emission";
3649
3650
let expression = r.expression;
3651
if expression? then
3652
let expr_value = expression.value;
3653
let result_field = frame.result_field;
3654
let class_result_type = frame.class_result_type;
3655
if expr_value? /\ result_field? /\ class_result_type? then
3656
let boxed_value =
3657
_boxer.box_if_needed(
3658
expr_value,
3659
class_result_type
3660
);
3661
add("ldarg.0");
3662
add(boxed_value);
3663
add("stfld {result_field.get_il_reference()}");
3664
fi
3665
fi
3666
3667
add("leave {success_label}");
3668
return;
3669
fi
3670
3671
let value: Value? mut = null;
3672
let return_type = if function? then function.return_type else null fi;
3673
3674
let expression = r.expression;
3675
if
3676
expression? /\
3677
return_type?
3678
then
3679
let expr_value = expression.value;
3680
if expr_value? /\ expr_value.type? then
3681
value =
3682
_boxer.box_if_needed(
3683
expr_value,
3684
return_type
3685
);
3686
fi
3687
fi
3688
3689
// Bare `return;` from a non-void function: push the
3690
// default value of the return type. Matches the fall-
3691
// off-end emission below and honours the warning at
3692
// compile_bindings.pre_return ("return without value
3693
// from non void function returns default value of type
3694
// {T}"). Without this, `ret` runs with nothing on the
3695
// stack and the JIT rejects the IL with
3696
// `InvalidProgramException`.
3697
if
3698
!value? /\
3699
return_type? /\
3700
return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME
3701
then
3702
value = IR.Values.DEFAULT(return_type);
3703
fi
3704
3705
let current_try = _loops.get_current_try();
3706
3707
if current_try? then
3708
if value? then
3709
current_try.return_value!.store(value);
3710
fi
3711
3712
current_try.return_needed!.store(Literal.NUMBER("1", _innate_symbol_lookup.get_bool_type(), "i4"));
3713
3714
let brancher = get_brancher_for_block();
3715
3716
if current_try.is_in_finally then
3717
brancher.branch(current_try.middle);
3718
else
3719
brancher.leave(current_try.start);
3720
fi
3721
else
3722
if value? then
3723
add(value);
3724
fi
3725
3726
add("ret");
3727
fi
3728
si
3729
3730
// Emit IL for a `return E` whose target is a `val ... lav`
3731
// block (innermost wins, recorded by compile-expressions on
3732
// the RETURN AST node). The frame on `_val_block_il_stack`
3733
// matches `target` by identity. Three shapes vary by where
3734
// the value lives across the exit:
3735
//
3736
// Capture mode, no inner `.try`: push the value onto the
3737
// evaluation stack and `br` to the frame's end_label —
3738
// same shape `if` / `case`-in-expression branches use.
3739
//
3740
// Spill mode (val-block body contains an `await` / `yield`,
3741
// so VAL_BLOCK.pre allocated a spill field on the state-
3742
// machine frame): `stfld` the value into that field. The
3743
// stack ends up empty so `leave` is well-formed even
3744
// across an inner `.try`; the val-block's BLOCK contents
3745
// become a `ldfld` of the same field, so all paths
3746
// converge with the value in the field.
3747
//
3748
// Capture mode crossing an inner `.try`: a `br` across a
3749
// protected region is invalid IL; `leave` requires an
3750
// empty stack we can't deliver while carrying the value.
3751
// Mirrors the function-return-from-try pattern — stash
3752
// the value into the eagerly-allocated CLR-local TEMP on
3753
// the frame, `leave` to a cross-try join label that sits
3754
// outside any inner try, `ldloc` the TEMP at the join,
3755
// and fall through into end_label with the value on the
3756
// stack. Void val-blocks skip the TEMP and `leave`
3757
// end_label directly.
3758
_gen_val_block_targeted_return(r: Statements.RETURN, target: Trees.Expressions.VAL_BLOCK) is
3759
let frame: VAL_BLOCK_IL? mut = null;
3760
3761
let i mut = _val_block_il_stack.count - 1;
3762
while i >= 0 do
3763
let candidate = _val_block_il_stack[i];
3764
if candidate.block == target then
3765
frame = candidate;
3766
i = -1;
3767
else
3768
i = i - 1;
3769
fi
3770
od
3771
3772
assert frame? else "val_block_target stamped on return has no matching IL frame";
3773
3774
// A `.try` opened INSIDE the val-block body is still
3775
// open at this return — `_loops.open_try_count`
3776
// exceeds the count we cached at val-block entry.
3777
let crosses_try = _loops.open_try_count > frame.enclosing_try_count;
3778
3779
// Resolve the value to deliver. Bare `return;` in a
3780
// value-required val-block defers its diagnostic to
3781
// visit_return (inferred-return lambdas need the body
3782
// walk to settle first); emit the result type's
3783
// default so the IL verifies — matches the bare-
3784
// return-from-non-void-function path at visit(RETURN).
3785
let value: IR.Values.Value? mut = null;
3786
if !frame.is_void /\ frame.result_type? then
3787
let result_type = frame.result_type;
3788
3789
if let expression = r.expression then
3790
if let v = expression.value then
3791
value = _boxer.box_if_needed(v, result_type);
3792
else
3793
value = IR.Values.DEFAULT(result_type);
3794
fi
3795
else
3796
value = IR.Values.DEFAULT(result_type);
3797
fi
3798
fi
3799
3800
if let spill_field = frame.spill_field then
3801
// Spill mode: stfld into the val-block's spill
3802
// field. Stack is empty after stfld so `leave`
3803
// and `br` are both valid; use `leave` only when
3804
// crossing an inner try (where `br` would be
3805
// invalid IL).
3806
add(IR.RAW("ldarg.0"));
3807
if let v = value then
3808
add(v);
3809
fi
3810
add("stfld {spill_field.get_il_reference()}");
3811
3812
if crosses_try then
3813
get_brancher_for_block().leave(frame.end_label);
3814
else
3815
get_brancher_for_block().branch(frame.end_label);
3816
fi
3817
elif !crosses_try then
3818
// Capture mode no-cross-try: push the value and
3819
// `br` end_label — the join shape if / case use.
3820
if let v = value then
3821
add(v);
3822
fi
3823
get_brancher_for_block().branch(frame.end_label);
3824
elif !value? then
3825
// Capture mode cross-try, void val-block: nothing
3826
// to carry, `leave end_label` directly. (end_label
3827
// sits outside any inner try because the frame
3828
// cached enclosing_try_count.)
3829
get_brancher_for_block().leave(frame.end_label);
3830
else
3831
// Capture mode cross-try, value val-block: stash
3832
// value into the pre-allocated cross_try_temp,
3833
// `leave` to cross_try_join_label. VAL_BLOCK.pre's
3834
// post-body emission `ldloc`s the TEMP at the join
3835
// and falls into end_label.
3836
assert frame.cross_try_temp? else "capture-mode val-block missing cross-try TEMP";
3837
assert frame.cross_try_join_label? else "capture-mode val-block missing cross-try join label";
3838
3839
frame.cross_try_used = true;
3840
frame.cross_try_temp!.store(value);
3841
get_brancher_for_block().leave(frame.cross_try_join_label!);
3842
fi
3843
si
3844
3845
// Generator yield IL emission:
3846
//
3847
// ldarg.0
3848
// <X's IR> // expression value (boxed-if-needed
3849
// // against the element type)
3850
// stfld _current
3851
// ldarg.0
3852
// ldc.i4 N // freshly-allocated state number
3853
// stfld _state
3854
// ldc.i4.1
3855
// ret
3856
// Label_N: // resumption point recorded so the
3857
// // entry dispatch can jump here on
3858
// // a subsequent MoveNext call
3859
//
3860
// The state number / label pair is recorded on the enclosing
3861
// generator function's STATE_MACHINE so `_visit_generator_
3862
// function` can populate the entry-dispatch placeholder
3863
// after the body walk completes.
3864
visit(`yield: Statements.YIELD) is
3865
super.visit(`yield);
3866
3867
let function = current_function;
3868
let state_machine = Semantic.Symbols.state_machine_for(function);
3869
3870
if !state_machine? then
3871
return;
3872
fi
3873
3874
let frame = state_machine.frame;
3875
3876
assert frame? else "generator state machine has no frame at IL generation";
3877
3878
let state = state_machine.allocate_state();
3879
let resume = LABEL();
3880
3881
state_machine.record_label(state, resume);
3882
3883
_register_resume_with_dispatch(state, resume);
3884
3885
let current_field_ref = frame.current_field.get_il_reference();
3886
let state_field_ref = frame.state_field.get_il_reference();
3887
3888
// Store yielded value into _current.
3889
add("ldarg.0");
3890
3891
if `yield.expression.value? then
3892
let value =
3893
_boxer.box_if_needed(
3894
`yield.expression.value,
3895
frame.element_type
3896
);
3897
3898
add(value);
3899
fi
3900
3901
add("stfld {current_field_ref}");
3902
3903
// Stamp the resumption state — keep the CLR local
3904
// `'.gen_state'` in sync so state-guarded finally
3905
// bodies see the suspending value when our `leave`
3906
// (below) fires them.
3907
_emit_state_stamp("ldc.i4 {state}", "'.gen_state'", state_field_ref);
3908
3909
// Suspend. From INSIDE an inner `.try`, `ret` is
3910
// invalid IL; emit `leave` to a shared return-true label
3911
// at MoveNext's outer scope. From OUTSIDE any inner try
3912
// (only the outer dispatch holder open), `ret` is fine.
3913
if _async_dispatch_stack.count > 1 then
3914
if !_current_generator_yield_return_label? then
3915
_current_generator_yield_return_label = LABEL();
3916
fi
3917
add("leave {_current_generator_yield_return_label}");
3918
else
3919
add("ldc.i4.1");
3920
add("ret");
3921
fi
3922
3923
add("{resume}:");
3924
3925
// Cold-resume cleanup: clear state to -1 (and the local)
3926
// so a subsequent `leave` (next yield's suspend, or the
3927
// end-of-try leave on exhaustion) sees the "not
3928
// suspending" sentinel. Without this, the finally body's
3929
// state guard would still see the previous yield's state
3930
// and skip cleanup forever.
3931
_emit_state_stamp("ldc.i4.m1", "'.gen_state'", state_field_ref);
3932
si
3933
3934
// Emit `ldarg.0; <value_load_il>; dup; stloc <state_local_il>;
3935
// stfld <state_field_ref>` — write a state value to BOTH the
3936
// CLR local cached at MoveNext entry AND the state-machine
3937
// frame's state field, so later `leave`s out of `.try` blocks
3938
// fire finally bodies that see the correct sentinel via the
3939
// local. `value_load_il` is "ldc.i4 N" for a suspend or
3940
// "ldc.i4.m1" for the not-suspending sentinel.
3941
_emit_state_stamp(value_load_il: string, state_local_il: string, state_field_ref: string) is
3942
add("ldarg.0");
3943
add(value_load_il);
3944
add("dup");
3945
add("stloc {state_local_il}");
3946
add("stfld {state_field_ref}");
3947
si
3948
3949
visit(`throw: Statements.THROW) is
3950
super.visit(`throw);
3951
3952
add(
3953
_boxer.box_if_value(
3954
`throw.expression!.value!
3955
)
3956
);
3957
3958
add("throw");
3959
si
3960
3961
// Build an AWAIT_SUSPEND for `awaited`: allocates a state
3962
// number + cold/hot resume labels + a fresh `$awaiter_N`
3963
// field, and records the (state, awaiter, label) triple
3964
// for entry-dispatch population. Returns null when the
3965
// awaiter type cannot be resolved.
3966
_build_await_suspend(awaited: Value, awaited_type: Semantic.Types.Type) -> IR.Values.AWAIT_SUSPEND? is
3967
let async_sm = _current_async_state_machine!;
3968
let frame = async_sm.frame;
3969
3970
assert frame? else "async state machine has no frame at await suspend emission";
3971
3972
let element_type =
3973
Semantic.Symbols.TYPE_ARGUMENT_EXTRACTOR.extract(
3974
awaited_type,
3975
_innate_symbol_lookup.get_unspecialized_task_type()
3976
);
3977
3978
let awaiter_type_opt: Semantic.Types.Type? mut;
3979
let result_type: Semantic.Types.Type mut;
3980
3981
if element_type? then
3982
awaiter_type_opt = _innate_symbol_lookup.get_task_awaiter_type(element_type);
3983
result_type = element_type;
3984
else
3985
awaiter_type_opt = _innate_symbol_lookup.get_task_awaiter_void_type();
3986
result_type = _innate_symbol_lookup.get_void_type();
3987
fi
3988
3989
if !awaiter_type_opt? then
3990
return null;
3991
fi
3992
3993
let awaiter_type = awaiter_type_opt;
3994
let state = async_sm.allocate_state();
3995
let cold_resume = IR.LABEL();
3996
let hot_resume = IR.LABEL();
3997
let awaiter_field = frame.declare_awaiter_field(awaiter_type);
3998
3999
async_sm.record_label(state, awaiter_field, cold_resume);
4000
4001
_register_resume_with_dispatch(state, cold_resume);
4002
4003
let unspec_awaiter_type =
4004
if element_type? then
4005
// reaching async IL emit implies ghul-runtime resolved
4006
_innate_symbol_lookup.get_unspecialized_task_awaiter_type()!;
4007
else
4008
awaiter_type;
4009
fi;
4010
4011
return IR.Values.AWAIT_SUSPEND(
4012
result_type,
4013
awaited,
4014
awaited_type,
4015
awaiter_type,
4016
unspec_awaiter_type,
4017
state,
4018
cold_resume,
4019
hot_resume,
4020
_current_async_end_label!,
4021
_specialized_frame_type(frame, async_sm.get_construction_type_arguments(), awaited.location) ?? frame.type!,
4022
frame.state_field,
4023
awaiter_field,
4024
frame.builder_field!
4025
);
4026
si
4027
4028
// Emit the conditional-throw IL shared between statement-form
4029
// `assert` and the expression-form `assert ... in expr`. Condition
4030
// value is loaded by `brancher.branch`; on the failure path the
4031
// message (or default condition-printout) is loaded, optionally
4032
// wrapped in an AssertFailedException, and thrown. Falls through
4033
// at the success label.
4034
emit_assertion(
4035
location: Source.LOCATION,
4036
condition_value: IR.Values.Value,
4037
message: Trees.Expressions.Expression?,
4038
default_message_source: object
4039
) is
4040
let brancher = get_brancher_for_block();
4041
4042
let end = LABEL();
4043
4044
brancher.branch(BRANCH.NZ, condition_value, end);
4045
4046
let need_exception_wrapper mut = true;
4047
4048
if message? then
4049
let message_value = message.value;
4050
if !_innate_symbol_lookup.get_exception_type().is_assignable_from(message_value!.type!) then
4051
add(
4052
Literal.STRING(
4053
"{location}: ",
4054
_innate_symbol_lookup.get_string_type()
4055
)
4056
);
4057
4058
add(message_value!);
4059
4060
add("call string string::Concat(object,object)");
4061
else
4062
add(message_value!);
4063
need_exception_wrapper = false;
4064
fi
4065
else
4066
add(
4067
Literal.STRING(
4068
"{location}: {default_message_source}",
4069
_innate_symbol_lookup.get_string_type()
4070
)
4071
);
4072
fi
4073
4074
if need_exception_wrapper then
4075
add("newobj instance void class ['ghul-runtime']Ghul.AssertFailedException::.ctor(class [System.Runtime]System.String)");
4076
fi
4077
4078
add("throw");
4079
4080
brancher.label(end);
4081
si
4082
4083
visit(`assert: Statements.ASSERT) is
4084
super.visit(`assert);
4085
4086
emit_assertion(
4087
`assert.location,
4088
`assert.expression.value!,
4089
`assert.message,
4090
`assert.expression
4091
);
4092
si
4093
4094
pre(assert_in: Expressions.ASSERT_IN) -> bool is
4095
super.pre(assert_in);
4096
4097
assert_in.condition.walk(self);
4098
4099
if assert_in.message? then
4100
assert_in.message.walk(self);
4101
fi
4102
4103
emit_assertion(
4104
assert_in.location,
4105
assert_in.condition.value!,
4106
assert_in.message,
4107
assert_in.condition
4108
);
4109
4110
assert_in.expression.walk(self);
4111
4112
return true;
4113
si
4114
4115
visit(assert_in: Expressions.ASSERT_IN) is
4116
assert_in.compile_expressions_state.value = assert_in.expression.value;
4117
si
4118
4119
pre(list: Statements.LIST) -> bool is
4120
super.pre(list);
4121
return true;
4122
si
4123
4124
visit(list: Statements.LIST) is
4125
// BLOCK-with-suspend spill. When the LIST is in a value-
4126
// required position (list.value non-null) AND we're in
4127
// a state machine AND any statement (outside nested
4128
// function literals) contains an `await` or `yield`, the
4129
// captured-IL pattern would trap the suspend's `leave` /
4130
// `ret` inside list.value's BLOCK; when the consumer
4131
// later gens that BLOCK, the suspend replays wrapped in
4132
// the consumer's stack setup (receiver on stack at leave
4133
// is invalid IL; state lost across yield's MoveNext re-
4134
// entry breaks the next stfld). Spill the BLOCK's value
4135
// to a frame field: body IL (including the suspend)
4136
// flows to outer current_block in the same flat MoveNext-
4137
// body stream as the dispatcher; the BLOCK is rewritten
4138
// to a load of the spill field. The recursive case (val-
4139
// block inside `if` in expression position, etc.) works
4140
// because each composite's spill produces a clean load
4141
// for its enclosing composite.
4142
//
4143
// The `want_dispose` (RAII) path keeps the existing .try
4144
// wrapping — its body must live inside the protected
4145
// region.
4146
if _try_spill_block_with_suspend(list) then
4147
return;
4148
fi
4149
4150
if list.value? then
4151
enter_block(cast IR.Values.BLOCK?(list.value)!)
4152
fi;
4153
4154
let return_type = current_function!.return_type;
4155
4156
let return_needed: TEMP? mut;
4157
let return_value: TEMP? mut = _;
4158
let label: LOOP_LABELS? mut = _;
4159
4160
let outer_try: LOOP_LABELS? mut = _;
4161
4162
let brancher = get_brancher_for_block();
4163
4164
// A `.try` inside a state-machine body needs its own
4165
// dispatch holder (so awaits/yields inside register here,
4166
// keeping the `beq cold_resume_N` and the target label
4167
// in the same protected region) plus a state-guarded
4168
// finally body (skip dispose / user-cleanup on suspend
4169
// `leave`). Both works the same way for let-use and
4170
// user-written try; the helpers below no-op outside a
4171
// state machine.
4172
let dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null;
4173
4174
if list.want_dispose then
4175
(outer_try, return_needed, return_value) = get_exception_handler_temps();
4176
4177
for v in list.variables_to_dispose do
4178
let buffer = System.Text.StringBuilder();
4179
v.gen_definition_header(buffer);
4180
4181
add(buffer.to_string());
4182
od
4183
4184
label = _loops.enter_try(return_needed, return_value);
4185
4186
add(".try {{ // RAII ");
4187
4188
dispatch_holder = _maybe_push_dispatch_holder();
4189
fi
4190
4191
for s in list.statements do
4192
enter_node(s);
4193
try
4194
s.walk(self);
4195
finally
4196
leave_node(s);
4197
yrt
4198
od
4199
4200
if list.value? then
4201
if let list.last?, last.value? then
4202
add(value);
4203
fi
4204
fi
4205
4206
if list.want_dispose then
4207
let try_label = label!;
4208
4209
_maybe_pop_dispatch_holder(dispatch_holder);
4210
4211
ensure_runtime_symbols_are_materialized();
4212
4213
try_label.is_in_finally = true;
4214
4215
brancher.leave(try_label.start);
4216
4217
add("}}");
4218
add("finally {{");
4219
4220
let skip_dispose_label = _open_finally_state_guard();
4221
4222
let to_dispose = list.variables_to_dispose;
4223
4224
let i mut = to_dispose.count - 1;
4225
4226
while i >= 0 do
4227
let v = to_dispose[i];
4228
4229
let skip_label: LABEL? mut = null;
4230
4231
if !v.type!.is_value_type then
4232
skip_label = LABEL();
4233
4234
brancher.branch(BRANCH.Z, _symbol_loader.load_local_variable(list.location, v), skip_label);
4235
fi
4236
4237
let variable_value = v.load(list.location, null, _symbol_loader); // _symbol_loader.load_local_variable(list.location, v);
4238
let dispose_call = _dispose.call(list.location, variable_value, System.Array.empty`[Value](), null, _function_caller);
4239
4240
add(dispose_call);
4241
4242
if skip_label? then
4243
brancher.label(skip_label);
4244
fi
4245
4246
i = i - 1;
4247
od
4248
4249
_close_finally_state_guard(skip_dispose_label);
4250
4251
brancher.label(try_label.middle);
4252
4253
add("endfinally");
4254
add("}}");
4255
4256
_loops.leave_loop();
4257
4258
gen_exception_handler_exit(outer_try, try_label, return_value);
4259
fi
4260
4261
if list.value? then
4262
leave_block();
4263
fi
4264
si
4265
4266
// Emit the scrutinee evaluation, optional isinst cast, the
4267
// presence test, and the branch-to-fail for one `if let`
4268
// clause. Returns the temp holding the (possibly cast)
4269
// scrutinee value so the caller can bind the clause's
4270
// pattern from it; returns null when there is nothing to
4271
// bind (a missing scrutinee value, defensive).
4272
_emit_if_let_clause_test(
4273
c: Statements.REFUTABLE_BINDING_CLAUSE,
4274
fail: LABEL,
4275
is_first: bool,
4276
brancher: BLOCK_BRANCHER
4277
) -> TEMP? is
4278
c.scrutinee.walk(self);
4279
4280
if !c.scrutinee.value? then
4281
return null;
4282
fi
4283
4284
let scrutinee_value = c.scrutinee.value;
4285
let typed_value: Value mut = scrutinee_value;
4286
4287
if let c.narrow_type_expression?, narrow_type_expression.type? then
4288
// The scrutinee's value type already reflects the
4289
// then-edge narrowing applied in compile_conditionals
4290
// — when the scrutinee is an identifier and the
4291
// narrow target is a variant, narrowing specialises
4292
// (e.g. `CONS` -> `CONS[int]`) by mutating the
4293
// symbol type via NARROWING_FLOW. Use that
4294
// specialised form as the isinst target so the
4295
// runtime test references the closed-generic
4296
// variant. Detect "specialised form" by checking the
4297
// value-type and the written narrow type share the
4298
// same classy (i.e. the value type is the narrow
4299
// target with type args bound). Otherwise — e.g. the
4300
// scrutinee is a member access typed wider than the
4301
// narrow target — use the written narrow type
4302
// itself, so the isinst tests the target.
4303
let written_narrow = type;
4304
let scrutinee_classy = _classy_of_type(scrutinee_value.type!);
4305
let written_classy = _classy_of_type(written_narrow);
4306
4307
let narrow_target =
4308
if
4309
scrutinee_value.type? /\
4310
!scrutinee_value.type!.is_inferred /\
4311
!scrutinee_value.type!.is_error /\
4312
scrutinee_classy? /\
4313
written_classy? /\
4314
scrutinee_classy == written_classy
4315
then
4316
scrutinee_value.type;
4317
else
4318
written_narrow;
4319
fi;
4320
4321
typed_value =
4322
_type_caster.cast_value(
4323
c.location,
4324
scrutinee_value,
4325
narrow_target!,
4326
true
4327
);
4328
fi
4329
4330
let temp = TEMP(current_block, "if_let", typed_value);
4331
4332
let init_type = typed_value.type!;
4333
4334
// The presence test: a reference type is absent when
4335
// null; an option-shaped value type is absent when its
4336
// `has_value` member is false.
4337
let presence: Value? mut = null;
4338
4339
if init_type.is_value_type then
4340
let has_value_member = init_type.find_member("has_value");
4341
4342
if has_value_member? then
4343
presence = has_value_member.load(LOCATION.internal, temp.load(), _symbol_loader);
4344
fi
4345
else
4346
presence = temp.load();
4347
fi
4348
4349
if presence? then
4350
if is_first then
4351
brancher.branch(BRANCH.Z, presence, fail, "if");
4352
else
4353
brancher.branch(BRANCH.Z, presence, fail, "elif");
4354
fi
4355
fi
4356
4357
return temp;
4358
si
4359
4360
// Bind one clause's pattern from the temp produced by the
4361
// clause's test. Reference bindings take the value as-is;
4362
// option-shaped value-type bindings unwrap `.value` first.
4363
_emit_if_let_clause_bind(
4364
c: Statements.REFUTABLE_BINDING_CLAUSE,
4365
temp: TEMP,
4366
fail: LABEL
4367
) is
4368
let init_type = temp.load().type;
4369
4370
if init_type.is_value_type then
4371
let value_member = init_type.find_member("value");
4372
4373
if value_member? then
4374
gen_destructuring_initialize(
4375
c.pattern,
4376
value_member.load(LOCATION.internal, temp.load(), _symbol_loader),
4377
fail
4378
);
4379
fi
4380
else
4381
gen_destructuring_initialize(c.pattern, temp.load(), fail);
4382
fi
4383
si
4384
4385
pre(i: Statements.IF) -> bool is
4386
super.pre(i);
4387
4388
return true;
4389
si
4390
4391
visit(`if: Statements.IF) is
4392
let is_first mut = true;
4393
let seen_else mut = false;
4394
4395
let end = LABEL();
4396
4397
// Spill the IF's value through a frame field when the
4398
// body contains a suspend; otherwise capture as usual.
4399
// See COMPOSITE_VALUE_SPILLER. Recursive: inner
4400
// composites (val-blocks, nested IFs) handle their own
4401
// suspends the same way. `if.value` is non-null exactly
4402
// when `if.want_value` is true (visit_if sets it only
4403
// in that case), so the spiller takes the value
4404
// directly.
4405
let spiller = COMPOSITE_VALUE_SPILLER(
4406
self,
4407
`if.value,
4408
_current_state_machine_frame(),
4409
_contains_suspend(`if)
4410
);
4411
4412
spiller.enter();
4413
4414
let brancher = get_brancher_for_block();
4415
4416
for b in `if.branches do
4417
let next = LABEL();
4418
let first_clause_temp: TEMP? mut = null;
4419
4420
if let b.condition? /\ !b.binding? then
4421
// Enter the arm's own scope before walking the
4422
// condition: a `val ... lav` condition can declare
4423
// its own locals (`if val let v = e; v lav then`),
4424
// and those live in the arm's scope, not the
4425
// enclosing one.
4426
self.pre(b);
4427
4428
condition.walk(self);
4429
4430
if is_first then
4431
brancher.branch(BRANCH.Z, condition.value!, next, "if");
4432
else
4433
brancher.branch(BRANCH.Z, condition.value!, next, "elif");
4434
fi
4435
elif let b.binding? then
4436
// `if let` branch: for each clause, evaluate its
4437
// scrutinee into a temp, emit an isinst for the
4438
// type test when the clause carries `: V`, branch
4439
// past this arm if the result has no value, then
4440
// bind the clause's pattern. Clauses chain left
4441
// to right; every clause's test and optional
4442
// guard must succeed for the then-arm to fire.
4443
//
4444
// The first clause's scrutinee evaluates BEFORE
4445
// the if-arm scope opens, so its own bindings are
4446
// not visible to it (matching the single-clause
4447
// shape). Subsequent clauses' scrutinees evaluate
4448
// INSIDE the scope after earlier clauses' bindings
4449
// are stored — letting `if let x = a, y = x.b`
4450
// chain values through.
4451
first_clause_temp = _emit_if_let_clause_test(binding.clauses[0], next, is_first, brancher);
4452
4453
// enter if block scope:
4454
self.pre(b);
4455
else
4456
seen_else = true;
4457
4458
// enter if block scope:
4459
self.pre(b);
4460
fi
4461
4462
if let b.binding? then
4463
// Declare CLR locals for every clause's pattern
4464
// up front — pre(REFUTABLE_BINDING) iterates all
4465
// clauses.
4466
self.pre(binding);
4467
4468
if first_clause_temp? then
4469
_emit_if_let_clause_bind(binding.clauses[0], first_clause_temp, next);
4470
fi
4471
4472
if binding.clauses[0].guard? then
4473
binding.clauses[0].guard!.walk(self);
4474
brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, next, "if-let-guard");
4475
fi
4476
4477
let i mut = 1;
4478
while i < binding.clauses.count do
4479
let clause = binding.clauses[i];
4480
4481
let temp = _emit_if_let_clause_test(clause, next, false, brancher);
4482
4483
if temp? then
4484
_emit_if_let_clause_bind(clause, temp, next);
4485
fi
4486
4487
if clause.guard? then
4488
clause.guard.walk(self);
4489
brancher.branch(BRANCH.Z, clause.guard!.value!, next, "if-let-guard");
4490
fi
4491
4492
i = i + 1;
4493
od
4494
fi
4495
4496
b.body.walk(self);
4497
4498
// no-op?
4499
b.accept(self);
4500
4501
if `if.want_value then
4502
if let b.body.value? then
4503
// Coerce the branch value to the IF's result type
4504
// so a value-type branch flowing into a wider slot
4505
// is boxed (T -> object) or wrapped (T -> T?); the
4506
// else branch already lowers null -> default at
4507
// compile time, but a present-value branch pushes
4508
// its bare value here and must match the join type.
4509
let block_type = `if.value?.type;
4510
let coerced =
4511
if block_type? /\ !block_type.is_void /\ !block_type.is_error then
4512
_boxer.box_if_needed(value, block_type)
4513
else
4514
value
4515
fi;
4516
4517
spiller.emit_value(coerced);
4518
4519
// Discard the capture-mode value when the IF is
4520
// in expected-void position but a branch supplied
4521
// a non-void value. Spill mode stfld'd into a
4522
// typed field so no extra pop is needed.
4523
if let
4524
`if.expected_type? /\
4525
!spiller.is_spilling /\
4526
expected_type.is_void /\
4527
!value.type!.is_void
4528
then
4529
add("pop");
4530
fi
4531
fi
4532
fi
4533
4534
brancher.branch(end);
4535
brancher.label(next);
4536
4537
is_first = false;
4538
od
4539
4540
brancher.label(end);
4541
4542
super.visit(`if);
4543
4544
spiller.leave();
4545
si
4546
4547
pre(`case: Statements.CASE) -> bool is
4548
super.pre(`case);
4549
4550
return true;
4551
si
4552
4553
visit(`case: Statements.CASE) is
4554
// Spill the CASE's value through a frame field when the
4555
// body contains a suspend; otherwise capture as usual.
4556
// See COMPOSITE_VALUE_SPILLER. `case.value` is non-null
4557
// exactly when `case.want_value` is true.
4558
let spiller = COMPOSITE_VALUE_SPILLER(
4559
self,
4560
`case.value,
4561
_current_state_machine_frame(),
4562
_contains_suspend(`case)
4563
);
4564
4565
spiller.enter();
4566
4567
let temp = TEMP(current_block, "case", `case.expression.value!);
4568
4569
let brancher = get_brancher_for_block();
4570
4571
let labels = _loops.enter_loop();
4572
4573
let has_else mut = false;
4574
for m in `case.matches do
4575
if !m.expressions? /\ !m.pattern? then
4576
has_else = true;
4577
fi
4578
od
4579
4580
for m in `case.matches do
4581
let next = LABEL();
4582
4583
if m.expressions? then
4584
if m.expressions.expressions.count == 1 then
4585
m.expressions.expressions[0].walk(self);
4586
brancher.branch(BRANCH.NE, temp.load(), m.expressions!.expressions[0].value!, next);
4587
elif m.expressions.expressions.count > 1 then
4588
let match = LABEL();
4589
4590
for e in m.expressions! do
4591
e.walk(self);
4592
4593
brancher.branch(BRANCH.EQ, temp.load(), e.value!, match);
4594
od
4595
4596
brancher.branch(next);
4597
4598
brancher.label(match);
4599
fi
4600
4601
m.walk(self);
4602
elif m.pattern? then
4603
// Pattern arm: enter the arm's scope, narrow the
4604
// case temp against the pattern's type (ascription
4605
// → isinst; option-shape value type → has_value),
4606
// branch to `next` on failure, then bind. Mirrors
4607
// `if let`'s presence-test + bind shape, applied
4608
// against the case's scrutinee TEMP instead of a
4609
// per-branch initializer TEMP.
4610
self.pre(m);
4611
4612
let pattern = m.pattern!;
4613
let init_type: Semantic.Types.Type mut = `case.expression.value!.type!;
4614
let arm_source: IR.Values.Value mut = temp.load();
4615
4616
if pattern.is_explicit_type /\ pattern.type_expression.type? then
4617
init_type = pattern.type_expression.type!;
4618
arm_source = IR.Values.CAST(init_type, arm_source);
4619
fi
4620
4621
let arm_temp = TEMP(current_block, "case_let", arm_source);
4622
4623
let presence: IR.Values.Value? mut = null;
4624
if init_type.is_value_type then
4625
let has_value_member = init_type.find_member("has_value");
4626
if has_value_member? then
4627
presence = has_value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader);
4628
fi
4629
else
4630
presence = arm_temp.load();
4631
fi
4632
4633
if presence? then
4634
brancher.branch(BRANCH.Z, presence, next, "case-let");
4635
fi
4636
4637
self.pre(pattern);
4638
4639
if init_type.is_value_type then
4640
let value_member = init_type.find_member("value");
4641
4642
if value_member? then
4643
// Option-shape value type: bind from the
4644
// unwrapped `.value` member.
4645
gen_destructuring_initialize(
4646
pattern.left,
4647
value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader),
4648
next
4649
);
4650
else
4651
// Non-option value type (e.g. a plain tuple):
4652
// destructure directly against the value.
4653
gen_destructuring_initialize(pattern.left, arm_temp.load(), next);
4654
fi
4655
else
4656
gen_destructuring_initialize(pattern.left, arm_temp.load(), next);
4657
fi
4658
4659
if m.guard? then
4660
m.guard.walk(self);
4661
brancher.branch(BRANCH.Z, m.guard!.value!, next, "case-when-guard");
4662
fi
4663
4664
self.visit(m);
4665
else
4666
m.walk(self);
4667
fi
4668
4669
if `case.want_value then
4670
if let m.statements.value? then
4671
// Coerce the arm value to the CASE's result type
4672
// so a value-type arm flowing into a wider slot is
4673
// boxed (T -> object) or wrapped (T -> T?), matching
4674
// the join type the other arms deliver.
4675
let block_type = `case.value?.type;
4676
let coerced =
4677
if block_type? /\ !block_type.is_void /\ !block_type.is_error then
4678
_boxer.box_if_needed(value, block_type)
4679
else
4680
value
4681
fi;
4682
4683
spiller.emit_value(coerced);
4684
4685
if let
4686
`case.expected_type? /\
4687
!spiller.is_spilling /\
4688
expected_type.is_void /\
4689
!value.type!.is_void
4690
then
4691
add("pop");
4692
fi
4693
fi
4694
fi
4695
4696
brancher.branch(_loops.get_current_loop()!.end);
4697
4698
brancher.label(next);
4699
od
4700
4701
// An exhaustive case-expression without an `else` arm
4702
// falls through here with no value pushed when none of
4703
// the arms matched — impossible at runtime, but the IL
4704
// verifier can't see that. Emit a throw so the stack
4705
// invariant holds for the verifier; the throw is dead.
4706
if `case.want_value /\ `case.is_exhaustive /\ !has_else then
4707
add(
4708
Literal.STRING(
4709
"case is not exhaustive at runtime",
4710
_innate_symbol_lookup.get_string_type()
4711
)
4712
);
4713
add("newobj instance void class ['ghul-runtime']Ghul.AssertFailedException::.ctor(class [System.Runtime]System.String)");
4714
add("throw");
4715
elif `case.want_value /\ `case.requires_default_fallthrough /\ !has_else then
4716
// Open-domain case-expression with a defaultable
4717
// expected type and no `else` arm — push default(T)
4718
// for the no-match path. CASE_EXHAUSTIVENESS_CHECKER
4719
// has emitted the `case-needs-else` warning.
4720
add(IR.Values.DEFAULT(`case.expected_type!));
4721
fi
4722
4723
brancher.label(_loops.get_current_loop()!.end);
4724
4725
_loops.leave_loop();
4726
4727
super.visit(`case);
4728
4729
spiller.leave();
4730
si
4731
4732
pre(match: Statements.CASE_MATCH) -> bool is
4733
super.pre(match);
4734
4735
return true;
4736
si
4737
4738
visit(match: Statements.CASE_MATCH) is
4739
match.statements.accept(self);
4740
4741
super.visit(match);
4742
si
4743
4744
pre(`try: Statements.TRY) -> bool is
4745
super.pre(`try);
4746
4747
return true;
4748
si
4749
4750
get_exception_handler_temps() -> (outer_try: LOOP_LABELS?, return_needed: TEMP, return_value: TEMP?) is
4751
let return_type = current_function!.return_type;
4752
4753
let return_needed: TEMP mut;
4754
let return_value: TEMP? mut = _;
4755
4756
let outer_try = _loops.get_current_try();
4757
4758
if outer_try? then
4759
// get_current_try returns only try-form labels, whose init sets return_needed
4760
return_needed = outer_try.return_needed!;
4761
return_value = outer_try.return_value;
4762
else
4763
return_needed = TEMP(current_block, "need_return", 1, _innate_symbol_lookup.get_bool_type());
4764
4765
if return_type? /\ !return_type.matches(_innate_symbol_lookup.get_void_type()) then
4766
return_value = TEMP(current_block, "return", return_type);
4767
fi
4768
fi
4769
4770
return (outer_try, return_needed, return_value);
4771
si
4772
4773
gen_exception_handler_exit(outer_try: LOOP_LABELS?, label: LOOP_LABELS, return_value: TEMP?) is
4774
let brancher = get_brancher_for_block();
4775
4776
brancher.label(label.start);
4777
brancher.branch(BRANCH.Z, label.return_needed!.load(), label.end);
4778
4779
if outer_try? then
4780
if return_value? then
4781
assert outer_try.return_value? else "outer try has no return value temporary";
4782
outer_try.return_value.store(return_value.load());
4783
fi
4784
4785
if outer_try.is_in_finally then
4786
outer_try.return_needed!.store(Literal.NUMBER("1", _innate_symbol_lookup.get_bool_type(), "i4"));
4787
brancher.branch(outer_try.middle);
4788
else
4789
brancher.leave(outer_try.start);
4790
fi
4791
elif
4792
_current_async_state_machine? /\
4793
_current_async_success_label? /\
4794
current_function == _current_async_state_machine!.function
4795
then
4796
// State-machine async: a return inside a try block
4797
// accumulates the value in `return_value` and the
4798
// "need return" flag. The post-try emit reaches here
4799
// — for state-machine async we stash the value to
4800
// `_result` and `leave success_label` (NOT `ret`),
4801
// so the trailer's `builder.SetResult` fires.
4802
let frame = _current_async_state_machine!.frame;
4803
4804
assert frame? else "async state machine has no frame at exception handler exit emission";
4805
4806
let result_field = frame.result_field;
4807
4808
if return_value? /\ result_field? then
4809
add("ldarg.0");
4810
add(return_value.load());
4811
add("stfld {result_field.get_il_reference()}");
4812
fi
4813
4814
add("leave {_current_async_success_label!}");
4815
else
4816
if return_value? then
4817
add(return_value.load());
4818
fi
4819
4820
add("ret");
4821
fi
4822
4823
brancher.label(label.end);
4824
si
4825
4826
visit(`try: Statements.TRY) is
4827
if
4828
`try.catches.count == 0 /\
4829
!`try.`finally?
4830
then
4831
`try.body.walk(self);
4832
4833
super.visit(`try);
4834
4835
return;
4836
fi
4837
4838
let brancher = get_brancher_for_block();
4839
4840
let return_type = current_function!.return_type;
4841
4842
let (outer_try, return_needed, return_value) = get_exception_handler_temps();
4843
4844
let label = _loops.enter_try(return_needed, return_value);
4845
4846
let need_double_try = `try.catches.count > 0 /\ `try.`finally?;
4847
4848
let outer_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null;
4849
let body_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null;
4850
4851
if need_double_try then
4852
add(".try {{");
4853
outer_dispatch_holder = _maybe_push_dispatch_holder();
4854
fi
4855
4856
add(".try {{");
4857
4858
body_dispatch_holder = _maybe_push_dispatch_holder();
4859
4860
`try.body.walk(self);
4861
4862
_maybe_pop_dispatch_holder(body_dispatch_holder);
4863
4864
brancher.leave(label.start);
4865
4866
add("}}");
4867
4868
for c in `try.catches do
4869
c.walk(self);
4870
od
4871
4872
if need_double_try then
4873
_maybe_pop_dispatch_holder(outer_dispatch_holder);
4874
add("}}");
4875
fi
4876
4877
let `finally = `try.`finally;
4878
4879
if `finally? then
4880
label.is_in_finally = true;
4881
4882
add("finally {{");
4883
4884
let skip_finally_label = _open_finally_state_guard();
4885
4886
`finally.walk(self);
4887
4888
_close_finally_state_guard(skip_finally_label);
4889
4890
brancher.label(label.middle);
4891
4892
add("endfinally");
4893
add("}}");
4894
fi
4895
4896
_loops.leave_loop();
4897
4898
// let outer_try = _loops.get_current_try();
4899
gen_exception_handler_exit(outer_try, label, return_value);
4900
4901
super.visit(`try);
4902
si
4903
4904
pre(`catch: Statements.CATCH) -> bool is
4905
super.pre(`catch);
4906
4907
return true;
4908
si
4909
4910
visit(`catch: Statements.CATCH) is
4911
let variable = `catch.variable!;
4912
4913
add("catch {variable.type_expression.type!.get_il_type()} {{");
4914
4915
let brancher = get_brancher_for_block();
4916
4917
// Catch handler enters with the exception on the stack
4918
// and immediately stores it into the catch variable. The
4919
// loop-scoped default-store added for issue #483 must be
4920
// suppressed here — it would clobber the caught value.
4921
_in_catch_variable = true;
4922
variable.walk(self);
4923
_in_catch_variable = false;
4924
4925
let symbol = find(variable.name!);
4926
4927
// State-machine-resident catch variable (async function):
4928
// the exception's on the stack but `stloc <name>` would
4929
// reference an undeclared CLR local since pre(VARIABLE)
4930
// skipped the `.locals init` directive. Route through
4931
// the frame field via a temp instead.
4932
if let v: Semantic.Symbols.Variable = symbol, sm_field = v.state_machine_field then
4933
let field_ref = sm_field.get_il_reference();
4934
let exception_il = variable.type_expression.type!.get_il_type();
4935
add(".locals init ({exception_il} '.catch_exc')");
4936
add("stloc '.catch_exc'");
4937
add("ldarg.0");
4938
add("ldloc '.catch_exc'");
4939
add("stfld {field_ref}");
4940
else
4941
add("stloc {symbol!.get_il_reference()}");
4942
fi
4943
4944
`catch.body.walk(self);
4945
4946
brancher.leave(_loops.get_current_try()!.start);
4947
4948
add("}}");
4949
4950
super.visit(`catch);
4951
si
4952
4953
pre(`do: Statements.DO) -> bool is
4954
super.pre(`do);
4955
4956
return true;
4957
si
4958
4959
visit(`do: Statements.DO) is
4960
let brancher = get_brancher_for_block();
4961
4962
let loop = _loops.enter_loop();
4963
4964
brancher.label(loop.start);
4965
4966
if let `do.binding? then
4967
// `while let` clauses: emit the same per-clause
4968
// test / bind sequence as `if let`, but branch
4969
// failures to `loop.end` (loop exit) instead of an
4970
// arm-`next` label. Each iteration re-evaluates
4971
// every clause's scrutinee and re-binds — the
4972
// bound names are freshly stored on each pass.
4973
let first = _emit_if_let_clause_test(binding.clauses[0], loop.end, true, brancher);
4974
4975
self.pre(binding);
4976
4977
if first? then
4978
_emit_if_let_clause_bind(binding.clauses[0], first, loop.end);
4979
fi
4980
4981
if binding.clauses[0].guard? then
4982
binding.clauses[0].guard!.walk(self);
4983
brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, loop.end, "while-let-guard");
4984
fi
4985
4986
let i mut = 1;
4987
while i < binding.clauses.count do
4988
let clause = binding.clauses[i];
4989
4990
let temp = _emit_if_let_clause_test(clause, loop.end, false, brancher);
4991
4992
if temp? then
4993
_emit_if_let_clause_bind(clause, temp, loop.end);
4994
fi
4995
4996
if clause.guard? then
4997
clause.guard.walk(self);
4998
brancher.branch(BRANCH.Z, clause.guard!.value!, loop.end, "while-let-guard");
4999
fi
5000
5001
i = i + 1;
5002
od
5003
elif `do.condition? then
5004
`do.condition.walk(self);
5005
5006
brancher.branch(BRANCH.Z, `do.condition!.value!, loop.end, "while");
5007
fi
5008
5009
`do.body.walk(self);
5010
5011
brancher.branch(loop.start);
5012
5013
brancher.label(loop.end);
5014
5015
super.visit(`do);
5016
5017
_loops.leave_loop();
5018
si
5019
5020
pre(`for: Statements.FOR) -> bool is
5021
super.pre(`for);
5022
5023
return true;
5024
si
5025
5026
visit(`for: Statements.FOR) is
5027
// A recognised, fusible Pipe[T] chain is lowered to one
5028
// inline loop over the pinned base, applying the map/filter
5029
// stages per element, instead of iterating the built pipe
5030
// objects. Not in a state machine, whose iterator must live
5031
// on the frame to survive yield/await re-entry - those fall
5032
// through to the normal loop below.
5033
if
5034
`for.fusion? /\
5035
!Semantic.Symbols.state_machine_for(current_function)? /\
5036
!Semantic.Symbols.async_state_machine_for(current_function)?
5037
then
5038
if `for.fusion!.needs_guard then
5039
_visit_guarded_for(`for);
5040
else
5041
_visit_fused_for(`for);
5042
fi
5043
5044
return;
5045
fi
5046
5047
let brancher = get_brancher_for_block();
5048
let loop = _loops.enter_loop();
5049
5050
let expression = `for.expression!;
5051
let variable = `for.variable!;
5052
let body = `for.body!;
5053
5054
let iterator: Value mut;
5055
5056
if `for.read_iterator? then
5057
iterator = `for.read_iterator.call(expression.location, expression.value!, Collections.LIST[Value](0), null, _function_caller);
5058
else
5059
iterator = expression.value!;
5060
fi
5061
5062
variable.walk(self);
5063
5064
// Generator / async FOR: the iterator value has to
5065
// survive every yield / await in the body. Allocate a
5066
// frame field and route both load and store through it;
5067
// the regular CLR-local TEMP path would reset on every
5068
// MoveNext re-entry.
5069
let sm_for_function = Semantic.Symbols.state_machine_for(current_function);
5070
let async_sm_for_function: Semantic.Symbols.ASYNC_STATE_MACHINE? mut = null;
5071
if !sm_for_function? then
5072
async_sm_for_function = Semantic.Symbols.async_state_machine_for(current_function);
5073
fi
5074
5075
let iterator_field: Semantic.Symbols.Field? mut = null;
5076
let iterator_frame: Semantic.Symbols.Classy? mut = null;
5077
5078
if sm_for_function? /\ sm_for_function.frame? /\ iterator.type? then
5079
let frame = sm_for_function.frame!;
5080
iterator_field = frame.declare_anonymous_field("for_iterator", iterator.type!);
5081
iterator_frame = frame;
5082
elif async_sm_for_function? /\ async_sm_for_function.frame? /\ iterator.type? then
5083
let frame = async_sm_for_function.frame!;
5084
iterator_field = frame.declare_anonymous_field("for_iterator", iterator.type!);
5085
iterator_frame = frame;
5086
fi
5087
5088
if iterator_field? then
5089
add("ldarg.0");
5090
add(iterator);
5091
add("stfld {iterator_field.get_il_reference()}");
5092
fi
5093
5094
let temp: TEMP? mut = null;
5095
5096
if !iterator_field? then
5097
temp = TEMP(current_block, "iterator", iterator);
5098
fi
5099
5100
brancher.label(loop.start);
5101
5102
let load_iter_a =
5103
if iterator_field? then
5104
cast Value(IR.Values.Load.INSTANCE_FIELD(
5105
IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type),
5106
iterator_field
5107
));
5108
else
5109
cast Value(temp!.load());
5110
fi;
5111
5112
let has_next = `for.move_next!.call(expression.location, load_iter_a, Collections.LIST[Value](0), null, _function_caller);
5113
5114
brancher.branch(BRANCH.Z, has_next, loop.end);
5115
5116
let load_iter_b =
5117
if iterator_field? then
5118
cast Value(IR.Values.Load.INSTANCE_FIELD(
5119
IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type),
5120
iterator_field
5121
));
5122
else
5123
cast Value(temp!.load());
5124
fi;
5125
5126
let current = `for.read_current!.call(expression.location, load_iter_b, Collections.LIST[Value](0), null, _function_caller);
5127
5128
gen_destructuring_initialize(variable.left, current);
5129
5130
// not to generate code for the expression itself, but to capture IL for any anonymous function bodies:
5131
// TODO check that if expressions within the expression are emitted correctly
5132
expression.walk(self);
5133
5134
body.walk(self);
5135
5136
brancher.branch(loop.start);
5137
5138
brancher.label(loop.end);
5139
5140
super.visit(`for);
5141
5142
_loops.leave_loop();
5143
si
5144
5145
// Declare the per-stage locals a fused chain needs before its loop,
5146
// filling `stage_delegates` / `stage_counters` parallel to the
5147
// stages (null where a slot does not apply): an inlined stage's
5148
// parameter local, a delegate stage's hoisted function value, an
5149
// index stage's running counter initialised to its start. Shared by
5150
// the pure and guarded fused-loop emitters.
5151
_setup_fused_stages(
5152
fusion: Syntax.Process.PIPE_FUSION,
5153
stage_delegates: Collections.LIST[TEMP?],
5154
stage_counters: Collections.LIST[TEMP?]
5155
) is
5156
for stage in fusion.stages_outermost_first do
5157
if stage.is_countdown then
5158
// take/skip: evaluate the count once into a running
5159
// counter the loop decrements per pulled element.
5160
let counter = TEMP(current_block, "fused_countdown", _innate_symbol_lookup.get_int_type());
5161
counter.store(stage.argument!.value!);
5162
stage_delegates.add(null);
5163
stage_counters.add(counter);
5164
elif stage.is_inlined then
5165
add(".locals init ({stage.param_local!.type!.get_il_type()} {stage.param_local!.il_name})");
5166
stage_delegates.add(null);
5167
stage_counters.add(null);
5168
elif stage.is_index then
5169
let counter = TEMP(current_block, "fused_index", _innate_symbol_lookup.get_int_type());
5170
counter.store(Literal.NUMBER("{stage.index_start}", _innate_symbol_lookup.get_int_type(), "i4"));
5171
stage_delegates.add(null);
5172
stage_counters.add(counter);
5173
else
5174
stage_delegates.add(TEMP(current_block, "fused_stage", stage.argument!.value!));
5175
stage_counters.add(null);
5176
fi
5177
od
5178
si
5179
5180
// Apply a fused chain's stages to `element` innermost-first,
5181
// returning the fully-transformed value. A map result and an index
5182
// result each land in a fresh temp so a downstream stage can reload
5183
// them; a filter reject branches to `loop_start` (the next pull); an
5184
// index stage builds INDEXED_VALUE(counter++, current) directly.
5185
// Shared by the pure and guarded fused-loop emitters.
5186
_apply_fused_stages(
5187
fusion: Syntax.Process.PIPE_FUSION,
5188
element: Value,
5189
stage_delegates: Collections.List[TEMP?],
5190
stage_counters: Collections.List[TEMP?],
5191
brancher: BLOCK_BRANCHER,
5192
loop_start: LABEL,
5193
loop_end: LABEL
5194
) -> Value is
5195
let int_type = _innate_symbol_lookup.get_int_type();
5196
5197
let current_value: Value mut = element;
5198
5199
let index mut = fusion.stages_outermost_first.count - 1;
5200
5201
while index >= 0 do
5202
let stage = fusion.stages_outermost_first[index];
5203
5204
if stage.is_take then
5205
// Decrement the counter for this element; once it goes
5206
// negative the take limit is spent, so leave the loop.
5207
// The element passes through unchanged otherwise.
5208
brancher.branch(BRANCH.LT, cast Value(IR.Values.PRE_DECREMENT(stage_counters[index]!.il_name, int_type)), cast Value(Literal.NUMBER("0", int_type, "i4")), loop_end);
5209
elif stage.is_skip then
5210
// Decrement the counter; while it stays non-negative this
5211
// is a leading element to drop, so pull the next one.
5212
brancher.branch(BRANCH.GE, cast Value(IR.Values.PRE_DECREMENT(stage_counters[index]!.il_name, int_type)), cast Value(Literal.NUMBER("0", int_type, "i4")), loop_start);
5213
else
5214
let stage_result: Value mut;
5215
5216
if stage.is_inlined then
5217
// Assign the incoming element to the parameter local;
5218
// the pre-harvested body IR reads it (and any captured
5219
// outer locals) as ordinary locals - no delegate, no
5220
// frame.
5221
add(IR.Values.Store.LOCAL_VARIABLE(stage.param_local!, current_value));
5222
5223
stage_result = stage.inline_body!;
5224
elif stage.is_index then
5225
// INDEXED_VALUE(counter, current) with the counter
5226
// post-incremented in place - no INDEX_PIPE object.
5227
let arguments = Collections.LIST[Value]();
5228
arguments.add(cast Value(IR.Values.POST_INCREMENT(stage_counters[index]!.il_name, int_type)));
5229
arguments.add(current_value);
5230
5231
stage_result = IR.Values.NEW(stage.indexed_value_type!, stage.indexed_value_constructor!, arguments);
5232
else
5233
let delegate = stage_delegates[index]!.load();
5234
let func_type = stage.argument!.value!.type!;
5235
5236
let result_type =
5237
if func_type.is_action then
5238
_innate_symbol_lookup.get_void_type();
5239
else
5240
func_type.arguments[func_type.arguments.count - 1];
5241
fi;
5242
5243
let call_arguments = Collections.LIST[Value]();
5244
call_arguments.add(current_value);
5245
5246
stage_result = IR.Values.Call.CLOSURE(delegate, result_type, func_type.is_action, func_type, call_arguments);
5247
fi
5248
5249
if stage.is_filter then
5250
// Rejected elements skip straight to the next pull.
5251
brancher.branch(BRANCH.Z, stage_result, loop_start);
5252
else
5253
current_value = TEMP(current_block, "fused_mapped", stage_result).load();
5254
fi
5255
fi
5256
5257
index = index - 1;
5258
od
5259
5260
return current_value;
5261
si
5262
5263
// Lower a recognised fusible Pipe[T] chain to a single loop:
5264
// drive the pinned base's own iterator, apply each map/filter/index
5265
// stage inline per element (map/index assign a temp, filter skips to
5266
// the next element), then bind the loop variable and run the
5267
// body. The built MAP_PIPE / FilterPipe / INDEX_PIPE objects and
5268
// their per-element virtual dispatch are never emitted.
5269
_visit_fused_for(`for: Statements.FOR) is
5270
let fusion = `for.fusion!;
5271
let source = fusion.source;
5272
let variable = `for.variable!;
5273
let body = `for.body!;
5274
5275
let brancher = get_brancher_for_block();
5276
let loop = _loops.enter_loop();
5277
5278
// Iterate the source directly - the wrapping pipe objects are
5279
// never built. read_iterator is null when the source is its
5280
// own iterator (e.g. a range).
5281
let iterator: Value mut;
5282
5283
if fusion.source_read_iterator? then
5284
iterator = fusion.source_read_iterator.call(source.location, source.value!, Collections.LIST[Value](0), null, _function_caller);
5285
else
5286
iterator = source.value!;
5287
fi
5288
5289
variable.walk(self);
5290
5291
let temp = TEMP(current_block, "fused_iterator", iterator);
5292
5293
// Per-stage setup: an inlined stage needs a slot for its
5294
// parameter local; a delegate stage hoists its function value
5295
// into a local so the delegate is built once, not per element;
5296
// an index stage needs a running counter, initialised to its
5297
// start.
5298
let stage_delegates = Collections.LIST[TEMP?]();
5299
let stage_counters = Collections.LIST[TEMP?]();
5300
5301
_setup_fused_stages(fusion, stage_delegates, stage_counters);
5302
5303
brancher.label(loop.start);
5304
5305
let has_next = fusion.source_move_next!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller);
5306
5307
brancher.branch(BRANCH.Z, has_next, loop.end);
5308
5309
let element = fusion.source_read_current!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller);
5310
5311
let current_value = _apply_fused_stages(fusion, element, stage_delegates, stage_counters, brancher, loop.start, loop.end);
5312
5313
gen_destructuring_initialize(variable.left, current_value);
5314
5315
// Emit any anonymous-function bodies in the chain (the
5316
// map/filter lambdas, plus anything inside the source).
5317
// Walking these nodes emits the nested closure method bodies
5318
// without emitting the pipe-construction IL itself.
5319
source.walk(self);
5320
5321
for stage in fusion.stages_outermost_first do
5322
if stage.argument? then
5323
stage.argument.walk(self);
5324
fi
5325
od
5326
5327
body.walk(self);
5328
5329
brancher.branch(loop.start);
5330
5331
brancher.label(loop.end);
5332
5333
super.visit(`for);
5334
5335
_loops.leave_loop();
5336
si
5337
5338
// Lower a fusible chain whose source could dynamically be a user
5339
// Pipe with an overridden map/filter (any non-sealed source). The
5340
// chain is fused behind a per-site guard that tests the source the
5341
// same way pipe() does: `source isa Pipe[element]` true means
5342
// pipe() would short-circuit to the source and run its own
5343
// map/filter, so take the ordinary pipe-object loop; false means
5344
// pipe() wraps the source in the built-in default pipe, so the
5345
// fused loop is equivalent. Guard and pipe() agree by construction,
5346
// so this is sound with no runtime-library change.
5347
//
5348
// Both paths share ONE copy of the loop body: generate_il emits
5349
// precomputed IR and IR.Values.BLOCK is single-shot, so a
5350
// val-block in the body could not be walked twice. Instead a
5351
// loop-invariant `use_fused` flag selects the advance mechanism per
5352
// element (a perfectly predictable branch). Never reached inside a
5353
// state machine - visit(FOR)'s dispatch keeps generator/async loops
5354
// on the normal lowering, whose iterator lives on the frame to
5355
// survive yield/await.
5356
_visit_guarded_for(`for: Statements.FOR) is
5357
let fusion = `for.fusion!;
5358
let source = fusion.source;
5359
let variable = `for.variable!;
5360
let body = `for.body!;
5361
let expression = `for.expression!;
5362
5363
let brancher = get_brancher_for_block();
5364
let loop = _loops.enter_loop();
5365
5366
let bool_type = _innate_symbol_lookup.get_bool_type();
5367
let element_type = `for.read_current!.return_type!;
5368
5369
variable.walk(self);
5370
5371
// Shared mutable locals, declared once before the guard branch.
5372
// `use_fused` is set once (loop-invariant); `element_slot`
5373
// carries the per-element value into the shared bind + body.
5374
let use_fused = TEMP(current_block, "guard_use_fused", bool_type);
5375
let element_slot = TEMP(current_block, "guard_element", element_type);
5376
5377
// Fallback iterator (the built pipe chain's) and fused iterator
5378
// (the source's own). Each is stored in only one branch; the
5379
// other stays default and is never read on that path.
5380
let pipe_iterator_type =
5381
if `for.read_iterator? then
5382
`for.read_iterator.return_type!
5383
else
5384
expression.value!.type!
5385
fi;
5386
5387
let source_iterator_type =
5388
if fusion.source_read_iterator? then
5389
fusion.source_read_iterator.return_type!
5390
else
5391
source.value!.type!
5392
fi;
5393
5394
let pipe_iterator = TEMP(current_block, "guard_pipe_iterator", pipe_iterator_type);
5395
let source_iterator = TEMP(current_block, "guard_source_iterator", source_iterator_type);
5396
5397
// Per-stage setup for the fused path (shared with the pure
5398
// fused loop): declared before the guard branch. On the fallback
5399
// path these stay unused - a hoisted delegate is simply built
5400
// and not read.
5401
let stage_delegates = Collections.LIST[TEMP?]();
5402
let stage_counters = Collections.LIST[TEMP?]();
5403
5404
_setup_fused_stages(fusion, stage_delegates, stage_counters);
5405
5406
let fused_setup = LABEL();
5407
let after_setup = LABEL();
5408
5409
// if source isa Pipe[element] then fall back else fuse.
5410
let is_pipe = cast Value(IR.Values.ISA(bool_type, fusion.guard_isa_type!, source.value!));
5411
5412
brancher.branch(BRANCH.Z, is_pipe, fused_setup, "pipe-guard");
5413
5414
// Fallback setup: build the pipe chain and iterate it, exactly
5415
// as the normal for loop would.
5416
use_fused.store(Literal.NUMBER("0", bool_type, "i4"));
5417
5418
let pipe_iterator_value =
5419
if `for.read_iterator? then
5420
`for.read_iterator.call(expression.location, expression.value!, Collections.LIST[Value](0), null, _function_caller)
5421
else
5422
expression.value!
5423
fi;
5424
5425
pipe_iterator.store(pipe_iterator_value);
5426
5427
brancher.branch(after_setup);
5428
5429
// Fused setup: drive the source's own iterator directly - the
5430
// pipe objects are never built.
5431
brancher.label(fused_setup);
5432
5433
use_fused.store(Literal.NUMBER("1", bool_type, "i4"));
5434
5435
let source_iterator_value =
5436
if fusion.source_read_iterator? then
5437
fusion.source_read_iterator.call(source.location, source.value!, Collections.LIST[Value](0), null, _function_caller)
5438
else
5439
source.value!
5440
fi;
5441
5442
source_iterator.store(source_iterator_value);
5443
5444
brancher.label(after_setup);
5445
5446
// --- Merged loop over one shared body. ---
5447
brancher.label(loop.start);
5448
5449
let fused_advance = LABEL();
5450
let bind = LABEL();
5451
5452
brancher.branch(BRANCH.NZ, use_fused.load(), fused_advance, "use-fused");
5453
5454
// Fallback advance: pull from the pipe chain's iterator.
5455
let fallback_has_next = `for.move_next!.call(expression.location, pipe_iterator.load(), Collections.LIST[Value](0), null, _function_caller);
5456
5457
brancher.branch(BRANCH.Z, fallback_has_next, loop.end);
5458
5459
let fallback_current = `for.read_current!.call(expression.location, pipe_iterator.load(), Collections.LIST[Value](0), null, _function_caller);
5460
5461
element_slot.store(fallback_current);
5462
5463
brancher.branch(bind);
5464
5465
// Fused advance: pull from the source, apply stages inline.
5466
brancher.label(fused_advance);
5467
5468
let has_next = fusion.source_move_next!.call(source.location, source_iterator.load(), Collections.LIST[Value](0), null, _function_caller);
5469
5470
brancher.branch(BRANCH.Z, has_next, loop.end);
5471
5472
let element = fusion.source_read_current!.call(source.location, source_iterator.load(), Collections.LIST[Value](0), null, _function_caller);
5473
5474
let current_value = _apply_fused_stages(fusion, element, stage_delegates, stage_counters, brancher, loop.start, loop.end);
5475
5476
element_slot.store(current_value);
5477
5478
// --- Shared bind + body (walked once). ---
5479
brancher.label(bind);
5480
5481
gen_destructuring_initialize(variable.left, element_slot.load());
5482
5483
body.walk(self);
5484
5485
brancher.branch(loop.start);
5486
5487
brancher.label(loop.end);
5488
5489
// Emit the nested closure method bodies (the map/filter lambdas
5490
// and anything inside the source) exactly once. The fallback
5491
// path's pipe chain and the fused path's delegate temps both
5492
// reference these methods; a second walk would redefine them.
5493
source.walk(self);
5494
5495
for stage in fusion.stages_outermost_first do
5496
if stage.argument? then
5497
stage.argument.walk(self);
5498
fi
5499
od
5500
5501
super.visit(`for);
5502
5503
_loops.leave_loop();
5504
si
5505
5506
pre(pragma: Statements.PRAGMA) -> bool is
5507
process_pragma(pragma.pragma, true);
5508
return false;
5509
si
5510
5511
visit(pragma: Statements.PRAGMA) is
5512
process_pragma(pragma.pragma, false);
5513
si
5514
5515
pre(labelled: Statements.LABELLED) -> bool is
5516
super.pre(labelled);
5517
5518
return true;
5519
si
5520
5521
visit(labelled: Statements.LABELLED) is
5522
_loops.next_name(labelled.label.name);
5523
5524
labelled.statement.walk(self);
5525
5526
super.visit(labelled);
5527
si
5528
5529
visit(`break: Statements.BREAK) is
5530
let label = `break.label;
5531
5532
let target =
5533
if label? then
5534
_loops.find(label.name)
5535
else
5536
_loops.get_current_loop()
5537
fi;
5538
5539
if !target? then
5540
if label? then
5541
_logger.error(`break.location, "no enclosing loop labelled {label.name}");
5542
else
5543
_logger.error(`break.location, "break outside of loop");
5544
fi
5545
5546
return;
5547
fi
5548
5549
let brancher = get_brancher_for_block();
5550
5551
if _loops.is_in_try then
5552
brancher.leave(target.end);
5553
else
5554
brancher.branch(target.end);
5555
fi
5556
si
5557
5558
visit(`continue: Statements.CONTINUE) is
5559
let label = `continue.label;
5560
5561
let target =
5562
if label? then
5563
_loops.find(label.name)
5564
else
5565
_loops.get_current_loop()
5566
fi;
5567
5568
if !target? then
5569
if label? then
5570
_logger.error(`continue.location, "no enclosing loop labelled {label.name}");
5571
else
5572
_logger.error(`continue.location, "continue outside of loop");
5573
fi
5574
5575
return;
5576
fi
5577
5578
let brancher = get_brancher_for_block();
5579
5580
if _loops.is_in_try then
5581
brancher.leave(target.start);
5582
else
5583
brancher.branch(target.start);
5584
fi
5585
si
5586
5587
visit(interpolation: Expressions.STRING_INTERPOLATION) is
5588
ensure_runtime_symbols_are_materialized();
5589
5590
enter_block(cast IR.Values.BLOCK?(interpolation.value)!);
5591
5592
let string_type = _innate_symbol_lookup.get_string_type();
5593
5594
let literal_length = Literal.NUMBER("{interpolation.literal_length}", _innate_symbol_lookup.get_int_type(), "i4");
5595
let expression_count = Literal.NUMBER("{interpolation.expression_count}", _innate_symbol_lookup.get_int_type(), "i4");
5596
5597
let interpolator =
5598
TEMP(
5599
current_block,
5600
"interpolator",
5601
IR.Values.NEW(
5602
_interpolation_handler!,
5603
_constructor, [
5604
literal_length,
5605
expression_count
5606
]:Value
5607
)
5608
);
5609
5610
for eaf in interpolation.values do
5611
let expression = eaf.expression;
5612
let alignment = eaf.alignment;
5613
let format = eaf.format;
5614
let expr_value = expression.value!;
5615
5616
let args = Collections.LIST[Value](1);
5617
5618
args.add(expr_value);
5619
5620
if isa Expressions.Literals.STRING(expression) then
5621
if expression.value_string.length == 0 then
5622
continue;
5623
fi
5624
5625
let call = _append_literal.call(interpolation.location, interpolator.load(), args, null, _function_caller);
5626
5627
add(call);
5628
else
5629
let append_formatted: Semantic.Symbols.Symbol mut;
5630
let args = Collections.LIST[Value](3);
5631
5632
args.add(expr_value);
5633
5634
if format? /\ alignment? then
5635
args.add(alignment.value!);
5636
args.add(Literal.STRING(format, string_type));
5637
5638
append_formatted = _append_formatted_generic_alignment_format.specialize([expr_value.type!]);
5639
5640
elif format? then
5641
args.add(Literal.STRING(format, string_type));
5642
5643
append_formatted = _append_formatted_generic_format.specialize([expr_value.type!]);
5644
elif alignment? then
5645
args.add(alignment.value!);
5646
5647
append_formatted = _append_formatted_generic_alignment.specialize([expr_value.type!]);
5648
else
5649
append_formatted = _append_formatted_generic.specialize([expr_value.type!]);
5650
fi
5651
5652
let call = append_formatted.call(interpolation.location, interpolator.load(), args, null, _function_caller);
5653
5654
add(call);
5655
fi
5656
od
5657
5658
let result = _to_string_and_clear.call(interpolation.location, interpolator.load(), System.Array.empty`[Value](), null, _function_caller);
5659
add(result);
5660
5661
leave_block();
5662
si
5663
si
5664
5665
class LOOP_LABELS is
5666
is_try: bool;
5667
is_in_finally: bool public;
5668
is_loop: bool => !is_try;
5669
5670
name: string?;
5671
start: LABEL;
5672
middle: LABEL;
5673
end: LABEL;
5674
5675
return_needed: TEMP?;
5676
return_value: TEMP?;
5677
5678
init(name: string?) is
5679
is_try = false;
5680
self.name = name;
5681
start = LABEL();
5682
end = LABEL();
5683
si
5684
5685
init(return_needed: TEMP, return_value: TEMP?) is
5686
is_try = true;
5687
self.return_needed = return_needed;
5688
self.return_value = return_value;
5689
start = LABEL();
5690
middle = LABEL();
5691
end = LABEL();
5692
si
5693
5694
matches(name: string) -> bool => self.name? /\ self.name! =~ name;
5695
si
5696
5697
class LOOP_LABEL_STACK is
5698
_next_name: string?;
5699
5700
loops: Collections.MutableList[LOOP_LABELS];
5701
is_in_loop: bool => get_current_loop() != null;
5702
is_in_try: bool => get_current_try() != null;
5703
5704
get_current_try() -> LOOP_LABELS? is
5705
let index: int mut = loops.count - 1;
5706
5707
while index >= 0 do
5708
let result = loops[index];
5709
5710
if result.is_try then
5711
return result;
5712
fi
5713
5714
index = index - 1;
5715
od
5716
return null;
5717
si
5718
5719
// Number of `is_try` entries currently on the stack. Cached
5720
// at val-block entry; a return inside the body whose count
5721
// exceeds the cached value opened a try INSIDE the block,
5722
// so `br end_label` would cross the try's boundary.
5723
open_try_count: int is
5724
let count mut = 0;
5725
for l in loops do
5726
if l.is_try then
5727
count = count + 1;
5728
fi
5729
od
5730
return count;
5731
si
5732
5733
get_current_loop() -> LOOP_LABELS? is
5734
let index: int mut = loops.count - 1;
5735
5736
while index >= 0 do
5737
let result = loops[index];
5738
5739
if result.is_loop then
5740
return result;
5741
fi
5742
5743
index = index - 1;
5744
od
5745
return null;
5746
si
5747
5748
init() is
5749
loops = Collections.LIST[LOOP_LABELS]();
5750
si
5751
5752
next_name(name: string) is
5753
_next_name = name;
5754
si
5755
5756
find(name: string) -> LOOP_LABELS? is
5757
let index: int mut = loops.count - 1;
5758
5759
while index >= 0 do
5760
let result = loops[index];
5761
5762
if result.matches(name) then
5763
return result;
5764
fi
5765
5766
index = index - 1;
5767
od
5768
return null;
5769
si
5770
5771
enter_try(return_needed: TEMP, return_value: TEMP?) -> LOOP_LABELS is
5772
let result = LOOP_LABELS(return_needed, return_value);
5773
5774
loops.add(result);
5775
5776
return result;
5777
si
5778
5779
enter_loop() -> LOOP_LABELS is
5780
let result = LOOP_LABELS(_next_name);
5781
5782
_next_name = null;
5783
5784
loops.add(result);
5785
5786
return result;
5787
si
5788
5789
leave_loop() is
5790
loops.remove_at(loops.count - 1);
5791
si
5792
si
5793
si