Skip to content
← Back

src/semantic/symbols/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-yield bookkeeping recorded by yield IL emission.
15
// The resumption label is placed immediately after the
16
// `ret` that suspends the generator; entry-dispatch jumps
17
// here when MoveNext re-enters in this state.
18
class STATE_LABEL is
19
state: int
20
label: IR.LABEL
21
22
init(state: int, label: IR.LABEL) is
23
self.state = state
24
self.label = label
25
si
26
si
27
28
// State held on a generator function symbol: the synthesised
29
// frame class, the running state-number counter, the recorded
30
// (state, resumption-label) pairs for entry-dispatch emission.
31
// Lives as a non-null field on each *_GENERATOR_* function;
32
// checked-for via `state_machine_for(...)`.
33
class STATE_MACHINE is
34
function: Function public
35
36
_frame: STATE_MACHINE_FRAME?
37
_next_state: int
38
_yield_labels: Collections.LIST[STATE_LABEL]
39
40
init(function: Function) is
41
self.function = function
42
43
_next_state = 1
44
_yield_labels = Collections.LIST[STATE_LABEL]()
45
si
46
47
// Lazy: the frame class is materialised on first access,
48
// pulling its element type from the (now-resolved) function
49
// return type. Returns null only if the return type is not
50
// an `Iterable[T]` / `Iterator[T]` — which is an upstream
51
// diagnostic, not something the frame can recover from.
52
frame: STATE_MACHINE_FRAME? is
53
if !_frame? then
54
let element_type = _extract_element_type()
55
56
if !element_type? then
57
return null
58
fi
59
60
_frame = STATE_MACHINE_FRAME(function.owner!, function, element_type)
61
fi
62
63
return _frame
64
si
65
66
_extract_element_type() -> Type? is
67
let return_type = function.return_type
68
69
if !return_type? then
70
return null
71
fi
72
73
let lookup = IoC.CONTAINER.instance.innate_symbol_lookup
74
75
// A generator returns Pipe[T]; extract T from the Pipe
76
// itself (self-match), not from its Iterable[T] base — the
77
// base carries Pipe's own type parameter, not the concrete
78
// element type the return is constructed over. When Pipe is
79
// unavailable (no runtime referenced) fall back to the bare
80
// Iterable[T] / Iterator[T] forms.
81
let pipe = lookup.get_unspecialized_pipe_type()
82
83
if pipe? then
84
return TYPE_ARGUMENT_EXTRACTOR.extract(return_type, pipe)
85
fi
86
87
let candidates = Collections.LIST[Type]()
88
candidates.add(lookup.get_unspecialized_iterator_type())
89
candidates.add(lookup.get_unspecialized_iterable_type())
90
91
return TYPE_ARGUMENT_EXTRACTOR.extract_from_any(return_type, candidates)
92
si
93
94
allocate_state() -> int is
95
let n = _next_state
96
97
_next_state = _next_state + 1
98
99
return n
100
si
101
102
record_label(state: int, label: IR.LABEL) is
103
_yield_labels.add(STATE_LABEL(state, label))
104
si
105
106
yield_labels: Collections.Iterable[STATE_LABEL] => _yield_labels
107
108
// Move every type parameter visible inside the body to a
109
// class-level position on the frame, and back again, so
110
// IR.Values built by compile-expressions reach `!N` — what
111
// MoveNext sees — rather than `!!N` on the original function.
112
// See `STATE_MACHINE_TYPE_PARAMS` for details.
113
install_body_emission_overrides() is
114
STATE_MACHINE_TYPE_PARAMS.walk_install(function, true)
115
si
116
117
uninstall_body_emission_overrides() is
118
STATE_MACHINE_TYPE_PARAMS.walk_install(function, false)
119
si
120
121
// Type arguments to pass at `newobj` construction time, in
122
// the same canonical order the frame captures them. See
123
// `STATE_MACHINE_TYPE_PARAMS` for details.
124
get_construction_type_arguments() -> Collections.List[Type]? =>
125
STATE_MACHINE_TYPE_PARAMS.construction_type_arguments(function)
126
si
127
128
// Synthesised class holding a generator's state machine.
129
// Implements `Iterator[T]` (so the ienumerable/ienumerator
130
// boilerplate emitters fire automatically). Members: `$state`
131
// (0=initial, -1=done, N=resumption point), `$current` (last
132
// yielded value), `$outer_self` (instance-only). The Iterator
133
// methods (MoveNext/get_Current/Dispose/Reset) are hand-emitted
134
// in generate_il rather than declared ghūl-side.
135
class STATE_MACHINE_FRAME: STATE_MACHINE_FRAME_BASE is
136
_next_id: int static
137
138
_element_type: Type
139
140
// Null until declare() runs; outer-self stays null past
141
// declare() for static and global generators.
142
_state_field: Field?
143
_current_field: Field?
144
145
element_type: Type => _element_type
146
147
// The element type rewritten into the frame class's scope —
148
// for use when hand-emitting IL strings that reference the
149
// element type inside the class body (e.g. accessor method
150
// signatures). Renders as `!N` (class-level) for a generic
151
// generator, same as `_element_type` for a non-generic one.
152
class_element_type: Type => _class_relative(_element_type)
153
// Non-null once declare() has run.
154
state_field: Field => _state_field!
155
current_field: Field => _current_field!
156
157
next_id: int static is
158
let result = _next_id
159
_next_id = _next_id + 1
160
return result
161
si
162
163
init(owner: Scope, owning_function: Function, element_type: Type) is
164
let owner_owner: Scope mut
165
166
if isa Symbol(owner) then
167
let owner_symbol = owner
168
owner_owner = owner_symbol.owner!
169
else
170
owner_owner = owner
171
fi
172
173
super.init(
174
LOCATION.internal,
175
LOCATION.internal,
176
owner_owner,
177
"$StateMachine_{owning_function.name}_{next_id}",
178
owner,
179
owning_function
180
)
181
182
_element_type = element_type
183
184
set_type(Types.NAMED(self))
185
si
186
187
// Declare the frame's members lazily — called by IL emission.
188
// Fields and constructor get real Symbol entities so reference
189
// emission (ldfld, stfld, newobj) routes through the existing
190
// SYMBOL_LOADER paths. The Iterator[T] / Iterable[T] methods
191
// are NOT declared as ghūl symbols — their IL is emitted by
192
// hand in `gen_all` using the canonical .NET names.
193
declare() is
194
if _state_field? then
195
// Recompute argument field types that may have been
196
// captured as not-yet-settled inference placeholders on
197
// the first declare().
198
refresh_argument_field_types()
199
return
200
fi
201
202
let listener = IoC.CONTAINER.instance.symbol_definition_locations
203
204
declare_captured_type_params(listener)
205
206
let int_type = IoC.CONTAINER.instance.innate_symbol_lookup.get_int_type()
207
208
let state_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$state")
209
state_field.set_type(int_type)
210
declare(LOCATION.internal, state_field, listener)
211
_state_field = state_field
212
213
let current_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$current")
214
current_field.set_type(_class_relative(_element_type))
215
declare(LOCATION.internal, current_field, listener)
216
_current_field = current_field
217
218
let ctor_argument_names = Collections.LIST[string]()
219
let ctor_argument_types = Collections.LIST[Type]()
220
221
// Instance generators carry a reference to the user's
222
// enclosing instance so the body's `self` / instance
223
// field access can be redirected via `ldarg.0; ldfld
224
// _outer_self`. The outer method passes `this` to the
225
// .ctor as the first argument.
226
if owning_function.is_instance /\ owning_function.owner? /\ isa Classy(owning_function.owner) then
227
let outer_classy = cast Classy?(owning_function.owner)!
228
let self_type = outer_self_type(outer_classy)
229
230
let outer_self_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$outer_self")
231
outer_self_field.set_type(self_type)
232
declare(LOCATION.internal, outer_self_field, listener)
233
_outer_self_field = outer_self_field
234
235
ctor_argument_names.add("$outer_self")
236
ctor_argument_types.add(self_type)
237
fi
238
239
if owning_function.argument_names.count > 0 then
240
for arg_name in owning_function.argument_names do
241
let local = cast Symbols.LOCAL_ARGUMENT?(owning_function.find_direct(arg_name))
242
243
if !local? then
244
continue
245
fi
246
247
// As in the async frame: collapse a settled
248
// INFERRED_VARIABLE_TYPE placeholder before it can
249
// reach a field signature.
250
let arg_field_type = _class_relative(
251
SETTLED_PLACEHOLDER_RESOLVER.instance.resolve(local.type ?? Types.ERROR()))
252
253
let arg_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$arg_{arg_name}")
254
arg_field.set_type(arg_field_type)
255
declare(LOCATION.internal, arg_field, listener)
256
257
_argument_fields.add(arg_field)
258
259
// A pristine copy of the constructor argument, so the
260
// emitted Reset can restore what the body started
261
// from after the body has written the working field.
262
// Prefixed rather than suffixed: a suffix on one
263
// synthesised name can collide with another - a
264
// generator taking `n` and `n_initial` would derive
265
// `$arg_n_initial` twice - while no `$arg_`-prefixed
266
// name can equal an `$initial_arg_`-prefixed one.
267
let arg_initial_field = Symbols.INSTANCE_FIELD(LOCATION.internal, self, "$initial_arg_{arg_name}")
268
arg_initial_field.set_type(arg_field_type)
269
declare(LOCATION.internal, arg_initial_field, listener)
270
271
_argument_initial_fields.add(arg_initial_field)
272
273
local.state_machine_field = arg_field
274
275
ctor_argument_names.add(arg_name)
276
ctor_argument_types.add(arg_field_type)
277
od
278
fi
279
280
let ctor = Symbols.INSTANCE_METHOD(LOCATION.internal, LOCATION.internal, self, "init", self)
281
ctor.set_arguments(ctor_argument_names, ctor_argument_types)
282
ctor.set_void_return_type()
283
declare(LOCATION.internal, ctor, listener)
284
_constructor = ctor
285
286
_ctor_argument_names = ctor_argument_names
287
288
// Ancestors — Object + Iterable[T]/Iterator[T] so the
289
// standard boilerplate emitters (gen_extends /
290
// gen_implements, ienumerable/ienumerator bridges) fire.
291
// Interface args use the frame's own T so the implements
292
// clause emits `IEnumerable<!N>`.
293
let lookup = IoC.CONTAINER.instance.innate_symbol_lookup
294
let class_relative_element = _class_relative(_element_type)
295
296
add_ancestor(lookup.get_object_type())
297
add_ancestor(_construct_specialised(lookup.get_unspecialized_iterable_type(), class_relative_element))
298
add_ancestor(_construct_specialised(lookup.get_unspecialized_iterator_type(), class_relative_element))
299
300
// If the declared return type is a *proper* subtype of
301
// Iterable[T] / Iterator[T] — a richer iterable trait such
302
// as Ghul.Pipes.Pipe[T] — implement it too, so the outer
303
// method can return the state machine directly as that
304
// type and the caller gets the trait's members (the pipe
305
// combinators) for free. A return type that is exactly
306
// Iterable[T] or Iterator[T] is already covered above;
307
// find_ancestor does not self-match, so it is not re-added.
308
let return_type = owning_function.return_type
309
310
if
311
return_type? /\
312
return_type.is_settled /\
313
(
314
return_type.find_ancestor(lookup.get_unspecialized_iterable_type())? \/
315
return_type.find_ancestor(lookup.get_unspecialized_iterator_type())?
316
)
317
then
318
add_ancestor(_class_relative(return_type))
319
fi
320
si
321
322
_construct_specialised(unspecialized: Type, type_arg: Type) -> Type is
323
let classy = cast Symbols.Classy?(unspecialized.symbol.unspecialized_symbol)!
324
325
let args = Collections.LIST[Type]()
326
args.add(type_arg)
327
328
return Types.GENERIC(LOCATION.internal, classy, args)
329
si
330
si
331
332
// Free helper: returns the STATE_MACHINE held by any of the three
333
// generator function forms, or null if `f` is a plain function.
334
state_machine_for(f: Function?) -> STATE_MACHINE? is
335
if !f? then
336
return null
337
fi
338
339
if isa STATIC_GENERATOR_METHOD(f) then
340
return f.state_machine
341
fi
342
343
if isa INSTANCE_GENERATOR_METHOD(f) then
344
return f.state_machine
345
fi
346
347
if isa GLOBAL_GENERATOR_FUNCTION(f) then
348
return f.state_machine
349
fi
350
351
return null
352
si
353
354
// Concrete generator-function forms — thin extensions of the
355
// existing function/method classes that additionally carry a
356
// STATE_MACHINE bookkeeping object. Each form differs only in
357
// its CLR-level signature shape (instance, static, global).
358
359
class STATIC_GENERATOR_METHOD: STATIC_METHOD is
360
state_machine: STATE_MACHINE public
361
362
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
363
"class generator"
364
365
is_accessible_to(accessor: Classy?) -> bool =>
366
_underscore_is_accessible_to(accessor)
367
368
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
369
super.init(location, span, owner, name, enclosing_scope)
370
371
state_machine = STATE_MACHINE(self)
372
si
373
si
374
375
class INSTANCE_GENERATOR_METHOD: INSTANCE_METHOD is
376
state_machine: STATE_MACHINE public
377
378
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
379
"generator"
380
381
is_accessible_to(accessor: Classy?) -> bool =>
382
_underscore_is_accessible_to(accessor)
383
384
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
385
super.init(location, span, owner, name, enclosing_scope)
386
387
state_machine = STATE_MACHINE(self)
388
si
389
390
// Inside the body, `self` and instance-member access go via
391
// the state-machine frame's _outer_self field rather than
392
// ldarg.0 (which would point at the state machine itself).
393
// Force the frame's declare() so _outer_self_field is
394
// available — compile-expressions reaches here before
395
// _pre_generator_function (the regular declare site) but
396
// after return-type resolution, so the lazy frame creation
397
// succeeds.
398
load_self(location: LOCATION, loader: SYMBOL_LOADER) -> Value is
399
let frame = state_machine.frame
400
401
if frame? then
402
frame.declare()
403
404
let outer_self_field = frame.outer_self_field
405
406
if outer_self_field? then
407
let context = IoC.CONTAINER.instance.symbol_table.current_instance_context
408
if context? then
409
return IR.Values.Load.OUTER_SELF(context, context.type, outer_self_field)
410
fi
411
fi
412
fi
413
414
return super.load_self(location, loader)
415
si
416
si
417
418
// A generator declared in a trait body. Always has a body — the
419
// generator classification comes from finding `yield` in one — so
420
// there is no abstract counterpart, and it is a default trait
421
// method for inheritance purposes like any other bodied trait
422
// member.
423
class DEFAULT_TRAIT_GENERATOR_METHOD: INSTANCE_GENERATOR_METHOD is
424
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
425
"default trait generator"
426
427
is_default_trait_method: bool => true
428
429
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
430
super.init(location, span, owner, name, enclosing_scope)
431
si
432
433
try_instance_override_me(into: Classy, overrider: Function, logger: Logger) is
434
super.try_instance_override_me(into, overrider, logger)
435
436
_check_ineffective_trait_override(into, overrider, logger)
437
si
438
si
439
440
class GLOBAL_GENERATOR_FUNCTION: GLOBAL_FUNCTION is
441
state_machine: STATE_MACHINE public
442
443
describe_kind(context: DESCRIBE_CONTEXT) -> string? =>
444
"global generator"
445
446
init(location: LOCATION, span: LOCATION, owner: Scope, name: string, enclosing_scope: Scope) is
447
super.init(location, span, owner, name, enclosing_scope)
448
449
state_machine = STATE_MACHINE(self)
450
si
451
si
452
si