Appearance
| 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 | // Generator state machines: the outer-method stub, the MoveNext body walk and the deferred | |
| 18 | // state-machine class emission. | |
| 19 | partial GENERATE_IL is | |
| 20 | // Generator IL emission: | |
| 21 | // | |
| 22 | // 1. The outer user-facing method gets a stub body | |
| 23 | // (`newobj $StateMachine.ctor(); ret`) emitted inline, | |
| 24 | // so the AST visit order is unchanged for the caller. | |
| 25 | // 2. The function body is walked here (recursively, via | |
| 26 | // self) into a fresh IR.Values.BLOCK that represents | |
| 27 | // MoveNext's body. Yield statements emit their state- | |
| 28 | // machine IL into this block as they are walked. | |
| 29 | // 3. The state-machine class itself is deferred — captured | |
| 30 | // in `_pending_state_machines` and emitted at the SAME | |
| 31 | // level as the user's class (after `visit(`class)` | |
| 32 | // closes the user class), since CIL doesn't allow | |
| 33 | // classes nested inside method bodies. | |
| 34 | _pre_generator_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, state_machine: Semantic.Symbols.STATE_MACHINE) -> bool is | |
| 35 | let frame = state_machine.frame | |
| 36 | ||
| 37 | if !frame? then | |
| 38 | _logger.error(function.location, "generator must return Pipe[T]") | |
| 39 | ||
| 40 | return true | |
| 41 | fi | |
| 42 | ||
| 43 | // Realise the frame's symbol-level declarations (fields | |
| 44 | // + ctor) once. Required for both the outer method's | |
| 45 | // newobj reference and the state-machine class itself. | |
| 46 | frame.declare() | |
| 47 | ||
| 48 | // --- Outer user-facing method --- | |
| 49 | // | |
| 50 | // Forward this method's own arguments, construct the | |
| 51 | // frame, return it. | |
| 52 | _emit_generator_outer_method_srm(symbol, frame, state_machine) | |
| 53 | ||
| 54 | // --- Walk the body into MoveNext's block --- | |
| 55 | // | |
| 56 | // The block lands captured on PENDING_STATE_MACHINE | |
| 57 | // alongside the state machine; `_emit_pending_state_ | |
| 58 | // machines` writes it out inside the class definition | |
| 59 | // once the enclosing user class has closed. | |
| 60 | // | |
| 61 | // For a generic generator, move each function-T symbol to | |
| 62 | // a class-level position on the frame, so any IR.Values | |
| 63 | // inside MoveNext that baked in function-T (callvirt owner | |
| 64 | // types on the for-each dispatch, locals init for the | |
| 65 | // iterator current, etc.) reach `!N` — MoveNext has no | |
| 66 | // type parameters of its own — rather than `!!N`. | |
| 67 | state_machine.install_body_emission_overrides() | |
| 68 | ||
| 69 | enter_scope(function) | |
| 70 | ||
| 71 | let move_next_block = Values.BLOCK() | |
| 72 | ||
| 73 | enter_block(move_next_block) | |
| 74 | ||
| 75 | // `.maxstack` must precede any IL opcodes in the | |
| 76 | // method body. Add it before the dispatch placeholder | |
| 77 | // so the populated dispatch IL lands after the | |
| 78 | // directive at the start of MoveNext. | |
| 79 | add(Values.MAX_STACK(64)) | |
| 80 | ||
| 81 | // Cache the state field to a CLR local at MoveNext | |
| 82 | // entry. Every yield keeps it in sync via `dup; stloc; | |
| 83 | // stfld` so per-region dispatches and state-guarded | |
| 84 | // finally bodies read a stable value. Same pattern as | |
| 85 | // the async path — needed to allow yield inside `.try` | |
| 86 | // without branching into a protected region from | |
| 87 | // outside (ECMA-335 forbids that, and the previous | |
| 88 | // centralized-dispatch shape had exactly that bug). | |
| 89 | let outer_dispatch_holder = | |
| 90 | _open_state_machine_entry(frame, frame.state_field, ".gen_state") | |
| 91 | ||
| 92 | // Stash the previous generator-yield-return label so | |
| 93 | // nested generator functions (rare) don't trip over | |
| 94 | // each other. visit(YIELD) lazily allocates a label the | |
| 95 | // first time it needs to `leave` from inside a `.try`. | |
| 96 | let prev_yield_return = _current_generator_yield_return_label | |
| 97 | _current_generator_yield_return_label = null | |
| 98 | ||
| 99 | // A frame whose previous pass ran out is put back as | |
| 100 | // constructed here, on the next MoveNext, rather than where | |
| 101 | // the stream ended: a bare `return` can sit under a `try` | |
| 102 | // whose finally still reads the locals, so the end paths only | |
| 103 | // mark the frame done and the rewind waits for the next pull. | |
| 104 | let int_type = _innate_symbol_lookup.get_int_type() | |
| 105 | let brancher = get_brancher_for_block() | |
| 106 | let not_done = LABEL() | |
| 107 | ||
| 108 | brancher.branch(BRANCH.NE, cast Value(Values.Load.TEMP(".gen_state", int_type)), cast Value(Literal.NUMBER(-1, int_type)), not_done) | |
| 109 | add(_build_frame_rewind(frame)) | |
| 110 | brancher.label(not_done) | |
| 111 | ||
| 112 | // Unpack any destructured formal arguments into their leaf | |
| 113 | // frame fields before the body reads them. The plain path's | |
| 114 | // AST walk does this from visit(Variables.VARIABLE); this | |
| 115 | // path drives its own walk and never reaches those nodes. | |
| 116 | _gen_destructured_formal_prologue(function) | |
| 117 | ||
| 118 | // Walk the body. Visitors fire pre/visit on every | |
| 119 | // node; visit(YIELD) is the interesting one — it | |
| 120 | // appends state-machine IL into current_block | |
| 121 | // (which IS move_next_block). | |
| 122 | if function.body? then | |
| 123 | function.body.walk(self) | |
| 124 | fi | |
| 125 | ||
| 126 | // Fell-off-end trailer: mark done, return false. The next | |
| 127 | // MoveNext rewinds the frame and starts the body over, as any | |
| 128 | // pipe does once it has run out. Plain `ret` since we're at the | |
| 129 | // MoveNext outer scope (no open `.try`). | |
| 130 | add(_build_frame_field_store( | |
| 131 | frame, frame.state_field, Values.INSTRUCTION(ILOpCode.LDC_I4_M1))) | |
| 132 | add(Values.INSTRUCTION(ILOpCode.LDC_I4_0)) | |
| 133 | add(Values.RET()) | |
| 134 | ||
| 135 | // If any yield inside an inner `.try` used the | |
| 136 | // leave-return-true label, emit it now (outside any try) | |
| 137 | // followed by `ldc.i4.1; ret`. Reached only via `leave | |
| 138 | // L:` from inside a protected region. | |
| 139 | if let yield_return_label = _current_generator_yield_return_label then | |
| 140 | add(Values.MARK_LABEL(yield_return_label)) | |
| 141 | add(Values.INSTRUCTION(ILOpCode.LDC_I4_1)) | |
| 142 | add(Values.RET()) | |
| 143 | fi | |
| 144 | ||
| 145 | _current_generator_yield_return_label = prev_yield_return | |
| 146 | ||
| 147 | _pop_async_dispatch_holder(outer_dispatch_holder) | |
| 148 | ||
| 149 | leave_block() | |
| 150 | leave_scope(function) | |
| 151 | ||
| 152 | // Uninstall — re-installed by _emit_state_machine_class | |
| 153 | // around move_next_block.gen() so deferred IR.Value.gen() | |
| 154 | // calls inside MoveNext still see the override. Between | |
| 155 | // here and there, other class members emit normally. | |
| 156 | state_machine.uninstall_body_emission_overrides() | |
| 157 | ||
| 158 | _pending_state_machines.add(PENDING_STATE_MACHINE(state_machine, move_next_block)) | |
| 159 | ||
| 160 | // Skip the framework's default body walk + the framework's | |
| 161 | // visit(function) close-out path. Our visit(function) | |
| 162 | // sees the state-machine and returns early. | |
| 163 | return true | |
| 164 | si | |
| 165 | ||
| 166 | _visit_generator_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, state_machine: Semantic.Symbols.STATE_MACHINE) is | |
| 167 | // _pre_generator_function emitted the outer method and | |
| 168 | // captured the move_next block onto _pending_state_machines. | |
| 169 | // Nothing more to do at AST visit time. | |
| 170 | si | |
| 171 | ||
| 172 | // `newobj` operand for the SM frame's constructor. For a | |
| 173 | // generic frame, constructs a specialized type so the | |
| 174 | // class-T's appear in the signature; for a non-generic frame, | |
| 175 | // just the default ctor's IL reference. Exceptions in lookup | |
| 176 | // are logged and a sentinel returned. | |
| 177 | // A generator's outer method, encoded rather than written. Its | |
| 178 | // whole body is: forward this method's own arguments, construct | |
| 179 | // the frame, return it. | |
| 180 | // | |
| 181 | // The row is the function's own, so this deposits its offset the | |
| 182 | // same way an ordinary method body does. | |
| 183 | _emit_generator_outer_method_srm( | |
| 184 | symbol: Semantic.Symbols.Function, | |
| 185 | frame: Semantic.Symbols.STATE_MACHINE_FRAME, | |
| 186 | state_machine: Semantic.Symbols.STATE_MACHINE | |
| 187 | ) is | |
| 188 | let assembly_emitter = _context.srm_assembly_emitter | |
| 189 | let body = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 190 | ||
| 191 | // An instance generator hands `this` to the frame's | |
| 192 | // constructor, which stores it into _outer_self so the | |
| 193 | // body's `self` access can read back through the frame. | |
| 194 | let slot mut = if symbol.is_emitted_with_receiver then 1 else 0 fi | |
| 195 | ||
| 196 | if frame.outer_self_field? then | |
| 197 | body.ldarg(0) | |
| 198 | fi | |
| 199 | ||
| 200 | for i in 0..symbol.argument_names.count do | |
| 201 | body.ldarg(slot) | |
| 202 | ||
| 203 | slot = slot + 1 | |
| 204 | od | |
| 205 | ||
| 206 | body.new_object( | |
| 207 | _context.resolve_call_target( | |
| 208 | _specialized_frame_constructor(frame, state_machine, symbol.location))) | |
| 209 | ||
| 210 | body.ret() | |
| 211 | ||
| 212 | assembly_emitter.handles.set_body_offset(symbol, body.flush(assembly_emitter)) | |
| 213 | assembly_emitter.handles.set_il_outputs(symbol, body.il_outputs) | |
| 214 | si | |
| 215 | ||
| 216 | // The frame constructor as it must be named from outside: on the | |
| 217 | // frame's own instantiation when the frame is generic, and on | |
| 218 | // the frame itself otherwise. | |
| 219 | _specialized_frame_constructor( | |
| 220 | frame: Semantic.Symbols.STATE_MACHINE_FRAME, | |
| 221 | state_machine: Semantic.Symbols.STATE_MACHINE, | |
| 222 | location: Source.LOCATION | |
| 223 | ) -> Semantic.Symbols.Function => | |
| 224 | _specialized_constructor( | |
| 225 | frame, | |
| 226 | frame.constructor, | |
| 227 | state_machine.get_construction_type_arguments(), | |
| 228 | location) | |
| 229 | ||
| 230 | // The same, for any frame: a constructed generic's constructor | |
| 231 | // is a different symbol from the open type's, and only the | |
| 232 | // constructed one names a type that exists at run time. | |
| 233 | _specialized_constructor( | |
| 234 | frame: Semantic.Symbols.Classy, | |
| 235 | default_constructor: Semantic.Symbols.Function, | |
| 236 | construction_type_args: Collections.List[Semantic.Types.Type]?, | |
| 237 | location: Source.LOCATION | |
| 238 | ) -> Semantic.Symbols.Function is | |
| 239 | // With no type arguments there is nothing to specialize | |
| 240 | // over, and the constructed form would be a second symbol | |
| 241 | // for the same constructor — one the numbering pass never | |
| 242 | // saw, so it has no row and gets named as if it were | |
| 243 | // imported. | |
| 244 | if !construction_type_args? \/ construction_type_args.count == 0 then | |
| 245 | return default_constructor | |
| 246 | fi | |
| 247 | ||
| 248 | if let specialized = | |
| 249 | _specialized_frame_type(frame, construction_type_args, location) | |
| 250 | then | |
| 251 | if let function: Semantic.Symbols.Function = specialized.find_member("init") then | |
| 252 | return function | |
| 253 | fi | |
| 254 | fi | |
| 255 | ||
| 256 | return default_constructor | |
| 257 | si | |
| 258 | ||
| 259 | _emit_state_machine_class(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: Values.BLOCK) is | |
| 260 | let frame = state_machine.frame | |
| 261 | ||
| 262 | assert frame? else "state machine has no frame at IL emission" | |
| 263 | ||
| 264 | // Local field types snapshot at the compile-expressions | |
| 265 | // walk moment; narrowing can retype the source local | |
| 266 | // later, so re-read the settled types before emitting. | |
| 267 | frame.refresh_local_field_types() | |
| 268 | ||
| 269 | // Re-install the body-emission override so deferred | |
| 270 | // IR.Value.gen() inside MoveNext renders function-T as | |
| 271 | // class-level `!N`. Uninstalled at the bottom of this | |
| 272 | // method. | |
| 273 | state_machine.install_body_emission_overrides() | |
| 274 | ||
| 275 | // .ctor — parameterless, chains to Object::.ctor. | |
| 276 | _gen_state_machine_ctor_srm(frame) | |
| 277 | ||
| 278 | // MoveNext — populated during the body walk. | |
| 279 | _emit_frame_member_body_srm( | |
| 280 | frame, IR.Emitter.FrameMember.MOVE_NEXT, move_next_block) | |
| 281 | ||
| 282 | // Other Iterator[T] / IDisposable members. | |
| 283 | _gen_state_machine_accessors_srm(frame) | |
| 284 | ||
| 285 | state_machine.uninstall_body_emission_overrides() | |
| 286 | si | |
| 287 | ||
| 288 | // The IL that puts a frame back as constructed: the state to its | |
| 289 | // initial value, each argument field restored from its pristine | |
| 290 | // twin (the body may have written the working copy - a parameter | |
| 291 | // is just a local to it), and every hoisted field back to its | |
| 292 | // default, so a body that re-runs from the top behaves exactly as | |
| 293 | // a freshly constructed one would. Re-entering at state 0 re-runs | |
| 294 | // initializers for locals it re-declares; a deferred-init local | |
| 295 | // without one reads the default here rather than a stale value | |
| 296 | // from the previous pass. Emitted as the Reset member, and at the | |
| 297 | // top of MoveNext for a frame whose previous pass ran out, since a | |
| 298 | // pipe starts over once it has run out. | |
| 299 | _build_frame_rewind(frame: Semantic.Symbols.STATE_MACHINE_FRAME) -> Values.BLOCK is | |
| 300 | let block = Values.BLOCK() | |
| 301 | ||
| 302 | block.add(_build_frame_field_store( | |
| 303 | frame, frame.state_field, Values.INSTRUCTION(ILOpCode.LDC_I4_0))) | |
| 304 | ||
| 305 | let initial_fields = frame.argument_initial_fields | |
| 306 | let index mut = 0 | |
| 307 | ||
| 308 | for arg_field in frame.argument_fields do | |
| 309 | block.add(_build_frame_field_store( | |
| 310 | frame, arg_field, _build_frame_field_load(frame, initial_fields[index]))) | |
| 311 | ||
| 312 | index = index + 1 | |
| 313 | od | |
| 314 | ||
| 315 | for local_field in frame.local_fields do | |
| 316 | block.add(_build_frame_field_store( | |
| 317 | frame, local_field, Values.DEFAULT(local_field.type!))) | |
| 318 | od | |
| 319 | ||
| 320 | return block | |
| 321 | si | |
| 322 | ||
| 323 | // A frame member the lowering synthesises, encoded into a body | |
| 324 | // of its own and deposited against the (frame, member) key the | |
| 325 | // structure walk reads when it writes the row. There is no | |
| 326 | // symbol to key on: the member exists only in the emitted | |
| 327 | // assembly. | |
| 328 | _emit_frame_member_body_srm( | |
| 329 | frame: Semantic.Symbols.STATE_MACHINE_FRAME, | |
| 330 | member: IR.Emitter.FrameMember, | |
| 331 | block: Values.BLOCK | |
| 332 | ) is | |
| 333 | let assembly_emitter = _context.srm_assembly_emitter | |
| 334 | let previous = _context.current_srm_body_emitter | |
| 335 | ||
| 336 | let body = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 337 | ||
| 338 | _context.current_srm_body_emitter = body | |
| 339 | ||
| 340 | block.gen(_context) | |
| 341 | ||
| 342 | _context.current_srm_body_emitter = previous | |
| 343 | ||
| 344 | assembly_emitter.handles.set_frame_member_body( | |
| 345 | frame, member, body.flush(assembly_emitter)) | |
| 346 | si | |
| 347 | ||
| 348 | // The frame's constructor: chain to Object, then copy each | |
| 349 | // constructor argument into the field the lowering declared for | |
| 350 | // it. It is a declared symbol with a row of its own, so its body | |
| 351 | // is deposited the same way any other method's is. | |
| 352 | _gen_state_machine_ctor_srm(frame: Semantic.Symbols.STATE_MACHINE_FRAME) is | |
| 353 | let assembly_emitter = _context.srm_assembly_emitter | |
| 354 | let body = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 355 | ||
| 356 | body.ldarg(0) | |
| 357 | body.call(_context.resolve_object_constructor()) | |
| 358 | ||
| 359 | // Argument layout mirrors what STATE_MACHINE_FRAME.declare | |
| 360 | // set up: [$outer_self,] arg0, arg1, ... The constructor is | |
| 361 | // an instance method, so its declared arguments start at | |
| 362 | // slot 1. | |
| 363 | let slot mut = 1 | |
| 364 | ||
| 365 | if let outer_self_field = frame.outer_self_field then | |
| 366 | body.ldarg(0) | |
| 367 | body.ldarg(slot) | |
| 368 | body.stfld(_context.resolve_field_target(outer_self_field)) | |
| 369 | ||
| 370 | slot = slot + 1 | |
| 371 | fi | |
| 372 | ||
| 373 | // The pristine twins, index-aligned with `argument_fields`. | |
| 374 | let initial_fields = frame.argument_initial_fields | |
| 375 | ||
| 376 | for arg_field in frame.argument_fields do | |
| 377 | body.ldarg(0) | |
| 378 | body.ldarg(slot) | |
| 379 | body.stfld(_context.resolve_field_target(arg_field)) | |
| 380 | ||
| 381 | slot = slot + 1 | |
| 382 | od | |
| 383 | ||
| 384 | // Copy each freshly stored argument into its pristine twin, | |
| 385 | // so the emitted Reset can put the working fields back to | |
| 386 | // what the generator was constructed with after the body | |
| 387 | // has written them. | |
| 388 | let index mut = 0 | |
| 389 | ||
| 390 | for arg_field in frame.argument_fields do | |
| 391 | body.ldarg(0) | |
| 392 | body.ldarg(0) | |
| 393 | body.ldfld(_context.resolve_field_target(arg_field)) | |
| 394 | body.stfld(_context.resolve_field_target(initial_fields[index])) | |
| 395 | ||
| 396 | index = index + 1 | |
| 397 | od | |
| 398 | ||
| 399 | body.ret() | |
| 400 | ||
| 401 | assembly_emitter.handles.set_body_offset( | |
| 402 | frame.constructor, body.flush(assembly_emitter)) | |
| 403 | assembly_emitter.handles.set_il_outputs( | |
| 404 | frame.constructor, body.il_outputs) | |
| 405 | si | |
| 406 | ||
| 407 | ||
| 408 | // The iterator surface every consumer reaches a frame through. | |
| 409 | // Each body is fixed and small, so they are built here rather | |
| 410 | // than walked: get_Current reads the current field, | |
| 411 | // GetEnumerator returns self, Dispose does nothing, Reset zeroes | |
| 412 | // the state, and ToString joins the pipe. | |
| 413 | _gen_state_machine_accessors_srm(frame: Semantic.Symbols.STATE_MACHINE_FRAME) is | |
| 414 | let assembly_emitter = _context.srm_assembly_emitter | |
| 415 | let handles = assembly_emitter.handles | |
| 416 | ||
| 417 | // get_Current — read the current field. | |
| 418 | let get_current = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 419 | get_current.ldarg(0) | |
| 420 | get_current.ldfld(_context.resolve_field_target(frame.current_field)) | |
| 421 | get_current.ret() | |
| 422 | handles.set_frame_member_body( | |
| 423 | frame, | |
| 424 | IR.Emitter.FrameMember.GET_CURRENT, | |
| 425 | get_current.flush(assembly_emitter)) | |
| 426 | ||
| 427 | // GetEnumerator — the frame implements Iterator[T] itself, | |
| 428 | // so it is its own enumerator. | |
| 429 | let get_enumerator = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 430 | get_enumerator.ldarg(0) | |
| 431 | get_enumerator.ret() | |
| 432 | handles.set_frame_member_body( | |
| 433 | frame, | |
| 434 | IR.Emitter.FrameMember.GET_ENUMERATOR, | |
| 435 | get_enumerator.flush(assembly_emitter)) | |
| 436 | ||
| 437 | // Dispose — a no-op. A generator abandoned mid-flight does | |
| 438 | // not run the user's finally bodies. | |
| 439 | let dispose = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 440 | dispose.ret() | |
| 441 | handles.set_frame_member_body( | |
| 442 | frame, IR.Emitter.FrameMember.DISPOSE, dispose.flush(assembly_emitter)) | |
| 443 | ||
| 444 | // Reset — rewind to a freshly constructed frame, then return. | |
| 445 | let reset = _build_frame_rewind(frame) | |
| 446 | reset.add(Values.RET()) | |
| 447 | _emit_frame_member_body_srm(frame, IR.Emitter.FrameMember.RESET, reset) | |
| 448 | ||
| 449 | // ToString — a generator is a Pipe[T], so stringify it the | |
| 450 | // way every other Pipe does. The trait's default is a | |
| 451 | // default interface method and does not override | |
| 452 | // Object.ToString, so each implementing class needs its own. | |
| 453 | let to_string = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 454 | to_string.ldarg(0) | |
| 455 | to_string.call(_context.resolve_pipe_join(frame.class_element_type)) | |
| 456 | to_string.ret() | |
| 457 | handles.set_frame_member_body( | |
| 458 | frame, IR.Emitter.FrameMember.TO_STRING, to_string.flush(assembly_emitter)) | |
| 459 | si | |
| 460 | si | |
| 461 | si |