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 | // Async machinery: the async outer method, dispatch holders, frame construction and the | |
| 18 | // async state-machine class emission. | |
| 19 | partial GENERATE_IL is | |
| 20 | // The outer method of an async function: everything | |
| 21 | // ASYNC_METHOD_LAUNCH emits, plus the return that hands back the | |
| 22 | // builder's task. | |
| 23 | _emit_async_outer_method_srm( | |
| 24 | symbol: Semantic.Symbols.Function, | |
| 25 | launch: IR.Values.ASYNC_METHOD_LAUNCH | |
| 26 | ) is | |
| 27 | let assembly_emitter = _context.srm_assembly_emitter | |
| 28 | let enclosing = _context.current_srm_body_emitter | |
| 29 | let body = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 30 | ||
| 31 | _context.current_srm_body_emitter = body | |
| 32 | ||
| 33 | try | |
| 34 | launch.gen(_context) | |
| 35 | ||
| 36 | body.ret() | |
| 37 | ||
| 38 | assembly_emitter.handles.set_body_offset(symbol, body.flush(assembly_emitter)) | |
| 39 | assembly_emitter.handles.set_il_outputs(symbol, body.il_outputs) | |
| 40 | finally | |
| 41 | _context.current_srm_body_emitter = enclosing | |
| 42 | yrt | |
| 43 | si | |
| 44 | ||
| 45 | // The frame type as it must be named in IL: constructed over the | |
| 46 | // type arguments visible inside the owning function, so a frame | |
| 47 | // declared in a generic owner is never referred to by its open | |
| 48 | // form. Null when the frame is not generic. | |
| 49 | _specialized_frame_type( | |
| 50 | frame: Semantic.Symbols.Classy, | |
| 51 | construction_type_args: Collections.List[Semantic.Types.Type]?, | |
| 52 | location: Source.LOCATION | |
| 53 | ) -> Semantic.Types.Type? is | |
| 54 | if !construction_type_args? then | |
| 55 | return null | |
| 56 | fi | |
| 57 | ||
| 58 | return Semantic.Types.GENERIC(location, frame, construction_type_args) | |
| 59 | si | |
| 60 | ||
| 61 | // A frame field as it must be named from the outer method: on | |
| 62 | // the frame's own instantiation, so the reference and the | |
| 63 | // field's type are both in the outer method's terms rather than | |
| 64 | // the frame's own. | |
| 65 | _specialized_field( | |
| 66 | frame: Semantic.Symbols.Classy, | |
| 67 | `field: Semantic.Symbols.Field, | |
| 68 | construction_type_args: Collections.List[Semantic.Types.Type]?, | |
| 69 | location: Source.LOCATION | |
| 70 | ) -> Semantic.Symbols.Field is | |
| 71 | if !construction_type_args? \/ construction_type_args.count == 0 then | |
| 72 | return `field | |
| 73 | fi | |
| 74 | ||
| 75 | if let specialized = | |
| 76 | _specialized_frame_type(frame, construction_type_args, location) | |
| 77 | then | |
| 78 | if let found: Semantic.Symbols.Field = | |
| 79 | specialized.find_member(`field.name) | |
| 80 | then | |
| 81 | return found | |
| 82 | fi | |
| 83 | fi | |
| 84 | ||
| 85 | return `field | |
| 86 | si | |
| 87 | ||
| 88 | // Async-function IL emission. Parallels | |
| 89 | // `_pre_generator_function`: emits the outer method (newobj | |
| 90 | // SM, builder.Start, return Task) and stashes a MoveNext | |
| 91 | // BLOCK to be flushed when the enclosing class closes. | |
| 92 | // MoveNext wraps the body in a try/catch routing exceptions | |
| 93 | // through `builder.SetException` and success through | |
| 94 | // `builder.SetResult`. | |
| 95 | _pre_async_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE) -> bool is | |
| 96 | let frame = async_state_machine.frame | |
| 97 | ||
| 98 | if !frame? then | |
| 99 | _logger.error(function.location, "async function must return a task-like type — one carrying AsyncMethodBuilderAttribute, such as Tasks.TASK or Tasks.ValueTask") | |
| 100 | return true | |
| 101 | fi | |
| 102 | ||
| 103 | frame.declare() | |
| 104 | ||
| 105 | let construction_type_args = async_state_machine.get_construction_type_arguments() | |
| 106 | ||
| 107 | let constructor = | |
| 108 | _specialized_constructor( | |
| 109 | frame, frame.constructor, construction_type_args, symbol.location) | |
| 110 | ||
| 111 | // The local holding the state machine, and the type argument | |
| 112 | // to builder.Start, both name the frame type. A frame in a | |
| 113 | // generic owner is generic too, so both need the constructed | |
| 114 | // form — the open form does not resolve at run time. | |
| 115 | let state_machine_type = | |
| 116 | _specialized_frame_type(frame, construction_type_args, symbol.location) ?? frame.type! | |
| 117 | ||
| 118 | // ctor args: instance methods prepend `ldarg.0` for | |
| 119 | // _outer_self, then each user-declared parameter via | |
| 120 | // `ldarg '<name>'`. Build them as a sequence Value so | |
| 121 | // ASYNC_METHOD_LAUNCH composes them in the right order. | |
| 122 | let ctor_args_block = Values.BLOCK() | |
| 123 | ||
| 124 | // The enclosing method's own arguments, forwarded | |
| 125 | // positionally: an instance method's receiver occupies slot | |
| 126 | // 0, so its declared arguments start at 1. | |
| 127 | let slot mut = if symbol.is_emitted_with_receiver then 1 else 0 fi | |
| 128 | ||
| 129 | if frame.outer_self_field? then | |
| 130 | ctor_args_block.add(Values.Load.ARGUMENT_SLOT(0, "ldarg.0")) | |
| 131 | fi | |
| 132 | for arg_name in symbol.argument_names do | |
| 133 | ctor_args_block.add(Values.Load.ARGUMENT_SLOT(slot, "ldarg '{arg_name}'")) | |
| 134 | slot = slot + 1 | |
| 135 | od | |
| 136 | ctor_args_block.close() | |
| 137 | ||
| 138 | let task_type = symbol.return_type! | |
| 139 | ||
| 140 | let launch = IR.Values.ASYNC_METHOD_LAUNCH( | |
| 141 | task_type, | |
| 142 | state_machine_type, | |
| 143 | constructor, | |
| 144 | ctor_args_block, | |
| 145 | _specialized_field( | |
| 146 | frame, frame.state_field, construction_type_args, symbol.location), | |
| 147 | _specialized_field( | |
| 148 | frame, frame.builder_field!, construction_type_args, symbol.location), | |
| 149 | ".sm" | |
| 150 | ) | |
| 151 | ||
| 152 | _emit_async_outer_method_srm(symbol, launch) | |
| 153 | ||
| 154 | // --- Walk the body into MoveNext's block --- | |
| 155 | async_state_machine.install_body_emission_overrides() | |
| 156 | ||
| 157 | enter_scope(function) | |
| 158 | ||
| 159 | let move_next_block = Values.BLOCK() | |
| 160 | ||
| 161 | enter_block(move_next_block) | |
| 162 | ||
| 163 | add(Values.MAX_STACK(64)) | |
| 164 | ||
| 165 | // Cache the state field to a CLR local at MoveNext entry. | |
| 166 | // Every AWAIT_SUSPEND keeps this local in sync via | |
| 167 | // `dup; stloc; stfld` so the per-region dispatch (below) | |
| 168 | // and the finally-body state guards read a stable value. | |
| 169 | // | |
| 170 | // Finally bodies (let-use auto-dispose and user-written | |
| 171 | // `finally`) state-guard their work on the same local: | |
| 172 | // when V_state >= 0 the function is mid-suspend (the | |
| 173 | // `leave` from the AwaitUnsafeOnCompleted path fires the | |
| 174 | // finally before exiting), and side effects must be | |
| 175 | // skipped. | |
| 176 | let outer_dispatch_holder = | |
| 177 | _open_state_machine_entry(frame, frame.state_field, ".async_state") | |
| 178 | ||
| 179 | // Labels used by the trailer: success fall-through and | |
| 180 | // the end of the wrapping try/catch. The entire MoveNext | |
| 181 | // body lives inside a `.try` whose handler routes | |
| 182 | // exceptions through `builder.SetException(e)`. | |
| 183 | let success_label = IR.LABEL() | |
| 184 | let end_label = IR.LABEL() | |
| 185 | ||
| 186 | // Stash the emission context so visit(LET-await), | |
| 187 | // visit(AWAIT), and visit(RETURN) inside the body know | |
| 188 | // they're inside this state machine and can reach the | |
| 189 | // frame / labels they need to emit. Cleared after the | |
| 190 | // body walk so anything else emitted at this nesting | |
| 191 | // level (other functions, nested classes' visits) | |
| 192 | // goes through the standard paths. | |
| 193 | let prev_async_sm = _current_async_state_machine | |
| 194 | let prev_success = _current_async_success_label | |
| 195 | let prev_end = _current_async_end_label | |
| 196 | _current_async_state_machine = async_state_machine | |
| 197 | _current_async_success_label = success_label | |
| 198 | _current_async_end_label = end_label | |
| 199 | ||
| 200 | // Unpack any destructured formal arguments into their leaf | |
| 201 | // frame fields before the body reads them. The plain path's | |
| 202 | // AST walk does this from visit(Variables.VARIABLE); this | |
| 203 | // path drives its own walk and never reaches those nodes. | |
| 204 | _gen_destructured_formal_prologue(function) | |
| 205 | ||
| 206 | // Walk the body. visit(AWAIT) appends AWAIT_SUSPEND IR | |
| 207 | // Values into move_next_block; other statements emit | |
| 208 | // normally. | |
| 209 | if function.body? then | |
| 210 | function.body.walk(self) | |
| 211 | fi | |
| 212 | ||
| 213 | // What the body produces goes into the frame's result before | |
| 214 | // leaving to success, exactly as `visit(RETURN)` puts it there. | |
| 215 | _gen_async_body_result(function.body, frame) | |
| 216 | ||
| 217 | // Body fell off the end: leave success_label so the | |
| 218 | // trailer fires. | |
| 219 | add(Values.BRANCH_TO(ILOpCode.LEAVE, success_label)) | |
| 220 | ||
| 221 | _current_async_state_machine = prev_async_sm | |
| 222 | _current_async_success_label = prev_success | |
| 223 | _current_async_end_label = prev_end | |
| 224 | ||
| 225 | // Populate the outer dispatch from the holder that | |
| 226 | // accumulated awaits not enclosed in any inner `.try`. | |
| 227 | _pop_async_dispatch_holder(outer_dispatch_holder) | |
| 228 | ||
| 229 | leave_block() | |
| 230 | leave_scope(function) | |
| 231 | ||
| 232 | async_state_machine.uninstall_body_emission_overrides() | |
| 233 | ||
| 234 | _pending_async_state_machines.add( | |
| 235 | PENDING_ASYNC_STATE_MACHINE( | |
| 236 | async_state_machine, | |
| 237 | move_next_block, | |
| 238 | success_label, | |
| 239 | end_label | |
| 240 | ) | |
| 241 | ) | |
| 242 | ||
| 243 | return true | |
| 244 | si | |
| 245 | ||
| 246 | // The state-local IL spelling of the innermost open | |
| 247 | // dispatch holder — `'.async_state'` for async state | |
| 248 | // machines, `'.gen_state'` for generators, null when no | |
| 249 | // state machine is open. Used by `.try` emission and the | |
| 250 | // finally-body state guard so the same code paths emit the | |
| 251 | // right local name without each caller having to know. | |
| 252 | _current_state_local_il() -> string? is | |
| 253 | if _async_dispatch_stack.count == 0 then | |
| 254 | return null | |
| 255 | fi | |
| 256 | return _async_dispatch_stack[_async_dispatch_stack.count - 1].state_local_il | |
| 257 | si | |
| 258 | ||
| 259 | // Declare a CLR `int32` local in the MoveNext frame, copy | |
| 260 | // the state field into it, and push the outer dispatch | |
| 261 | // holder bound to that local. Returns the pushed holder so | |
| 262 | // the caller can pop it at the end of the body walk via | |
| 263 | // `_pop_async_dispatch_holder`. Used by every state-machine | |
| 264 | // entry point (named function, lambda closure, generator). | |
| 265 | // `state_local_il` is the local's IL spelling — | |
| 266 | // `'.async_state'` for async, `'.gen_state'` for | |
| 267 | // generators — and travels with the holder so inner | |
| 268 | // dispatch sites pick it up. | |
| 269 | _open_state_machine_entry( | |
| 270 | frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE, | |
| 271 | state_field: Semantic.Symbols.Field, | |
| 272 | state_local_il: string | |
| 273 | ) -> ASYNC_DISPATCH_HOLDER is | |
| 274 | add(Values.DECLARE_LOCAL(state_local_il, _innate_symbol_lookup.get_int_type())) | |
| 275 | add(_build_frame_field_load(frame, state_field)) | |
| 276 | add(Values.STORE_TEMP(state_local_il, _innate_symbol_lookup.get_int_type())) | |
| 277 | ||
| 278 | let block = Values.BLOCK() | |
| 279 | let top_label = IR.LABEL() | |
| 280 | block.add(Values.MARK_LABEL(top_label)) | |
| 281 | add(block) | |
| 282 | let holder = ASYNC_DISPATCH_HOLDER(block, state_local_il, top_label, true) | |
| 283 | _async_dispatch_stack.add(holder) | |
| 284 | return holder | |
| 285 | si | |
| 286 | ||
| 287 | // Push an inner dispatch holder at the top of a `.try` body | |
| 288 | // if we're inside a state-machine method. Returns the | |
| 289 | // holder (to pop later) or null when no state machine is | |
| 290 | // open — the caller can pass that null back to | |
| 291 | // `_maybe_pop_dispatch_holder` for symmetric cleanup | |
| 292 | // without an `if` at every call site. | |
| 293 | _maybe_push_dispatch_holder() -> ASYNC_DISPATCH_HOLDER? is | |
| 294 | if !_current_state_local_il()? then | |
| 295 | return null | |
| 296 | fi | |
| 297 | return _push_async_dispatch_holder() | |
| 298 | si | |
| 299 | ||
| 300 | _maybe_pop_dispatch_holder(holder: ASYNC_DISPATCH_HOLDER?) is | |
| 301 | if holder? then | |
| 302 | _pop_async_dispatch_holder(holder) | |
| 303 | fi | |
| 304 | si | |
| 305 | ||
| 306 | // The state-machine frame for the current function — async | |
| 307 | // or generator, whichever applies. Used by the BLOCK-with- | |
| 308 | // suspend spill in `LIST.visit` / `visit(IF)` / `visit(CASE)` | |
| 309 | // (in expression position) to allocate spill fields that | |
| 310 | // survive across MoveNext re-entries. | |
| 311 | _current_state_machine_frame() -> Semantic.Symbols.STATE_MACHINE_FRAME_BASE? is | |
| 312 | let casm = _current_async_state_machine | |
| 313 | ||
| 314 | if casm? /\ casm.frame? then | |
| 315 | return casm.frame | |
| 316 | fi | |
| 317 | ||
| 318 | let cf = current_function | |
| 319 | if cf? then | |
| 320 | let sm = Semantic.Symbols.state_machine_for(cf) | |
| 321 | if sm? /\ sm.frame? then | |
| 322 | return sm.frame | |
| 323 | fi | |
| 324 | fi | |
| 325 | ||
| 326 | return null | |
| 327 | si | |
| 328 | ||
| 329 | // Build the IR Value that loads from `field` on the current | |
| 330 | // state-machine frame — used as the spilled BLOCK's stand-in | |
| 331 | // value. The consumer that does `add(block)` reads this | |
| 332 | // load when the BLOCK gens, in place of the original body | |
| 333 | // IL. | |
| 334 | _build_frame_field_load(frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE, spill_field: Semantic.Symbols.Field) -> IR.Values.Value => | |
| 335 | IR.Values.Load.INSTANCE_FIELD( | |
| 336 | IR.Values.Load.REFERENCE_SELF(frame, frame.type), | |
| 337 | spill_field | |
| 338 | ) | |
| 339 | ||
| 340 | // The counterpart store: `value` is emitted between the frame | |
| 341 | // reference and the `stfld`, so anything that leaves one item | |
| 342 | // on the stack can stand in for it. | |
| 343 | _build_frame_field_store( | |
| 344 | frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE, | |
| 345 | `field: Semantic.Symbols.Field, | |
| 346 | value: IR.Values.Value | |
| 347 | ) -> IR.Values.Value => | |
| 348 | IR.Values.Store.INSTANCE_FIELD( | |
| 349 | IR.Values.Load.REFERENCE_SELF(frame, frame.type), | |
| 350 | `field, | |
| 351 | value | |
| 352 | ) | |
| 353 | ||
| 354 | // BLOCK-with-suspend spill path for `Statements.LIST` in | |
| 355 | // expression position. The capture-mode LIST.visit wraps | |
| 356 | // its body in lots of state-machine plumbing (dispatch | |
| 357 | // holders, exception-handler temps, `.try` for `want | |
| 358 | // _dispose`) that the spill path doesn't need — the body | |
| 359 | // emits inline in outer current_block, the result lands | |
| 360 | // in a frame field, and the BLOCK ends up holding just a | |
| 361 | // load. Returns true if the spill path ran (caller skips | |
| 362 | // the ordinary capture path); false otherwise. | |
| 363 | _try_spill_block_with_suspend(list: Statements.LIST) -> bool is | |
| 364 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 365 | self, | |
| 366 | list, | |
| 367 | list.value, | |
| 368 | _current_state_machine_frame() | |
| 369 | ) | |
| 370 | ||
| 371 | if !spiller.is_spilling then | |
| 372 | return false | |
| 373 | fi | |
| 374 | ||
| 375 | // Walk body in outer current_block. Statement IL — | |
| 376 | // including visit(AWAIT) / visit(YIELD) suspend | |
| 377 | // emissions and nested composites' own spills — lands | |
| 378 | // in outer, in the same flat stream as the SM | |
| 379 | // dispatcher. | |
| 380 | for s in list.statements do | |
| 381 | enter_node(s) | |
| 382 | try | |
| 383 | s.walk(self) | |
| 384 | finally | |
| 385 | leave_node(s) | |
| 386 | yrt | |
| 387 | od | |
| 388 | ||
| 389 | // Tail-evaluate-and-spill. By now the tail's value is a | |
| 390 | // clean load (any tail-internal suspends already | |
| 391 | // hoisted by visit(AWAIT) / visit(YIELD), and any | |
| 392 | // tail-internal composites already spilled themselves), | |
| 393 | // so the ldarg.0 / value-IL / stfld sequence has no | |
| 394 | // suspend between receiver push and stfld. | |
| 395 | if let list.last?, last.value? then | |
| 396 | spiller.emit_value(value) | |
| 397 | fi | |
| 398 | ||
| 399 | spiller.leave() | |
| 400 | ||
| 401 | return true | |
| 402 | si | |
| 403 | ||
| 404 | ||
| 405 | // Record a (state, resume_label) pair with the innermost | |
| 406 | // open dispatch holder, so its dispatch block (at the top | |
| 407 | // of its `.try` body OR at MoveNext outer scope) routes | |
| 408 | // cold-resume entries to {resume_label}. No-op outside a | |
| 409 | // state machine. | |
| 410 | _register_resume_with_dispatch(state: int, resume_label: IR.LABEL) is | |
| 411 | if _async_dispatch_stack.count == 0 then | |
| 412 | return | |
| 413 | fi | |
| 414 | let holder = _async_dispatch_stack[_async_dispatch_stack.count - 1] | |
| 415 | holder.register(state, resume_label) | |
| 416 | si | |
| 417 | ||
| 418 | // Emit the `if V_state >= 0 → skip` guard at the top of a | |
| 419 | // finally body in a state-machine method. The `leave` that | |
| 420 | // ends a suspend (AwaitUnsafeOnCompleted or yield) fires the | |
| 421 | // surrounding finally with state >= 0; the user's code must | |
| 422 | // not run then. Real exits (state == -1) and exceptional | |
| 423 | // unwinds run the finally normally. | |
| 424 | // | |
| 425 | // Returns the skip label so the caller can plant it at the | |
| 426 | // end of the guarded code via `_close_finally_state_guard`. | |
| 427 | // Returns null when not inside a state machine — caller | |
| 428 | // emits the finally body unguarded. | |
| 429 | _open_finally_state_guard() -> LABEL? is | |
| 430 | let state_local_il = _current_state_local_il() | |
| 431 | ||
| 432 | if !state_local_il? then | |
| 433 | return null | |
| 434 | fi | |
| 435 | ||
| 436 | let skip = LABEL() | |
| 437 | ||
| 438 | let int_type = _innate_symbol_lookup.get_int_type() | |
| 439 | ||
| 440 | get_brancher_for_block().branch( | |
| 441 | BRANCH.GE, | |
| 442 | Values.Load.TEMP(state_local_il, int_type), | |
| 443 | Values.Literal.NUMBER(0, int_type), | |
| 444 | skip) | |
| 445 | ||
| 446 | return skip | |
| 447 | si | |
| 448 | ||
| 449 | _close_finally_state_guard(skip: LABEL?) is | |
| 450 | if skip? then | |
| 451 | get_brancher_for_block().label(skip) | |
| 452 | fi | |
| 453 | si | |
| 454 | ||
| 455 | // Push a new dispatch holder onto the stack and emit its | |
| 456 | // placeholder block at the current emission point. Returns | |
| 457 | // the holder so the caller can pop it once the protected | |
| 458 | // region's body has been walked. Use when opening a `.try` | |
| 459 | // body inside an async function or generator so the | |
| 460 | // AWAIT_SUSPENDs / YIELDs that fire in the body register | |
| 461 | // here and the dispatch ends up inside the same region as | |
| 462 | // the targets it reaches. The new holder inherits its | |
| 463 | // state-local IL spelling from the top of the stack — the | |
| 464 | // outer holder (pushed at MoveNext entry) chose the name | |
| 465 | // (`'.async_state'` for async, `'.gen_state'` for | |
| 466 | // generators) so inner holders just match. | |
| 467 | _push_async_dispatch_holder() -> ASYNC_DISPATCH_HOLDER is | |
| 468 | assert _async_dispatch_stack.count > 0 else "no outer dispatch holder to inherit state-local name from" | |
| 469 | let outer = _async_dispatch_stack[_async_dispatch_stack.count - 1] | |
| 470 | let block = Values.BLOCK() | |
| 471 | let top_label = IR.LABEL() | |
| 472 | block.add(Values.MARK_LABEL(top_label)) | |
| 473 | add(block) | |
| 474 | let holder = ASYNC_DISPATCH_HOLDER(block, outer.state_local_il, top_label, false) | |
| 475 | _async_dispatch_stack.add(holder) | |
| 476 | return holder | |
| 477 | si | |
| 478 | ||
| 479 | // Populate the holder's dispatch block from registered | |
| 480 | // (state, cold_resume_label) entries and pop. No-op if no | |
| 481 | // entries — keeps emitted IL minimal when a `.try` body | |
| 482 | // contains no awaits. The popped region's states are handed | |
| 483 | // to the enclosing holder against `top_label` (this region's | |
| 484 | // first instruction), so a resuming MoveNext is routed from | |
| 485 | // the MoveNext-entry dispatch directly into the innermost | |
| 486 | // region holding the resume point, entering each region at | |
| 487 | // its first instruction and skipping the statements between | |
| 488 | // them — re-running those would re-execute hoisted-variable | |
| 489 | // initializers and loop iterators that the frame fields | |
| 490 | // already carry mid-suspend values for. | |
| 491 | _pop_async_dispatch_holder(holder: ASYNC_DISPATCH_HOLDER) is | |
| 492 | _populate_async_dispatch(holder) | |
| 493 | assert _async_dispatch_stack.count > 0 else "dispatch holder stack underflow" | |
| 494 | _async_dispatch_stack.remove_at(_async_dispatch_stack.count - 1) | |
| 495 | ||
| 496 | // A state-machine entry holder (one per MoveNext body) is | |
| 497 | // never propagated: the holder beneath it on the stack | |
| 498 | // belongs to an ENCLOSING state machine — a different | |
| 499 | // method body — and a branch there could not reach this | |
| 500 | // body's labels. Try-to-try propagation stays within one | |
| 501 | // body by construction. | |
| 502 | if !holder.is_state_machine_entry /\ _async_dispatch_stack.count > 0 then | |
| 503 | let parent = _async_dispatch_stack[_async_dispatch_stack.count - 1] | |
| 504 | for entry in holder.entries do | |
| 505 | parent.register(entry.state, holder.top_label) | |
| 506 | od | |
| 507 | fi | |
| 508 | si | |
| 509 | ||
| 510 | // Fill the holder's placeholder block with one | |
| 511 | // `ldloc V_state; ldc.i4 N; beq cold_resume_N` entry per | |
| 512 | // registered await. The placeholder was inserted into the | |
| 513 | // surrounding emission stream when the holder was pushed — | |
| 514 | // these instructions now sit at the top of the protected | |
| 515 | // region's body, before any user code. | |
| 516 | _populate_async_dispatch(holder: ASYNC_DISPATCH_HOLDER) is | |
| 517 | for entry in holder.entries do | |
| 518 | holder.block.add( | |
| 519 | Values.Load.TEMP(holder.state_local_il, _innate_symbol_lookup.get_int_type())) | |
| 520 | holder.block.add( | |
| 521 | Values.Literal.NUMBER(entry.state, _innate_symbol_lookup.get_int_type())) | |
| 522 | holder.block.add( | |
| 523 | Values.BRANCH_TO(ILOpCode.BEQ, entry.cold_resume_label)) | |
| 524 | od | |
| 525 | si | |
| 526 | ||
| 527 | // Emit all pending state-machine classes accumulated during | |
| 528 | // the walk of an enclosing class or namespace. Called at the | |
| 529 | // tail of visit(`class) / visit(`namespace) so the classes | |
| 530 | // land at namespace scope, parallel to the user's class. | |
| 531 | emit_pending_state_machines() is | |
| 532 | if _pending_state_machines.count > 0 then | |
| 533 | let pending = _pending_state_machines | |
| 534 | _pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE]() | |
| 535 | ||
| 536 | for psm in pending do | |
| 537 | _emit_state_machine_class(psm.state_machine, psm.move_next_block) | |
| 538 | od | |
| 539 | fi | |
| 540 | ||
| 541 | if _pending_async_state_machines.count > 0 then | |
| 542 | let pending = _pending_async_state_machines | |
| 543 | _pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE]() | |
| 544 | ||
| 545 | for pasm in pending do | |
| 546 | _emit_async_state_machine_class( | |
| 547 | pasm.async_state_machine, | |
| 548 | pasm.move_next_block, | |
| 549 | pasm.success_label, | |
| 550 | pasm.end_label | |
| 551 | ) | |
| 552 | od | |
| 553 | fi | |
| 554 | si | |
| 555 | ||
| 556 | // Emits the synthesised IAsyncStateMachine class — parallels | |
| 557 | // `_emit_state_machine_class` for generators. Class members: | |
| 558 | // `$state`, `$builder`, `$result` (value-async), `$outer_self` | |
| 559 | // (instance), `$arg_*`, `$local_*`, `$awaiter_*` fields; ctor; | |
| 560 | // MoveNext (with entry-dispatch and try/catch routing through | |
| 561 | // the builder); trivial SetStateMachine. | |
| 562 | _emit_async_state_machine_class( | |
| 563 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE, | |
| 564 | move_next_block: Values.BLOCK, | |
| 565 | success_label: IR.LABEL, | |
| 566 | end_label: IR.LABEL | |
| 567 | ) is | |
| 568 | let frame = async_state_machine.frame | |
| 569 | ||
| 570 | assert frame? else "async state machine has no frame at IL emission" | |
| 571 | ||
| 572 | // Same settled-type refresh the generator emitter performs. | |
| 573 | frame.refresh_local_field_types() | |
| 574 | ||
| 575 | async_state_machine.install_body_emission_overrides() | |
| 576 | ||
| 577 | try | |
| 578 | _gen_async_state_machine_ctor_srm(frame) | |
| 579 | ||
| 580 | _emit_async_frame_member_body_srm( | |
| 581 | frame, | |
| 582 | IR.Emitter.FrameMember.MOVE_NEXT, | |
| 583 | () is | |
| 584 | _gen_async_move_next_srm( | |
| 585 | frame, move_next_block, success_label, end_label) | |
| 586 | si) | |
| 587 | ||
| 588 | _emit_async_frame_member_body_srm( | |
| 589 | frame, | |
| 590 | IR.Emitter.FrameMember.SET_STATE_MACHINE, | |
| 591 | () is si) | |
| 592 | finally | |
| 593 | async_state_machine.uninstall_body_emission_overrides() | |
| 594 | yrt | |
| 595 | si | |
| 596 | ||
| 597 | // A synthesised async frame member, encoded into a body of its | |
| 598 | // own and deposited against the key the structure walk reads. | |
| 599 | // `emit` writes the body; SetStateMachine's writes nothing, | |
| 600 | // because the whole of it is the `ret` added here. | |
| 601 | _emit_async_frame_member_body_srm( | |
| 602 | frame: Semantic.Symbols.ASYNC_STATE_MACHINE_FRAME, | |
| 603 | member: IR.Emitter.FrameMember, | |
| 604 | emit: () -> void | |
| 605 | ) is | |
| 606 | let assembly_emitter = _context.srm_assembly_emitter | |
| 607 | let enclosing = _context.current_srm_body_emitter | |
| 608 | let body = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 609 | ||
| 610 | _context.current_srm_body_emitter = body | |
| 611 | ||
| 612 | try | |
| 613 | emit() | |
| 614 | ||
| 615 | body.ret() | |
| 616 | ||
| 617 | assembly_emitter.handles.set_frame_member_body( | |
| 618 | frame, member, body.flush(assembly_emitter)) | |
| 619 | finally | |
| 620 | _context.current_srm_body_emitter = enclosing | |
| 621 | yrt | |
| 622 | si | |
| 623 | ||
| 624 | // MoveNext, wrapped in the try/catch that routes an escaping | |
| 625 | // exception through the builder. The success path falls out of | |
| 626 | // the region to `success_label`, completes the task, and both | |
| 627 | // paths meet at `end_label`. | |
| 628 | _gen_async_move_next_srm( | |
| 629 | frame: Semantic.Symbols.ASYNC_STATE_MACHINE_FRAME, | |
| 630 | move_next_block: Values.BLOCK, | |
| 631 | success_label: IR.LABEL, | |
| 632 | end_label: IR.LABEL | |
| 633 | ) is | |
| 634 | let body = _context.current_srm_body_emitter! | |
| 635 | ||
| 636 | let builder_field = frame.builder_field! | |
| 637 | let builder_type = builder_field.type! | |
| 638 | let exception_type = _innate_symbol_lookup.get_exception_type() | |
| 639 | ||
| 640 | let state_field = _context.resolve_field_target(frame.state_field) | |
| 641 | let builder_reference = _context.resolve_field_target(builder_field) | |
| 642 | ||
| 643 | let try_start = body.mark_region_boundary() | |
| 644 | ||
| 645 | move_next_block.gen(_context) | |
| 646 | ||
| 647 | let try_end = body.mark_region_boundary() | |
| 648 | let handler_start = try_end | |
| 649 | ||
| 650 | body.declare_local(_EXCEPTION_LOCAL, exception_type) | |
| 651 | body.stloc(_EXCEPTION_LOCAL) | |
| 652 | body.ldarg(0) | |
| 653 | body.ldc_i4(-1) | |
| 654 | body.stfld(state_field) | |
| 655 | body.ldarg(0) | |
| 656 | IR.Values.ASYNC_METHOD_LAUNCH.load_builder(body, builder_type, builder_reference) | |
| 657 | body.ldloc(_EXCEPTION_LOCAL) | |
| 658 | body.call( | |
| 659 | _context.resolve_async_builder_set_exception(builder_type)) | |
| 660 | body.branch(System.Reflection.Metadata.ILOpCode.LEAVE, end_label) | |
| 661 | ||
| 662 | let handler_end = body.mark_region_boundary() | |
| 663 | ||
| 664 | body.add_catch_region( | |
| 665 | try_start, | |
| 666 | try_end, | |
| 667 | handler_start, | |
| 668 | handler_end, | |
| 669 | _context.resolve_type_token(exception_type)) | |
| 670 | ||
| 671 | body.mark_label(success_label) | |
| 672 | body.ldarg(0) | |
| 673 | body.ldc_i4(-1) | |
| 674 | body.stfld(state_field) | |
| 675 | body.ldarg(0) | |
| 676 | IR.Values.ASYNC_METHOD_LAUNCH.load_builder(body, builder_type, builder_reference) | |
| 677 | ||
| 678 | let result_field = frame.result_field | |
| 679 | ||
| 680 | if result_field? then | |
| 681 | body.ldarg(0) | |
| 682 | body.ldfld(_context.resolve_field_target(result_field)) | |
| 683 | fi | |
| 684 | ||
| 685 | body.call(_context.resolve_async_builder_set_result(builder_type, result_field?)) | |
| 686 | ||
| 687 | body.mark_label(end_label) | |
| 688 | si | |
| 689 | ||
| 690 | _EXCEPTION_LOCAL: string static => ".exception" | |
| 691 | ||
| 692 | // The frame's constructor: chain to Object, then copy each | |
| 693 | // constructor argument into the field the lowering declared for | |
| 694 | // it. The builder is not among them — the outer method creates | |
| 695 | // it and stores it after the newobj. | |
| 696 | _gen_async_state_machine_ctor_srm(frame: Semantic.Symbols.ASYNC_STATE_MACHINE_FRAME) is | |
| 697 | let assembly_emitter = _context.srm_assembly_emitter | |
| 698 | let enclosing = _context.current_srm_body_emitter | |
| 699 | let body = IR.Emitter.SRM_METHOD_BODY_EMITTER() | |
| 700 | ||
| 701 | _context.current_srm_body_emitter = body | |
| 702 | ||
| 703 | try | |
| 704 | body.ldarg(0) | |
| 705 | body.call(_context.resolve_object_constructor()) | |
| 706 | ||
| 707 | let slot mut = 1 | |
| 708 | ||
| 709 | if let outer_self_field = frame.outer_self_field then | |
| 710 | body.ldarg(0) | |
| 711 | body.ldarg(slot) | |
| 712 | body.stfld(_context.resolve_field_target(outer_self_field)) | |
| 713 | ||
| 714 | slot = slot + 1 | |
| 715 | fi | |
| 716 | ||
| 717 | for arg_field in frame.argument_fields do | |
| 718 | body.ldarg(0) | |
| 719 | body.ldarg(slot) | |
| 720 | body.stfld(_context.resolve_field_target(arg_field)) | |
| 721 | ||
| 722 | slot = slot + 1 | |
| 723 | od | |
| 724 | ||
| 725 | body.ret() | |
| 726 | ||
| 727 | assembly_emitter.handles.set_body_offset( | |
| 728 | frame.constructor, body.flush(assembly_emitter)) | |
| 729 | assembly_emitter.handles.set_il_outputs( | |
| 730 | frame.constructor, body.il_outputs) | |
| 731 | finally | |
| 732 | _context.current_srm_body_emitter = enclosing | |
| 733 | yrt | |
| 734 | si | |
| 735 | ||
| 736 | si | |
| 737 | si |