Skip to content
← Back

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

1
namespace Syntax.Process is
2
use System.Reflection.Metadata.ILOpCode
3
use IO.Std
4
5
use System.Text.StringBuilder
6
7
use Logging
8
use Trees
9
use Source
10
11
use IR
12
use IR.Values
13
14
use Ghul.Pipes
15
16
17
// Function and closure IL: plain function pre/visit and SRM emission, closure walking and
18
// the async-closure prologue.
19
partial GENERATE_IL is
20
pre(function: Definitions.FUNCTION) -> bool is
21
let symbol = function_for(function)!
22
23
// Generator branch: a `*_GENERATOR_*` function expands
24
// into TWO IL methods (the outer stub returning a fresh
25
// state-machine instance + the state machine class with
26
// its MoveNext / get_Current / GetEnumerator / Dispose /
27
// Reset implementations). The split is handled by the
28
// gen-generator-function path so the rest of generate_il
29
// doesn't need to know.
30
//
31
// Both branches come before ordinary function emission,
32
// because a state machine is a shape rather than a body:
33
// the expansion has to happen before any of the pieces it
34
// produces can be emitted.
35
let state_machine = Semantic.Symbols.state_machine_for(cast Semantic.Symbols.Function?(symbol)!)
36
37
if state_machine? then
38
return _pre_generator_function(function, cast Semantic.Symbols.Function?(symbol)!, state_machine)
39
fi
40
41
// Async branch (parallel to the generator branch above).
42
let async_sm = Semantic.Symbols.async_state_machine_for(cast Semantic.Symbols.Function?(symbol)!)
43
44
if async_sm? then
45
return _pre_async_function(function, cast Semantic.Symbols.Function?(symbol)!, async_sm)
46
fi
47
48
return _pre_function_srm(function, cast Semantic.Symbols.Function?(symbol)!)
49
si
50
51
// Emission for every function the assembly defines. The
52
// structure walk has already
53
// reserved this function's MethodDef row and will write it once
54
// the walk is done, so nothing here touches metadata: the body
55
// is encoded into its own buffer and its offset left against
56
// the symbol.
57
_pre_function_srm(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function) -> bool is
58
// A declaration with no body - an abstract method, or a
59
// trait member left to its implementors - has nothing to
60
// encode, and its row is marked abstract rather than
61
// pointed at an empty body.
62
if symbol.is_abstract then
63
return true
64
fi
65
66
// A method that calls into a shared library has no body of
67
// its own: the runtime builds the call from the import its
68
// row names.
69
if symbol.pinvoke? then
70
return true
71
fi
72
73
enter_scope(function)
74
75
_context.current_srm_body_emitter = IR.Emitter.SRM_METHOD_BODY_EMITTER()
76
77
// Which function the assembly enters at was settled by
78
// select-entry-point, before any IL was written; this hands
79
// the choice to the emitter as the walk reaches it.
80
symbol.gen_entrypoint(_context)
81
82
enter_block()
83
84
return false
85
si
86
87
// The tail of `visit(function)` below.
88
_visit_function_srm(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function) is
89
if System.Environment.get_environment_variable("GHUL_EMIT_TRACE")? then
90
IO.Std.error.write_line("emit: {symbol.qualified_name} {symbol.location}")
91
fi
92
93
let body_emitter = _context.current_srm_body_emitter!
94
let assembly_emitter = _context.srm_assembly_emitter
95
96
// A body-less method that overrides an implemented one has
97
// a slot to fill and nothing to fill it with, so the body
98
// is a throw naming the method rather than a load of the
99
// return type's default.
100
let throws_unimplemented = symbol.throws_unimplemented
101
102
if throws_unimplemented then
103
add(Values.Literal.STRING(symbol.qualified_name, _innate_symbol_lookup.get_string_type()))
104
add(Values.NEW_NOT_IMPLEMENTED_EXCEPTION())
105
add(Values.INSTRUCTION(System.Reflection.Metadata.ILOpCode.THROW))
106
fi
107
108
// An implicit tail return delivers the body list's captured
109
// value - the whole body IL ending in the tail's value - boxed
110
// against the declared return type, in place of the default
111
// value loaded below.
112
let tail = if throws_unimplemented then null else _function_body_tail(function.body) fi
113
114
if !tail? then
115
current_block.gen(_context)
116
else
117
add(_boxer.box_if_needed(tail, symbol.return_type!))
118
current_block.gen(_context)
119
fi
120
121
leave_block()
122
123
// A non-void function ends with a default value loaded
124
// ahead of the trailing `ret`. The
125
// `ret` is unreachable whenever every path already
126
// returned, but unreachable is not the same as unverified:
127
// a method containing a protected region is checked whole,
128
// so a bare `ret` where a value is due is rejected outright
129
// rather than pruned.
130
//
131
// A body that delivered a tail value already leaves exactly
132
// that value on the stack, so no default load follows it.
133
// A machine-less result-less task-like return completes
134
// here instead of loading the default: its fall-through
135
// owes a handle, and the builder produces one.
136
if !tail? /\ !throws_unimplemented then
137
if let return_type = symbol.return_type then
138
if return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME then
139
if let completion = _builder_task_like_completion(symbol, return_type) then
140
completion.gen(_context)
141
else
142
Values.DEFAULT_RETURN(return_type).gen(_context)
143
fi
144
fi
145
fi
146
fi
147
148
body_emitter.ret()
149
150
assembly_emitter.handles.set_body_offset(symbol, body_emitter.flush(assembly_emitter))
151
assembly_emitter.handles.set_il_outputs(symbol, body_emitter.il_outputs)
152
153
_context.current_srm_body_emitter = null
154
155
leave_scope(function)
156
si
157
158
// The completion value a machine-less body owes for a bare
159
// `return;` or a fall-through: the handle produced by driving
160
// the task-like's own builder. Which return types complete this
161
// way is TASK_CONVERSION's to say - asking it rather than
162
// restating it keeps emission and the semantic side, which
163
// suppresses the definite-return warning on the same answer,
164
// from drifting apart. A state machine's own trailer completes
165
// through the builder field, so a body with one never asks.
166
_builder_task_like_completion(
167
function_symbol: Semantic.Symbols.Function,
168
return_type: Semantic.Types.Type?
169
) -> IR.Values.Value? is
170
if
171
Semantic.Symbols.async_state_machine_for(function_symbol)? \/
172
Semantic.Symbols.state_machine_for(function_symbol)?
173
then
174
return null
175
fi
176
177
if let task_like = _task_conversion.builder_completion_for(return_type) then
178
return IR.Values.COMPLETED_TASK_LIKE(return_type!, task_like.builder_type)
179
fi
180
181
return null
182
si
183
184
// The captured value of a function body whose final statement is
185
// an implicit tail return - the whole body IL, ending in the
186
// tail's value - or null when the body delivers nothing
187
// (terminated tails, empty bodies, void bodies, generators).
188
_function_body_tail(body: Trees.Bodies.Body?) -> IR.Values.Value? is
189
if let block = cast Trees.Bodies.BLOCK?(body) then
190
if let v = block.statements.compile_expressions_state.value then
191
return v
192
fi
193
fi
194
195
return null
196
si
197
198
// Deliver what an async body produces to its state machine's
199
// result, before the leave to the success label whose trailer
200
// reads that field to complete the builder.
201
_gen_async_body_result(
202
body: Trees.Bodies.Body?,
203
frame: Semantic.Symbols.ASYNC_STATE_MACHINE_FRAME
204
) is
205
if let result_field = frame.result_field, class_result_type = frame.class_result_type then
206
if let value = _async_body_value(body) then
207
add(_build_frame_field_store(
208
frame,
209
result_field,
210
_boxer.box_if_needed(value, class_result_type)
211
))
212
fi
213
214
return
215
fi
216
217
// A result-less machine has nowhere to put a value, so an
218
// expression body is emitted for its effects and whatever it
219
// leaves standing is dropped. A block body's statements have
220
// already emitted themselves.
221
if let expression_body = cast Trees.Bodies.EXPRESSION?(body) then
222
if let value = expression_body.expression.value then
223
add(value)
224
225
if let value_type = value.type then
226
if !value_type.is_void then
227
add(Values.INSTRUCTION(ILOpCode.POP))
228
fi
229
fi
230
fi
231
fi
232
si
233
234
// What an async body leaves for the result field. A block body
235
// leaves its tail; an `=> body` leaves its expression, whose own
236
// visitor emits nothing when the machine has a result field so
237
// the value arrives here once.
238
_async_body_value(body: Trees.Bodies.Body?) -> IR.Values.Value? is
239
if let expression_body = cast Trees.Bodies.EXPRESSION?(body) then
240
return expression_body.expression.value
241
fi
242
243
return _function_body_tail(body)
244
si
245
246
visit(function: Definitions.FUNCTION) is
247
let symbol = symbol_for(function)
248
249
let state_machine = Semantic.Symbols.state_machine_for(cast Semantic.Symbols.Function?(symbol)!)
250
251
if state_machine? then
252
_visit_generator_function(function, cast Semantic.Symbols.Function?(symbol)!, state_machine)
253
254
return
255
fi
256
257
let async_sm = Semantic.Symbols.async_state_machine_for(cast Semantic.Symbols.Function?(symbol)!)
258
259
if async_sm? then
260
// _pre_async_function already emitted the outer
261
// method and stashed the move_next block onto
262
// _pending_async_state_machines. Nothing else to
263
// do at AST visit time.
264
return
265
fi
266
267
let symbol_function = cast Semantic.Symbols.Function?(symbol)!
268
269
if !symbol_function.is_abstract /\ !symbol_function.pinvoke? then
270
_visit_function_srm(function, symbol_function)
271
fi
272
si
273
274
pre(function: Trees.Expressions.FUNCTION) -> bool is
275
super.pre(function)
276
277
let closure = cast Semantic.Symbols.Closure?(scope_for(function))
278
279
if !closure? then
280
return true
281
fi
282
283
closure.map_type_arguments()
284
285
return true
286
si
287
288
// A function literal's body is a method of its own, so the loops
289
// and protected regions it is written inside are not ones it can
290
// branch out of: a return in it leaves its own method, whatever
291
// try, disposal region or loop encloses the literal.
292
visit(function: Trees.Expressions.FUNCTION) is
293
let enclosing_loops = _loops
294
295
_loops = LOOP_LABEL_STACK()
296
297
_visit_function_literal(function)
298
299
_loops = enclosing_loops
300
si
301
302
_visit_function_literal(function: Trees.Expressions.FUNCTION) is
303
let closure = cast Semantic.Symbols.Closure?(scope_for(function))!
304
305
// Async closure dispatch (parallel to the named-function
306
// path in pre(Definitions.FUNCTION)). The lambda's $anon
307
// method body becomes a state-machine launch sequence
308
// (Create + MoveNext + return Task); the lambda body
309
// walks into a separate move_next_block; the SM class is
310
// queued for emission at class/namespace tail.
311
let async_sm = Semantic.Symbols.async_state_machine_for(closure)
312
if async_sm? then
313
_emit_async_closure(function, closure, async_sm)
314
super.visit(function)
315
closure.unmap_type_arguments()
316
return
317
fi
318
319
enter_block()
320
321
// Safety net: if a closure's return type resolved to ERROR
322
// (e.g. its body referenced a variable that itself never got
323
// a type), the consume-side check that should have surfaced
324
// a diagnostic missed this path — but `get_il_type()` will
325
// throw NotImplementedException as soon as we ask it to emit
326
// ERROR. Report a located diagnostic at the function's site
327
// and skip IL emission for this body. The function symbol
328
// itself remains valid; any caller will already have its
329
// own ERROR-typed result and will be handled by its own
330
// visit. (See error.ghul:30-34 for the underlying rule.)
331
if closure.return_type? /\ closure.return_type.is_error then
332
_logger.error(function.location, "cannot infer return type here")
333
leave_block()
334
super.visit(function)
335
closure.unmap_type_arguments()
336
return
337
fi
338
339
// The closure's body is encoded into a method body of its
340
// own. The enclosing method is mid-encode, so its emitter
341
// is set aside for the duration and restored after: the
342
// two bodies are separate, and the instructions of one
343
// must not land in the other.
344
let assembly_emitter = _context.srm_assembly_emitter
345
let enclosing = _context.current_srm_body_emitter
346
let enclosing_member = assembly_emitter.current_emission_member
347
let body_emitter = IR.Emitter.SRM_METHOD_BODY_EMITTER()
348
349
_context.current_srm_body_emitter = body_emitter
350
351
// Instructions in this body name types in the closure's terms,
352
// and a TypeSpec they reference is built from here rather than
353
// from the closure's own signature, so the emitter is told
354
// which member it is writing into.
355
assembly_emitter.current_emission_member = closure
356
357
_gen_closure_destructure_prologue(function, closure)
358
359
function.body.walk(self)
360
361
// An implicit tail return delivers the body list's captured
362
// value boxed against the closure's return type, in place of
363
// the default value a non-void fall-off loads.
364
let closure_tail = _function_body_tail(function.body)
365
366
if let ct = closure_tail, crt = closure.return_type then
367
add(_boxer.box_if_needed(ct, crt))
368
elif
369
closure.return_type? /\
370
closure.return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME
371
then
372
if let completion = _builder_task_like_completion(closure, closure.return_type) then
373
add(completion)
374
else
375
add(Values.DEFAULT_RETURN(closure.return_type!))
376
fi
377
fi
378
379
add(Values.RET())
380
381
current_block.gen(_context)
382
383
leave_block()
384
385
assembly_emitter.handles.set_body_offset(
386
closure, body_emitter.flush(assembly_emitter))
387
assembly_emitter.handles.set_il_outputs(
388
closure, body_emitter.il_outputs)
389
390
_context.current_srm_body_emitter = enclosing
391
assembly_emitter.current_emission_member = enclosing_member
392
393
super.visit(function)
394
395
closure.unmap_type_arguments()
396
si
397
398
// Declare and fill the names bound by any destructured lambda
399
// parameter, before the body that reads them runs. The
400
// parameter itself is one physical argument holding the
401
// aggregate; its leaves are ordinary locals, so they need both
402
// a `.locals init` slot and the unpacking stores - the same
403
// pair `pre(Variables.VARIABLE)` and `visit(Variables.VARIABLE)`
404
// emit for a named function's destructured formal argument.
405
_gen_closure_destructure_prologue(
406
function: Trees.Expressions.FUNCTION,
407
closure: Semantic.Symbols.Closure
408
) is
409
_gen_packed_parameter_prologue(function.location, closure)
410
411
for a in function.arguments.expressions do
412
let argument = cast Trees.Expressions.VARIABLE?(a)
413
414
if !argument? \/ !argument.left? then
415
continue
416
fi
417
418
let left = argument.left
419
420
for name in left.names! do
421
_declare_local(closure.find_direct(name.name))
422
od
423
424
let group_symbol = closure.find_direct(argument.name.name)
425
426
if group_symbol? then
427
gen_destructuring_initialize(left, group_symbol.load(argument.location, null, _symbol_loader))
428
fi
429
od
430
si
431
432
// A literal written with an argument pack spread out is emitted
433
// taking the pack's tuple. Each parameter it declared is a local,
434
// filled from the tuple before the body that reads it runs. The
435
// pack binds to anything that destructures by position, so the
436
// elements come from the tuple's members or from a `deconstruct`
437
// call, as they do for a destructuring `let`.
438
_gen_packed_parameter_prologue(location: Source.LOCATION, closure: Semantic.Symbols.Closure) is
439
let packed = closure.packed_parameters
440
let group = closure.pack_group
441
442
if !packed? \/ !group? then
443
return
444
fi
445
446
for parameter in packed do
447
current_block.add(Values.DECLARE_LOCAL(IR.Values.packed_parameter_local_for(parameter)!, parameter.type!))
448
od
449
450
let strategy = DESTRUCTURE_RESOLVER.resolve_strategy(group.type, packed.count)
451
452
let elements = Collections.LIST[Value]()
453
454
if strategy.is_deconstruct then
455
let deconstruct = strategy.deconstruct_function!
456
let call_arguments = Collections.LIST[Value]()
457
458
for i in 0..deconstruct.arguments.count do
459
let ref_type = deconstruct.arguments[i]
460
let element_type = ref_type.get_element_type()
461
462
assert element_type? else "deconstruct argument type has no element type"
463
464
let element = IR.TEMP(current_block, "pack_element", i, element_type)
465
466
elements.add(element.load())
467
call_arguments.add(IR.Values.ADDRESS(element.load(), ref_type))
468
od
469
470
add(
471
deconstruct.call(
472
location,
473
group.load(location, null, _symbol_loader),
474
call_arguments,
475
null,
476
_function_caller
477
)
478
)
479
else
480
for i in 0..packed.count do
481
elements.add(strategy.members[i]!.load(location, group.load(location, null, _symbol_loader), _symbol_loader))
482
od
483
fi
484
485
assert elements.count == packed.count else "argument pack of {packed.count} parameters has {elements.count} elements"
486
487
for i in 0..packed.count do
488
add(Values.Store.LOCAL_ARGUMENT(packed[i], _boxer.box_if_needed(elements[i], packed[i].type!)))
489
od
490
si
491
492
// Unpack a named function's destructured formal arguments into
493
// their leaf frame fields. The plain path needs no call to this:
494
// its AST walk reaches each formal's Variables.VARIABLE and
495
// `visit(Variables.VARIABLE)` emits the same stores at method
496
// entry. The generator and async paths drive their own walk into
497
// MoveNext and never reach those nodes, so they emit this first -
498
// placed after the state dispatch, so a resumed MoveNext branches
499
// straight to its await label and the unpack runs once, on first
500
// entry. The aggregate loads through its `$arg_*` frame field;
501
// compile-expressions registered every leaf's field.
502
_gen_destructured_formal_prologue(function: Definitions.FUNCTION) is
503
for v in function.arguments do
504
if v.is_argument /\ !v.left.is_simple_name then
505
let group_symbol = symbol_for(v)
506
507
if group_symbol? then
508
gen_destructuring_initialize(v.left, group_symbol.load(v.location, null, _symbol_loader))
509
fi
510
fi
511
od
512
si
513
514
// Async closure: the lambda's $anon body becomes a builder-
515
// create / Start / return-Task launch sequence; the user body
516
// walks into a separate move_next_block; the SM class is
517
// queued for class-tail emission alongside named-function SMs.
518
_emit_async_closure(
519
function: Trees.Expressions.FUNCTION,
520
closure: Semantic.Symbols.Closure,
521
async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE
522
) is
523
let frame = async_state_machine.frame
524
525
if !frame? then
526
_logger.error(function.location, "async function must return a task-like type — one carrying AsyncMethodBuilderAttribute, such as Tasks.TASK or Tasks.ValueTask")
527
return
528
fi
529
530
frame.declare()
531
532
// Body walk before launch IL — populates the frame's
533
// await labels / locals / awaiters before launch
534
// references any of them.
535
async_state_machine.install_body_emission_overrides()
536
537
enter_scope(function)
538
539
let move_next_block = Values.BLOCK()
540
541
enter_block(move_next_block)
542
543
add(Values.MAX_STACK(64))
544
545
// Same V_state caching + per-region dispatch pattern as
546
// `_pre_async_function` (see its commentary). Awaits in
547
// the closure body register with the outer holder unless
548
// an inner `.try` pushes its own.
549
let outer_dispatch_holder =
550
_open_state_machine_entry(frame, frame.state_field, ".async_state")
551
552
let success_label = IR.LABEL()
553
let end_label = IR.LABEL()
554
555
let prev_async_sm = _current_async_state_machine
556
let prev_success = _current_async_success_label
557
let prev_end = _current_async_end_label
558
_current_async_state_machine = async_state_machine
559
_current_async_success_label = success_label
560
_current_async_end_label = end_label
561
562
// The plain-closure path emits this before its body walk
563
// too - here into MoveNext, after the state dispatch, where
564
// the leaves' loads route through the frame fields
565
// `_register_lambda_pattern_fields` registered.
566
_gen_closure_destructure_prologue(function, closure)
567
568
function.body.walk(self)
569
570
// What the body produces goes into the frame's result before
571
// leaving to success, the same as a named function's state
572
// machine - the trailer reads that field to complete the
573
// builder, so a body that never reaches it completes with the
574
// result type's default.
575
_gen_async_body_result(function.body, frame)
576
577
add(Values.BRANCH_TO(ILOpCode.LEAVE, success_label))
578
579
_current_async_state_machine = prev_async_sm
580
_current_async_success_label = prev_success
581
_current_async_end_label = prev_end
582
583
_pop_async_dispatch_holder(outer_dispatch_holder)
584
585
leave_block()
586
leave_scope(function)
587
588
async_state_machine.uninstall_body_emission_overrides()
589
590
// --- Build the builder-launch sequence ---
591
enter_block()
592
add(Values.MAX_STACK(64))
593
594
let construction_type_args = async_state_machine.get_construction_type_arguments()
595
596
let constructor =
597
_specialized_constructor(
598
frame, frame.constructor, construction_type_args, function.location)
599
600
let state_machine_type =
601
_specialized_frame_type(frame, construction_type_args, function.location) ?? frame.type!
602
603
// Ctor args: instance closures prepend ldarg.0 (the
604
// closure-frame instance, which holds captures), then
605
// each user-declared lambda parameter via ldarg '<name>'.
606
let ctor_args_block = Values.BLOCK()
607
608
// As above: the closure's receiver, when it has one, takes
609
// slot 0 and its declared parameters follow.
610
let slot mut = if closure.is_emitted_with_receiver then 1 else 0 fi
611
612
if frame.outer_self_field? then
613
ctor_args_block.add(Values.Load.ARGUMENT_SLOT(0, "ldarg.0"))
614
fi
615
for arg_name in closure.argument_names do
616
ctor_args_block.add(Values.Load.ARGUMENT_SLOT(slot, "ldarg '{arg_name}'"))
617
slot = slot + 1
618
od
619
ctor_args_block.close()
620
621
let task_type = closure.return_type!
622
623
let launch = IR.Values.ASYNC_METHOD_LAUNCH(
624
task_type,
625
state_machine_type,
626
constructor,
627
ctor_args_block,
628
frame.state_field,
629
frame.builder_field!,
630
".sm"
631
)
632
633
add(launch)
634
add(Values.RET())
635
636
// The launch is the closure's own method body, not part of
637
// whatever is being emitted around the literal, so it needs
638
// a body emitter of its own or it lands in the enclosing
639
// method.
640
let assembly_emitter = _context.srm_assembly_emitter
641
let enclosing = _context.current_srm_body_emitter
642
let body = IR.Emitter.SRM_METHOD_BODY_EMITTER()
643
644
_context.current_srm_body_emitter = body
645
646
try
647
current_block.gen(_context)
648
649
assembly_emitter.handles.set_body_offset(
650
closure, body.flush(assembly_emitter))
651
assembly_emitter.handles.set_il_outputs(
652
closure, body.il_outputs)
653
finally
654
_context.current_srm_body_emitter = enclosing
655
yrt
656
657
leave_block()
658
659
_pending_async_state_machines.add(
660
PENDING_ASYNC_STATE_MACHINE(
661
async_state_machine,
662
move_next_block,
663
success_label,
664
end_label
665
)
666
)
667
si
668
669
si
670
si