Skip to content
← Back

src/semantic/symbols/async_state_machine.ghul

1
namespace Semantic.Symbols is
2
use IO.Std;
3
4
use System.Text.StringBuilder;
5
6
use IoC;
7
use Logging;
8
use Source;
9
10
use IR.Values.Value;
11
12
use Types.Type;
13
14
// Per-`let await` bookkeeping recorded by await IL emission.
15
// The resume label is placed immediately after the per-await
16
// `leave` that suspends MoveNext; entry-dispatch jumps here
17
// when the builder re-enters MoveNext on awaiter completion.
18
class ASYNC_STATE_LABEL is
19
state: int public;
20
awaiter_field: Field public;
21
label: IR.LABEL public;
22
23
init(state: int, awaiter_field: Field, label: IR.LABEL) is
24
self.state = state;
25
self.awaiter_field = awaiter_field;
26
self.label = label;
27
si
28
si
29
30
// State held on an async function symbol: the synthesised frame
31
// class, the running state-number counter, the recorded
32
// (state, awaiter_field, resume_label) triples for entry-dispatch
33
// emission. Lives as a non-null field on each *_ASYNC_*
34
// function; checked-for via `async_state_machine_for(...)`.
35
class ASYNC_STATE_MACHINE is
36
function: Function public;
37
38
_frame: ASYNC_STATE_MACHINE_FRAME?;
39
_next_state: int;
40
_await_labels: Collections.LIST[ASYNC_STATE_LABEL];
41
42
init(function: Function) is
43
self.function = function;
44
45
_next_state = 1;
46
_await_labels = Collections.LIST[ASYNC_STATE_LABEL]();
47
si
48
49
// Lazy: the frame class is materialised on first access,
50
// pulling its result type from the (now-resolved) function
51
// return type (Tasks.TASK[T] → T, Tasks.TASK → void). Returns
52
// null only when the return type isn't a Task — which is an
53
// upstream diagnostic, not something the frame can recover
54
// from.
55
frame: ASYNC_STATE_MACHINE_FRAME? is
56
let lookup = IoC.CONTAINER.instance.innate_symbol_lookup;
57
58
if !_frame? then
59
if !function.return_type? then
60
return null;
61
fi
62
63
let result_type: Type? mut = null;
64
let is_void mut = false;
65
66
// Named functions + closures with pinned return type
67
// extract directly from `-> Tasks.TASK[T]` /
68
// `-> Tasks.TASK`. Lambdas with INFERRED_RETURN_TYPE
69
// can't (an inferred placeholder `matches` anything) so fall back
70
// to the AST flag `is_void_async`; `result_type`
71
// fills in later via `ensure_result_field`.
72
let extracted = _extract_result_type(lookup);
73
74
if extracted? then
75
result_type = extracted;
76
elif function.return_type!.is_inferred then
77
is_void = function.is_void_async;
78
else
79
let void_task = lookup.get_void_task_type();
80
if void_task? /\ function.return_type!.matches(void_task) then
81
is_void = true;
82
else
83
return null;
84
fi
85
fi
86
87
_frame = ASYNC_STATE_MACHINE_FRAME(
88
function.owner!,
89
function,
90
result_type,
91
is_void
92
);
93
else
94
// Re-poll on each access: a value-async lambda whose
95
// return type wasn't yet settled when the frame was
96
// first realised needs its _result_type filled in
97
// once the type pins. Idempotent.
98
_frame.ensure_result_field(lookup);
99
fi
100
101
return _frame;
102
si
103
104
_extract_result_type(lookup: Lookups.InnateSymbolLookup) -> Type? =>
105
TYPE_ARGUMENT_EXTRACTOR.extract(function.return_type, lookup.get_unspecialized_task_type());
106
107
allocate_state() -> int is
108
let n = _next_state;
109
110
_next_state = _next_state + 1;
111
112
return n;
113
si
114
115
record_label(state: int, awaiter_field: Field, label: IR.LABEL) is
116
_await_labels.add(ASYNC_STATE_LABEL(state, awaiter_field, label));
117
si
118
119
await_labels: Collections.Iterable[ASYNC_STATE_LABEL] => _await_labels;
120
121
// See `STATE_MACHINE_TYPE_PARAMS` — generator + async use
122
// the same capture order so the helper handles both.
123
install_body_emission_overrides() is
124
STATE_MACHINE_TYPE_PARAMS.walk_install(function, true);
125
si
126
127
uninstall_body_emission_overrides() is
128
STATE_MACHINE_TYPE_PARAMS.walk_install(function, false);
129
si
130
131
get_construction_type_arguments() -> Collections.List[Type]? =>
132
STATE_MACHINE_TYPE_PARAMS.construction_type_arguments(function);
133
si
134
135
// Frame class synthesised for each async function. Implements
136
// `IAsyncStateMachine` (MoveNext, SetStateMachine); holds the
137
// current state, a builder, per-await awaiter fields, captured
138
// arguments + locals + outer-self, and a result slot for
139
// value-async (omitted for void-async). The class itself is
140
// generic in any type parameters visible inside the owning
141
// function (mirrors `STATE_MACHINE_FRAME`).
142
class ASYNC_STATE_MACHINE_FRAME: STATE_MACHINE_FRAME_BASE is
143
_next_id: int static;
144
145
// Null for void-async, and for a value-async lambda whose
146
// inferred return type hasn't pinned yet (filled in by
147
// ensure_result_field).
148
_result_type: Type?;
149
_is_void: bool;
150
151
// Null until declare() runs. Builder, result and outer-self
152
// stay null past declare() on some paths: builder when the
153
// builder type cannot be resolved, result for void-async,
154
// outer-self for statics and globals.
155
_state_field: Field?;
156
_builder_field: Field?;
157
_result_field: Field?;
158
_outer_self_field: Field?;
159
_constructor: Method?;
160
161
result_type: Type? => _result_type;
162
is_void: bool => _is_void;
163
164
class_result_type: Type? =>
165
if _result_type? then
166
_class_relative(_result_type)
167
else
168
null
169
fi;
170
171
// Non-null once declare() has run.
172
state_field: Field => _state_field!;
173
constructor: Method => _constructor!;
174
175
builder_field: Field? => _builder_field;
176
result_field: Field? => _result_field;
177
outer_self_field: Field? => _outer_self_field;
178
179
next_id: int static is
180
let result = _next_id;
181
_next_id = _next_id + 1;
182
return result;
183
si
184
185
init(owner: Scope, owning_function: Function, result_type: Type?, is_void: bool) is
186
let owner_owner: Scope mut;
187
188
if isa Symbol(owner) then
189
let owner_symbol = owner;
190
owner_owner = owner_symbol.owner!;
191
else
192
owner_owner = owner;
193
fi
194
195
super.init(
196
LOCATION.internal,
197
LOCATION.internal,
198
owner_owner,
199
"$AsyncStateMachine_{owning_function.name}_{next_id}",
200
owner,
201
owning_function
202
);
203
204
_result_type = result_type;
205
_is_void = is_void;
206
207
_argument_fields = Collections.LIST[Field]();
208
_awaiter_fields = Collections.LIST[Field]();
209
210
set_type(Types.NAMED(self));
211
si
212
213
_argument_fields: Collections.LIST[Field];
214
215
argument_fields: Collections.Iterable[Field] => _argument_fields;
216
217
_awaiter_fields: Collections.LIST[Field];
218
219
awaiter_fields: Collections.Iterable[Field] => _awaiter_fields;
220
221
// Lazy + idempotent realisation of frame members. Value-async
222
// frames short-circuit until `_result_type` is available
223
// (otherwise we'd resolve `AsyncTaskMethodBuilder<null>`).
224
declare() is
225
if _state_field? then
226
return;
227
fi
228
229
if !_is_void /\ !_result_type? then
230
return;
231
fi
232
233
let listener = IoC.CONTAINER.instance.symbol_definition_locations;
234
235
declare_captured_type_params(listener);
236
237
let int_type = IoC.CONTAINER.instance.innate_symbol_lookup.get_int_type();
238
239
let state_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$state");
240
state_field.set_type(int_type);
241
declare(LOCATION.internal, state_field, listener);
242
_state_field = state_field;
243
244
// Builder field — `AsyncTaskMethodBuilder<T>` for
245
// value-async, non-generic for void-async. Omitted when
246
// the type cannot be resolved; IL emission supplies it
247
// literally.
248
let builder_type = _resolve_builder_type();
249
if builder_type? then
250
let builder_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$builder");
251
builder_field.set_type(builder_type);
252
declare(LOCATION.internal, builder_field, listener);
253
_builder_field = builder_field;
254
fi
255
256
// `$result` only for value-async. Lambdas with
257
// unresolved result_type defer to `ensure_result_field`.
258
if !_is_void /\ _result_type? then
259
let result_type = _result_type;
260
261
let result_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$result");
262
result_field.set_type(_class_relative(result_type));
263
declare(LOCATION.internal, result_field, listener);
264
_result_field = result_field;
265
fi
266
267
let ctor_argument_names = Collections.LIST[string]();
268
let ctor_argument_types = Collections.LIST[Type]();
269
270
// Outer-self target: enclosing user class for an
271
// instance method, or the closure's captures-holding
272
// FRAME for an async closure (whose launch is emitted as
273
// an instance method on the FRAME).
274
let outer_classy: Classy? mut = null;
275
276
if owning_function.is_instance /\ isa Classy(owning_function.owner) then
277
outer_classy = cast Classy?(owning_function.owner)!;
278
elif let closure: Closure = owning_function then
279
if closure.frame? then
280
outer_classy = closure.frame;
281
fi
282
fi
283
284
if outer_classy? then
285
let self_type = outer_self_type(outer_classy);
286
287
let outer_self_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$outer_self");
288
outer_self_field.set_type(self_type);
289
declare(LOCATION.internal, outer_self_field, listener);
290
_outer_self_field = outer_self_field;
291
292
ctor_argument_names.add("$outer_self");
293
ctor_argument_types.add(self_type);
294
fi
295
296
if owning_function.argument_names.count > 0 then
297
for arg_name in owning_function.argument_names do
298
let local = cast Symbols.LOCAL_ARGUMENT?(owning_function.find_direct(arg_name));
299
300
if !local? then
301
continue;
302
fi
303
304
let arg_field_type = _class_relative(local.type!);
305
306
let arg_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$arg_{arg_name}");
307
arg_field.set_type(arg_field_type);
308
declare(LOCATION.internal, arg_field, listener);
309
310
_argument_fields.add(arg_field);
311
312
local.state_machine_field = arg_field;
313
314
ctor_argument_names.add(arg_name);
315
ctor_argument_types.add(arg_field_type);
316
od
317
fi
318
319
let ctor = Symbols.INSTANCE_METHOD(LOCATION.internal, LOCATION.internal, self, "init", self);
320
ctor.set_arguments(ctor_argument_names, ctor_argument_types);
321
ctor.set_void_return_type();
322
declare(LOCATION.internal, ctor, listener);
323
_constructor = ctor;
324
325
// Ancestors: Object as base class, IAsyncStateMachine as
326
// implemented interface. The frame doesn't expose
327
// Iterable/Iterator like the generator frame; the runtime
328
// calls MoveNext via the builder.
329
let lookup = IoC.CONTAINER.instance.innate_symbol_lookup;
330
331
add_ancestor(lookup.get_object_type());
332
333
let async_state_machine_type = _resolve_async_state_machine_type();
334
if async_state_machine_type? then
335
add_ancestor(async_state_machine_type);
336
fi
337
si
338
339
// Resolves to `System.Runtime.CompilerServices.AsyncTaskMethodBuilder<T>`
340
// for value-async or `System.Runtime.CompilerServices.AsyncTaskMethodBuilder`
341
// for void-async. Goes via the innate lookup so reflection
342
// supplies the actual Type.
343
_resolve_builder_type() -> Type? is
344
let lookup = IoC.CONTAINER.instance.innate_symbol_lookup;
345
if _is_void then
346
return lookup.get_async_task_method_builder_void_type();
347
else
348
if !_result_type? then
349
return null;
350
fi
351
let class_result = _class_relative(_result_type);
352
return lookup.get_async_task_method_builder_type(class_result);
353
fi
354
si
355
356
_resolve_async_state_machine_type() -> Type? is
357
let lookup = IoC.CONTAINER.instance.innate_symbol_lookup;
358
return lookup.get_async_state_machine_interface_type();
359
si
360
361
// Late-fill `_result_type` once the owning lambda's
362
// wrap-as-task pin settles. Unblocks declare() for value-
363
// async closures whose return type was inferred. Idempotent.
364
ensure_result_field(lookup: Lookups.InnateSymbolLookup) is
365
if _is_void \/ _result_type? then
366
return;
367
fi
368
if !owning_function.return_type? then
369
return;
370
fi
371
372
let extracted = TYPE_ARGUMENT_EXTRACTOR.extract(
373
owning_function.return_type,
374
lookup.get_unspecialized_task_type()
375
);
376
377
if extracted? then
378
_result_type = extracted;
379
fi
380
si
381
382
// Per-await awaiter field. Each call site gets its own slot
383
// since the awaiter is typically a value type
384
// (`TaskAwaiter<T>` / its non-generic sibling).
385
declare_awaiter_field(awaiter_type: Type) -> Field is
386
let id = next_local_id();
387
388
let listener = IoC.CONTAINER.instance.symbol_definition_locations;
389
390
let `field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$awaiter_{id}");
391
`field.set_type(_class_relative(awaiter_type));
392
declare(LOCATION.internal, `field, listener);
393
394
_awaiter_fields.add(`field);
395
396
return `field;
397
si
398
399
// Allocate a fresh field for a spilled intermediate value
400
// (SPILL_AWAITS pass + visit(SPILL) in generate-il) or for
401
// an await's result. The frame holds them so they survive
402
// across MoveNext re-entries.
403
declare_spill_field(type: Type) -> Field =>
404
declare_anonymous_field("spill", type);
405
si
406
407
// Free helper: returns the ASYNC_STATE_MACHINE held by any of
408
// the async function or closure forms, or null if `f` is a plain
409
// function / generator / sync closure.
410
async_state_machine_for(f: Function?) -> ASYNC_STATE_MACHINE? is
411
if !f? then
412
return null;
413
fi
414
415
if isa STATIC_ASYNC_METHOD(f) then
416
return f.async_state_machine;
417
fi
418
419
if isa INSTANCE_ASYNC_METHOD(f) then
420
return f.async_state_machine;
421
fi
422
423
if isa GLOBAL_ASYNC_FUNCTION(f) then
424
return f.async_state_machine;
425
fi
426
427
if isa INSTANCE_ASYNC_CLOSURE(f) then
428
return f.async_state_machine;
429
fi
430
431
if isa STATIC_ASYNC_CLOSURE(f) then
432
return f.async_state_machine;
433
fi
434
435
if isa GLOBAL_ASYNC_CLOSURE(f) then
436
return f.async_state_machine;
437
fi
438
439
return null;
440
si
441
442
// Concrete async-function forms — thin extensions of the
443
// STATIC_METHOD / INSTANCE_METHOD / GLOBAL_FUNCTION classes
444
// that additionally carry an ASYNC_STATE_MACHINE.
445
446
class STATIC_ASYNC_METHOD: STATIC_METHOD is
447
async_state_machine: ASYNC_STATE_MACHINE public;
448
449
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
450
"class async";
451
452
gen_access(buffer: System.Text.StringBuilder) is
453
_gen_underscore_access(buffer);
454
si
455
456
is_accessible_to(accessor: Classy?) -> bool =>
457
_underscore_is_accessible_to(accessor);
458
459
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
460
super.init(location, span, owner, name, enclosing_scope);
461
462
async_state_machine = ASYNC_STATE_MACHINE(self);
463
si
464
si
465
466
class INSTANCE_ASYNC_METHOD: INSTANCE_METHOD is
467
async_state_machine: ASYNC_STATE_MACHINE public;
468
469
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
470
"async";
471
472
gen_access(buffer: System.Text.StringBuilder) is
473
_gen_underscore_access(buffer);
474
si
475
476
is_accessible_to(accessor: Classy?) -> bool =>
477
_underscore_is_accessible_to(accessor);
478
479
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
480
super.init(location, span, owner, name, enclosing_scope);
481
482
async_state_machine = ASYNC_STATE_MACHINE(self);
483
si
484
485
// Inside the body, `self` and instance-member access go via
486
// the async state-machine frame's _outer_self field rather
487
// than ldarg.0 (which inside MoveNext refers to the state
488
// machine itself). Mirrors INSTANCE_GENERATOR_METHOD.load_self.
489
load_self(location: LOCATION, loader: SYMBOL_LOADER) -> Value is
490
let frame = async_state_machine.frame;
491
492
if frame? then
493
frame.declare();
494
495
let outer_self_field = frame.outer_self_field;
496
497
if outer_self_field? then
498
let context = IoC.CONTAINER.instance.symbol_table.current_instance_context;
499
if context? then
500
return IR.Values.Load.OUTER_SELF(context, context.type, outer_self_field);
501
fi
502
fi
503
fi
504
505
return super.load_self(location, loader);
506
si
507
si
508
509
// An async method declared in a trait body. Always has a body — the
510
// async classification comes from finding `await` in one — so there
511
// is no abstract counterpart, and it is a default trait method for
512
// inheritance purposes like any other bodied trait member.
513
class DEFAULT_TRAIT_ASYNC_METHOD: INSTANCE_ASYNC_METHOD is
514
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
515
"default trait async";
516
517
is_default_trait_method: bool => true;
518
519
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
520
super.init(location, span, owner, name, enclosing_scope);
521
si
522
523
try_instance_override_me(into: Classy, overrider: Function, logger: Logger) is
524
super.try_instance_override_me(into, overrider, logger);
525
526
_check_ineffective_trait_override(into, overrider, logger);
527
si
528
si
529
530
class GLOBAL_ASYNC_FUNCTION: GLOBAL_FUNCTION is
531
async_state_machine: ASYNC_STATE_MACHINE public;
532
533
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
534
"global async";
535
536
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
537
super.init(location, span, owner, name, enclosing_scope);
538
539
async_state_machine = ASYNC_STATE_MACHINE(self);
540
si
541
si
542
543
// Async closure forms — parallel to the *_ASYNC_METHOD /
544
// GLOBAL_ASYNC_FUNCTION trio, but for user-written async lambdas.
545
// Each is a thin extension of the corresponding sync closure that
546
// additionally carries an ASYNC_STATE_MACHINE; generate-il dispatches
547
// on `async_state_machine_for(symbol)` to route lambda emission down
548
// the state-machine path. Nested lambdas inside an async closure
549
// are sync by default — `declare_closure` returns the regular
550
// form, matching the named-function convention.
551
552
class INSTANCE_ASYNC_CLOSURE: INSTANCE_CLOSURE is
553
async_state_machine: ASYNC_STATE_MACHINE public;
554
555
init(location: LOCATION, owner: Scope, name: string, enclosing_scope: Scope, is_recursive: bool) is
556
super.init(location, owner, name, enclosing_scope, is_recursive);
557
558
async_state_machine = ASYNC_STATE_MACHINE(self);
559
si
560
561
to_string() -> string => "[instance async closure {name}]";
562
si
563
564
class STATIC_ASYNC_CLOSURE: STATIC_CLOSURE is
565
async_state_machine: ASYNC_STATE_MACHINE public;
566
567
init(location: LOCATION, owner: Scope, name: string, enclosing_scope: Scope, is_recursive: bool) is
568
super.init(location, owner, name, enclosing_scope, is_recursive);
569
570
async_state_machine = ASYNC_STATE_MACHINE(self);
571
si
572
573
to_string() -> string => "[static async closure {name}]";
574
si
575
576
class GLOBAL_ASYNC_CLOSURE: GLOBAL_CLOSURE is
577
async_state_machine: ASYNC_STATE_MACHINE public;
578
579
init(location: LOCATION, owner: Scope, name: string, enclosing_scope: Scope, is_recursive: bool) is
580
super.init(location, owner, name, enclosing_scope, is_recursive);
581
582
async_state_machine = ASYNC_STATE_MACHINE(self);
583
si
584
585
to_string() -> string => "[global async closure {name}]";
586
si
587
si