Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use IO.Std; | |
| 3 | ||
| 4 | use System.Text.StringBuilder; | |
| 5 | ||
| 6 | use Logging; | |
| 7 | use Trees; | |
| 8 | use Source; | |
| 9 | ||
| 10 | use IR; | |
| 11 | use IR.Values; | |
| 12 | ||
| 13 | use Ghul.Pipes; | |
| 14 | ||
| 15 | // Holds a generator function whose state-machine class hasn't | |
| 16 | // been emitted yet — its MoveNext block was filled in while | |
| 17 | // walking the function's body, but the wrapping `.class` lives | |
| 18 | // outside the enclosing user class in IL. Flushed by | |
| 19 | // `emit_pending_state_machines` at the tail of the enclosing | |
| 20 | // class / namespace visit. | |
| 21 | class PENDING_STATE_MACHINE is | |
| 22 | state_machine: Semantic.Symbols.STATE_MACHINE; | |
| 23 | move_next_block: IR.Values.BLOCK; | |
| 24 | ||
| 25 | init(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: IR.Values.BLOCK) is | |
| 26 | self.state_machine = state_machine; | |
| 27 | self.move_next_block = move_next_block; | |
| 28 | si | |
| 29 | si | |
| 30 | ||
| 31 | // Async sibling of PENDING_STATE_MACHINE. Flushed by | |
| 32 | // `emit_pending_state_machines` alongside the generator list. | |
| 33 | class PENDING_ASYNC_STATE_MACHINE is | |
| 34 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE; | |
| 35 | move_next_block: IR.Values.BLOCK; | |
| 36 | success_label: IR.LABEL; | |
| 37 | end_label: IR.LABEL; | |
| 38 | ||
| 39 | init( | |
| 40 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE, | |
| 41 | move_next_block: IR.Values.BLOCK, | |
| 42 | success_label: IR.LABEL, | |
| 43 | end_label: IR.LABEL | |
| 44 | ) is | |
| 45 | self.async_state_machine = async_state_machine; | |
| 46 | self.move_next_block = move_next_block; | |
| 47 | self.success_label = success_label; | |
| 48 | self.end_label = end_label; | |
| 49 | si | |
| 50 | si | |
| 51 | ||
| 52 | // One entry in a dispatch placeholder — the pair an | |
| 53 | // AWAIT_SUSPEND contributes when it registers with its | |
| 54 | // enclosing protected region. `state` is the suspension state | |
| 55 | // number; `cold_resume_label` is the IR.LABEL inside the same | |
| 56 | // region's `.try` body that GET_RESULT routes through. | |
| 57 | class ASYNC_DISPATCH_ENTRY is | |
| 58 | state: int; | |
| 59 | cold_resume_label: IR.LABEL; | |
| 60 | ||
| 61 | init(state: int, cold_resume_label: IR.LABEL) is | |
| 62 | self.state = state; | |
| 63 | self.cold_resume_label = cold_resume_label; | |
| 64 | si | |
| 65 | si | |
| 66 | ||
| 67 | // IL-emission frame for a `val ... lav` block currently in | |
| 68 | // scope. Returns inside the body whose val_block_target is this | |
| 69 | // block push their value and `br` to `end_label`; the natural | |
| 70 | // fall-through emits the tail value and falls into the same | |
| 71 | // label. All paths converge at end_label with exactly one value | |
| 72 | // on the evaluation stack (or empty for a void block). No value | |
| 73 | // ever lives in a CLR local or frame field at a suspension | |
| 74 | // point, so async-await and generator-yield compose without | |
| 75 | // bespoke handling. | |
| 76 | // | |
| 77 | // result_type is the resolved block value type from compile- | |
| 78 | // expressions; void means no value-push. is_void is cached to | |
| 79 | // skip the lookup at every return emission. enclosing_try_count | |
| 80 | // is the number of `.try` entries on the loop-label stack at | |
| 81 | // val-block entry — a return whose current try count exceeds | |
| 82 | // this opened a try INSIDE the val-block body and needs `leave` | |
| 83 | // instead of `br` to exit the protected region. value_block is | |
| 84 | // the captured IR BLOCK used in capture mode; pre(VAL_BLOCK) | |
| 85 | // eagerly allocates the cross-try TEMP on it before walking | |
| 86 | // the body, so the `.locals init` lands at the head of value_ | |
| 87 | // block's IL stream rather than inside an inner `.try {}`. | |
| 88 | class VAL_BLOCK_IL is | |
| 89 | block: Trees.Expressions.VAL_BLOCK; | |
| 90 | end_label: IR.LABEL; | |
| 91 | result_type: Semantic.Types.Type?; | |
| 92 | is_void: bool; | |
| 93 | enclosing_try_count: int; | |
| 94 | value_block: IR.Values.BLOCK?; | |
| 95 | ||
| 96 | // Non-null when generate-il is spilling this val-block to a | |
| 97 | // frame field (body contains a state-machine suspend). Val- | |
| 98 | // targeted returns then `stfld $spill_field` instead of | |
| 99 | // leaving the value on the stack, so all paths converge at | |
| 100 | // end_label with the value in the field and an empty stack. | |
| 101 | spill_field: Semantic.Symbols.Field? public; | |
| 102 | ||
| 103 | // Capture-mode cross-try state. The TEMP and label must be | |
| 104 | // allocated BEFORE the body walk so the `.locals init` | |
| 105 | // appears in value_block's IL stream OUTSIDE any inner | |
| 106 | // `.try` the body opens — ilasm scopes locals declared | |
| 107 | // inside `.try {}` to that block, so a load at the post- | |
| 108 | // body join site can't find them. VAL_BLOCK.pre pre- | |
| 109 | // allocates both eagerly when value_block has a non-void | |
| 110 | // result type and is not spilling. | |
| 111 | // | |
| 112 | // A val-targeted `return E` inside an inner `.try` can't | |
| 113 | // `br end_label` (invalid IL across a protected region) | |
| 114 | // and can't carry its value across `leave` (empty-stack | |
| 115 | // precondition). Mirrors the function-return-from-try | |
| 116 | // pattern: stash the value into `cross_try_temp`, `leave | |
| 117 | // cross_try_join_label`, and at the join `ldloc` the temp | |
| 118 | // back onto the stack so it falls through into end_label. | |
| 119 | // `cross_try_used` flags whether the join block needs to | |
| 120 | // be emitted at all — otherwise the unconditional `br | |
| 121 | // end_label; join: ldloc; end_label` would push | |
| 122 | // uninitialised data onto the stack on the natural path's | |
| 123 | // divergent variants. | |
| 124 | cross_try_temp: IR.TEMP? public; | |
| 125 | cross_try_join_label: IR.LABEL? public; | |
| 126 | cross_try_used: bool public; | |
| 127 | ||
| 128 | init( | |
| 129 | block: Trees.Expressions.VAL_BLOCK, | |
| 130 | end_label: IR.LABEL, | |
| 131 | result_type: Semantic.Types.Type?, | |
| 132 | is_void: bool, | |
| 133 | enclosing_try_count: int, | |
| 134 | value_block: IR.Values.BLOCK? | |
| 135 | ) is | |
| 136 | self.block = block; | |
| 137 | self.end_label = end_label; | |
| 138 | self.result_type = result_type; | |
| 139 | self.is_void = is_void; | |
| 140 | self.enclosing_try_count = enclosing_try_count; | |
| 141 | self.value_block = value_block; | |
| 142 | si | |
| 143 | si | |
| 144 | ||
| 145 | // Helper for the four composites whose AST value sits in an | |
| 146 | // `IR.Values.BLOCK` and whose body may suspend (`val ... lav`, | |
| 147 | // a `Statements.LIST` in expression position, `if` / `case` in | |
| 148 | // expression position). The natural emission captures body IL | |
| 149 | // inside the value BLOCK; a consumer later replays it. When the | |
| 150 | // captured IL contains a state-machine suspend, the replay traps | |
| 151 | // the suspend's `leave` / state register inside the consumer's | |
| 152 | // stack setup — invalid IL plus state-loss across MoveNext | |
| 153 | // re-entry. | |
| 154 | // | |
| 155 | // Spill mode breaks the capture: the body emits inline in outer | |
| 156 | // current_block alongside the dispatcher, each value-producing | |
| 157 | // path lands in an anonymous frame field via `ldarg.0; v; stfld`, | |
| 158 | // and the value BLOCK is rewritten to hold just a `ldfld` of the | |
| 159 | // field. Consumers see a clean load. | |
| 160 | // | |
| 161 | // One spiller per composite. `init` decides spill-vs-capture | |
| 162 | // from the value's BLOCK type, the SM frame, and whether body | |
| 163 | // contains a suspend outside nested function literals. `enter` | |
| 164 | // opens the BLOCK for direct emission when capturing; `emit | |
| 165 | // _value` emits each tail/branch value (spill or forward); | |
| 166 | // `leave` either rewrites the BLOCK's contents to a field load | |
| 167 | // (spill) or closes it with `leave_block` (capture). VAL_BLOCK | |
| 168 | // also reads `spill_field` to make val-targeted returns store | |
| 169 | // into the same field. | |
| 170 | class COMPOSITE_VALUE_SPILLER is | |
| 171 | _gen: GENERATE_IL; | |
| 172 | _value_block: IR.Values.BLOCK?; | |
| 173 | _frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE?; | |
| 174 | _spill_field: Semantic.Symbols.Field?; | |
| 175 | ||
| 176 | init( | |
| 177 | gen: GENERATE_IL, | |
| 178 | value: IR.Values.Value?, | |
| 179 | frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE?, | |
| 180 | contains_suspend: bool | |
| 181 | ) is | |
| 182 | _gen = gen; | |
| 183 | ||
| 184 | if !value? \/ !isa IR.Values.BLOCK(value) then | |
| 185 | return; | |
| 186 | fi | |
| 187 | ||
| 188 | let value_block = cast IR.Values.BLOCK(value); | |
| 189 | _value_block = value_block; | |
| 190 | ||
| 191 | if !frame? \/ !contains_suspend then | |
| 192 | return; | |
| 193 | fi | |
| 194 | ||
| 195 | // Void-typed value BLOCK: declare_anonymous_field on a | |
| 196 | // void type produces invalid IL, and the ldarg.0/v/stfld | |
| 197 | // triple would itself be bogus. Stay in capture mode — | |
| 198 | // the void value carries no data across the join. | |
| 199 | let value_type = value_block.type; | |
| 200 | ||
| 201 | if value_type.is_void then | |
| 202 | return; | |
| 203 | fi | |
| 204 | ||
| 205 | _frame = frame; | |
| 206 | _spill_field = frame.declare_anonymous_field("spill", value_type); | |
| 207 | si | |
| 208 | ||
| 209 | is_spilling: bool => _spill_field?; | |
| 210 | ||
| 211 | spill_field: Semantic.Symbols.Field? => _spill_field; | |
| 212 | ||
| 213 | enter() is | |
| 214 | if !_value_block? \/ _spill_field? then | |
| 215 | return; | |
| 216 | fi | |
| 217 | _gen.enter_block(_value_block); | |
| 218 | si | |
| 219 | ||
| 220 | emit_value(value: IR.Values.Value) is | |
| 221 | let spill = _spill_field; | |
| 222 | ||
| 223 | if spill? then | |
| 224 | _gen.add(IR.RAW("ldarg.0")); | |
| 225 | _gen.add(value); | |
| 226 | _gen.add("stfld {spill.get_il_reference()}"); | |
| 227 | else | |
| 228 | _gen.add(value); | |
| 229 | fi | |
| 230 | si | |
| 231 | ||
| 232 | leave() is | |
| 233 | if _spill_field? then | |
| 234 | _value_block!.add(_gen._build_frame_field_load(_frame!, _spill_field)); | |
| 235 | elif _value_block? then | |
| 236 | _gen.leave_block(); | |
| 237 | fi | |
| 238 | si | |
| 239 | si | |
| 240 | ||
| 241 | // Placeholder block sitting at the top of one protected region's | |
| 242 | // body (MoveNext's outer try, or any inner let-use / user `.try`). | |
| 243 | // While the region's body walks, AWAIT_SUSPENDs / YIELDs that | |
| 244 | // fire inside it append their state-label pair via `register`. | |
| 245 | // Region close populates `block` with `ldloc V_state; ldc.i4 N; | |
| 246 | // beq cold_N` for each entry — keeping the dispatch branches | |
| 247 | // inside the same `.try` as the targets they reach. | |
| 248 | // | |
| 249 | // `state_local_il` is the IL spelling of the CLR local cached at | |
| 250 | // MoveNext entry — `'.async_state'` for async state machines, | |
| 251 | // `'.gen_state'` for generators. Both nest under the same | |
| 252 | // dispatch-holder machinery; only the local name differs. | |
| 253 | class ASYNC_DISPATCH_HOLDER is | |
| 254 | block: IR.Values.BLOCK; | |
| 255 | state_local_il: string; | |
| 256 | _entries: Collections.LIST[ASYNC_DISPATCH_ENTRY]; | |
| 257 | ||
| 258 | init(block: IR.Values.BLOCK, state_local_il: string) is | |
| 259 | self.block = block; | |
| 260 | self.state_local_il = state_local_il; | |
| 261 | _entries = Collections.LIST[ASYNC_DISPATCH_ENTRY](); | |
| 262 | si | |
| 263 | ||
| 264 | register(state: int, cold_resume_label: IR.LABEL) is | |
| 265 | _entries.add(ASYNC_DISPATCH_ENTRY(state, cold_resume_label)); | |
| 266 | si | |
| 267 | ||
| 268 | entries: Collections.Iterable[ASYNC_DISPATCH_ENTRY] => _entries; | |
| 269 | si | |
| 270 | ||
| 271 | class GENERATE_IL: ScopedVisitor is | |
| 272 | _logger: Logger; | |
| 273 | _symbol_table: Semantic.SYMBOL_TABLE; | |
| 274 | _int_type: Semantic.Types.NAMED; | |
| 275 | _symbol_loader: Semantic.SYMBOL_LOADER; | |
| 276 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; | |
| 277 | _function_caller: Semantic.FUNCTION_CALLER; | |
| 278 | _type_caster: Semantic.TYPE_CASTER; | |
| 279 | _overload_resolver: Semantic.OVERLOAD_RESOLVER; | |
| 280 | _symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS; | |
| 281 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS; | |
| 282 | ||
| 283 | _context: CONTEXT; | |
| 284 | _brancher: BRANCHER; | |
| 285 | _boilerplate_generator: BOILERPLATE_GENERATOR; | |
| 286 | _boxer: VALUE_BOXER; | |
| 287 | _loops: LOOP_LABEL_STACK; | |
| 288 | _block_context: BlockContext; | |
| 289 | ||
| 290 | _il_output_depth: int; | |
| 291 | _in_catch_variable: bool; | |
| 292 | ||
| 293 | _indent: int; | |
| 294 | _depth: int; | |
| 295 | _run_on: bool; | |
| 296 | _indent_needed: bool; | |
| 297 | ||
| 298 | _interpolation_handler: Semantic.Types.Type?; | |
| 299 | _constructor: Semantic.Symbols.Function; | |
| 300 | _append_literal: Semantic.Symbols.Function; | |
| 301 | _append_formatted_generic: Semantic.Symbols.Function; | |
| 302 | _append_formatted_generic_alignment: Semantic.Symbols.Function; | |
| 303 | _append_formatted_generic_format: Semantic.Symbols.Function; | |
| 304 | _append_formatted_generic_alignment_format: Semantic.Symbols.Function; | |
| 305 | _to_string_and_clear: Semantic.Symbols.Function; | |
| 306 | _dispose: Semantic.Symbols.Function; | |
| 307 | ||
| 308 | // Generators whose state-machine class IL emission has been | |
| 309 | // deferred until the enclosing class/namespace closes. The | |
| 310 | // outer function method is emitted inline; the state-machine | |
| 311 | // class needs to land as a SIBLING of the user's class, not | |
| 312 | // inside it (CIL has no nested-class-in-method form). Mirrors | |
| 313 | // the existing closure-FRAME emission pattern (see | |
| 314 | // gen_closures called from visit(`class)). | |
| 315 | _pending_state_machines: Collections.LIST[PENDING_STATE_MACHINE]; | |
| 316 | _pending_async_state_machines: Collections.LIST[PENDING_ASYNC_STATE_MACHINE]; | |
| 317 | ||
| 318 | // Per-async-function emission context, set/cleared by | |
| 319 | // `_pre_async_function`. Visit handlers for AWAIT / RETURN | |
| 320 | // check `current_function == _current_async_state_machine | |
| 321 | // .function` so a nested closure's own visit doesn't fire | |
| 322 | // the outer function's async paths. | |
| 323 | _current_async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE?; | |
| 324 | _current_async_success_label: IR.LABEL?; | |
| 325 | _current_async_end_label: IR.LABEL?; | |
| 326 | ||
| 327 | // Leave target for yields inside a `.try` body. CIL forbids | |
| 328 | // `ret` from inside a protected region; yields that suspend | |
| 329 | // from within an enclosing `.try` `leave` to this label, which | |
| 330 | // sits outside any try and just emits `ldc.i4.1; ret`. | |
| 331 | // Lazy: yield's emission sets this on first use; the generator | |
| 332 | // trailer emits the label + ret only if it was actually used. | |
| 333 | _current_generator_yield_return_label: IR.LABEL?; | |
| 334 | ||
| 335 | // Stack of dispatch placeholders for the currently-open | |
| 336 | // protected regions (outermost is the MoveNext body itself). | |
| 337 | // Pushed when entering a `.try` body, popped on close. Each | |
| 338 | // AWAIT_SUSPEND that fires while a holder is active registers | |
| 339 | // its (state, cold_resume_label) pair with the top holder. | |
| 340 | // At close-of-region the holder's BLOCK is populated with | |
| 341 | // `ldloc V_state; ldc.i4 N; beq cold_resume_N` for each | |
| 342 | // registered await — so the `beq` and the target label live | |
| 343 | // in the same `.try`, never crossing a region boundary. | |
| 344 | _async_dispatch_stack: Collections.LIST[ASYNC_DISPATCH_HOLDER]; | |
| 345 | ||
| 346 | // Stack of `val ... lav` IL frames, parallel to the | |
| 347 | // val_block_target stamped on RETURN nodes by compile- | |
| 348 | // expressions. Each entry holds the block's end_label, the | |
| 349 | // resolved result_type, the open-`.try` count cached at | |
| 350 | // val-block entry, and (in capture mode) the eagerly- | |
| 351 | // allocated cross-try TEMP and join label. visit(RETURN) | |
| 352 | // reads the top via the node's val_block_target identity | |
| 353 | // and dispatches to one of three shapes: spill mode stfld's | |
| 354 | // into the frame field then leave/br end_label; capture | |
| 355 | // mode with no inner try pushes the value and `br`s | |
| 356 | // end_label; capture mode crossing an inner try stashes the | |
| 357 | // value into the TEMP and `leave`s the join label, where | |
| 358 | // a post-body `ldloc` joins it back onto end_label's stack. | |
| 359 | _val_block_il_stack: Collections.LIST[VAL_BLOCK_IL]; | |
| 360 | ||
| 361 | current_block: Values.BLOCK => _block_context.current_block; | |
| 362 | ||
| 363 | init( | |
| 364 | logger: Logger, | |
| 365 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 366 | namespaces: Semantic.NAMESPACES, | |
| 367 | symbol_loader: Semantic.SYMBOL_LOADER, | |
| 368 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 369 | function_caller: Semantic.FUNCTION_CALLER, | |
| 370 | type_caster: Semantic.TYPE_CASTER, | |
| 371 | overload_resolver: Semantic.OVERLOAD_RESOLVER, | |
| 372 | symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, | |
| 373 | context: CONTEXT, | |
| 374 | block_context: BlockContext, | |
| 375 | brancher: BRANCHER, | |
| 376 | boilerplate_generator: BOILERPLATE_GENERATOR, | |
| 377 | boxer: VALUE_BOXER, | |
| 378 | build_flags: Compiler.GLOBAL_BUILD_FLAGS | |
| 379 | ) | |
| 380 | is | |
| 381 | super.init(logger, symbol_table, namespaces); | |
| 382 | ||
| 383 | _globals_carriers_marked = Collections.SET[string](); | |
| 384 | ||
| 385 | _logger = logger; | |
| 386 | _symbol_table = symbol_table; | |
| 387 | _symbol_loader = symbol_loader; | |
| 388 | _innate_symbol_lookup = innate_symbol_lookup; | |
| 389 | _function_caller = function_caller; | |
| 390 | _type_caster = type_caster; | |
| 391 | _overload_resolver = overload_resolver; | |
| 392 | _symbol_use_locations = symbol_use_locations; | |
| 393 | _context = context; | |
| 394 | _block_context = block_context; | |
| 395 | _brancher = brancher; | |
| 396 | _boilerplate_generator = boilerplate_generator; | |
| 397 | _boxer = boxer; | |
| 398 | _build_flags = build_flags; | |
| 399 | ||
| 400 | _loops = LOOP_LABEL_STACK(); | |
| 401 | _pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE](); | |
| 402 | _pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE](); | |
| 403 | _async_dispatch_stack = Collections.LIST[ASYNC_DISPATCH_HOLDER](); | |
| 404 | _val_block_il_stack = Collections.LIST[VAL_BLOCK_IL](); | |
| 405 | si | |
| 406 | ||
| 407 | ensure_runtime_symbols_are_materialized() is | |
| 408 | if _interpolation_handler? then | |
| 409 | return; | |
| 410 | fi | |
| 411 | ||
| 412 | let string_type = _innate_symbol_lookup.get_string_type(); | |
| 413 | let int_type = _innate_symbol_lookup.get_int_type(); | |
| 414 | ||
| 415 | ||
| 416 | // Materialize IDisposable.Dispose() | |
| 417 | let idisposable_type = _innate_symbol_lookup.get_idisposable_type(); | |
| 418 | ||
| 419 | // there is only one overload of dispose: | |
| 420 | _dispose = cast Semantic.Symbols.FUNCTION_GROUP?(idisposable_type.find_member("dispose"))!.functions[0]; | |
| 421 | ||
| 422 | // Materialize the various methods of the string interpolation handler: | |
| 423 | let interpolation_handler = _innate_symbol_lookup.get_interpolated_string_handler_type(); | |
| 424 | _interpolation_handler = interpolation_handler; | |
| 425 | ||
| 426 | // the constructor overload we want is the only one with 2 arguments: | |
| 427 | _constructor = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("init"))!.functions |> | |
| 428 | filter(f => f.arguments.count == 2) |> | |
| 429 | only(); | |
| 430 | ||
| 431 | // there is only one overload of append_literal: | |
| 432 | _append_literal = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_literal"))!.functions[0]; | |
| 433 | ||
| 434 | // the first overload of append_formatted we want is the only one that is both generic and with 1 argument: | |
| 435 | _append_formatted_generic = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 436 | filter(f => f.is_generic /\ f.arguments.count == 1) |> | |
| 437 | only(); | |
| 438 | ||
| 439 | // there are two overloads of append_formatted with 2 arguments, we want the one where the second argument is an int: | |
| 440 | _append_formatted_generic_alignment = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 441 | filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(int_type) == Semantic.Types.MATCH.SAME) |> | |
| 442 | only(); | |
| 443 | ||
| 444 | // there are two overloads of append_formatted with 2 arguments, we want the one where the second argument is a string: | |
| 445 | // (BCL declares the `format` parameter as `string?`, so SAME no longer matches under directional compare; | |
| 446 | // != DIFFERENT accepts both `string` and `string?` shapes.) | |
| 447 | _append_formatted_generic_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 448 | filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(string_type) != Semantic.Types.MATCH.DIFFERENT) |> | |
| 449 | only(); | |
| 450 | ||
| 451 | // there is only one overload of append_formatted with 3 arguments: | |
| 452 | _append_formatted_generic_alignment_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 453 | filter(f => f.is_generic /\ f.arguments.count == 3) |> | |
| 454 | only(); | |
| 455 | ||
| 456 | // there is only one overload of to_string_and_clear: | |
| 457 | _to_string_and_clear = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("to_string_and_clear"))!.functions[0]; | |
| 458 | si | |
| 459 | ||
| 460 | apply(root: Trees.Node) is | |
| 461 | if _logger.any_errors then | |
| 462 | return; | |
| 463 | fi | |
| 464 | ||
| 465 | LABEL.set_pass("_"); | |
| 466 | ||
| 467 | root.walk(self); | |
| 468 | si | |
| 469 | ||
| 470 | // Mirrors compile_expressions.ghul's enter_node/leave_node. | |
| 471 | // pre(Statements.LIST) takes over the statement walk, so the | |
| 472 | // location bracketing has to be re-applied by hand around | |
| 473 | // each statement. | |
| 474 | enter_node(node: Trees.Node) is | |
| 475 | if !_build_flags.want_debug then | |
| 476 | return; | |
| 477 | fi | |
| 478 | IoC.CONTAINER.instance.location_stack.push(node.location); | |
| 479 | si | |
| 480 | ||
| 481 | leave_node(node: Trees.Node) is | |
| 482 | if !_build_flags.want_debug then | |
| 483 | return; | |
| 484 | fi | |
| 485 | IoC.CONTAINER.instance.location_stack.pop(); | |
| 486 | si | |
| 487 | ||
| 488 | enter_block(block: Values.BLOCK) is | |
| 489 | _block_context.enter_block(block); | |
| 490 | si | |
| 491 | ||
| 492 | enter_block() is | |
| 493 | _block_context.enter_block(); | |
| 494 | si | |
| 495 | ||
| 496 | leave_block() is | |
| 497 | _block_context.leave_block(); | |
| 498 | si | |
| 499 | ||
| 500 | get_brancher_for_block() -> BLOCK_BRANCHER => | |
| 501 | _brancher.get_for(_block_context.current_block); | |
| 502 | ||
| 503 | add(value: Values.Value) is | |
| 504 | current_block.add(value); | |
| 505 | si | |
| 506 | ||
| 507 | add(raw: string) is | |
| 508 | current_block.add(raw); | |
| 509 | si | |
| 510 | ||
| 511 | add(type: Semantic.Types.Type, raw: string) is | |
| 512 | current_block.add(type, raw); | |
| 513 | si | |
| 514 | ||
| 515 | println(value: object) is | |
| 516 | _context.write_line(value); | |
| 517 | si | |
| 518 | ||
| 519 | println(value: object, comment: object) is | |
| 520 | _context.write_line(value, comment); | |
| 521 | si | |
| 522 | ||
| 523 | println_comment(value: object) is | |
| 524 | _context.write_comment_line(value); | |
| 525 | si | |
| 526 | ||
| 527 | enter_class(symbol: Semantic.Symbols.Classy) is | |
| 528 | ADDRESS.reset_id(); | |
| 529 | TEMP.reset_id(); | |
| 530 | LABEL.reset_id(); | |
| 531 | si | |
| 532 | ||
| 533 | leave_class(symbol: Semantic.Symbols.Classy) is | |
| 534 | si | |
| 535 | ||
| 536 | print_class_def(symbol: Semantic.Symbols.Symbol) is | |
| 537 | println_comment("define {symbol.short_description}"); | |
| 538 | ||
| 539 | let buffer = System.Text.StringBuilder(); | |
| 540 | symbol.gen_definition_header(buffer); | |
| 541 | ||
| 542 | println(buffer); | |
| 543 | ||
| 544 | println("{{"); | |
| 545 | si | |
| 546 | ||
| 547 | // Namespaces whose carrier has already carried the marker attribute in | |
| 548 | // this compilation. ilasm merges the repeated `.class` blocks for one | |
| 549 | // name, so the attribute is emitted on the first block only. | |
| 550 | _globals_carriers_marked: Collections.SET[string]; | |
| 551 | ||
| 552 | // Open/close a `.class 'NS'.'$globals' { ... }` wrapper around a single | |
| 553 | // global function/property/variable definition. Multiple wrappers for | |
| 554 | // the same class get merged by ilasm (partial class definitions). | |
| 555 | gen_globals_class_open(`namespace: Semantic.Symbols.NAMESPACE) is | |
| 556 | let header = System.Text.StringBuilder(); | |
| 557 | header.append(".class public abstract auto ansi sealed "); | |
| 558 | `namespace.gen_globals_class_reference(header, null); | |
| 559 | header.append(" extends [System.Runtime]System.Object"); | |
| 560 | ||
| 561 | println(header); | |
| 562 | println("{{"); | |
| 563 | _context.indent(); | |
| 564 | ||
| 565 | if !_globals_carriers_marked.contains(`namespace.qualified_name) then | |
| 566 | _globals_carriers_marked.add(`namespace.qualified_name); | |
| 567 | ||
| 568 | println(".custom instance void ['ghul-runtime']{Semantic.DotNet.GLOBALS_CARRIER.attribute_name}::.ctor() = ( 01 00 00 00 )"); | |
| 569 | fi | |
| 570 | si | |
| 571 | ||
| 572 | gen_globals_class_close() is | |
| 573 | _context.outdent(); | |
| 574 | println("}}"); | |
| 575 | si | |
| 576 | ||
| 577 | pre(`namespace: Definitions.NAMESPACE) -> bool => super.pre(`namespace); | |
| 578 | ||
| 579 | visit(`namespace: Definitions.NAMESPACE) is | |
| 580 | let context = _symbol_table.current_closure_context; | |
| 581 | ||
| 582 | gen_anon_functions(context.get_closures()); | |
| 583 | gen_closures(context.get_closures()); | |
| 584 | ||
| 585 | emit_pending_state_machines(); | |
| 586 | ||
| 587 | super.visit(`namespace); | |
| 588 | si | |
| 589 | ||
| 590 | pre(`class: Definitions.CLASS) -> bool is | |
| 591 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`class))!; | |
| 592 | ||
| 593 | enter_class(symbol); | |
| 594 | ||
| 595 | let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!; | |
| 596 | ||
| 597 | print_class_def(symbol); | |
| 598 | ||
| 599 | enter_scope(`class); | |
| 600 | ||
| 601 | _context.indent(); | |
| 602 | ||
| 603 | // Closed-to-assembly ghūl classes carry a CLOSED_ATTRIBUTE | |
| 604 | // marker so that consumers in other assemblies can recognise | |
| 605 | // the class as closed during import — the type system uses | |
| 606 | // this to refuse cross-assembly extension and to treat the | |
| 607 | // subclass set as enumerable for narrowing. | |
| 608 | if let class_symbol: Semantic.Symbols.CLASS = symbol then | |
| 609 | if !class_symbol.is_open then | |
| 610 | println(".custom instance void ['ghul-runtime']Ghul.Internal.CLOSED_ATTRIBUTE::.ctor() = ( 01 00 00 00 )"); | |
| 611 | fi | |
| 612 | fi | |
| 613 | ||
| 614 | gen_custom_attributes(symbol); | |
| 615 | ||
| 616 | return false; | |
| 617 | si | |
| 618 | ||
| 619 | // Every attribute a function definition carries, in the one | |
| 620 | // order ILASM accepts: a `.custom` binds to the target named by | |
| 621 | // the most recent `.param [N]` directive, so each method-level | |
| 622 | // attribute has to be written before any per-slot directive or | |
| 623 | // it silently lands on that parameter instead of on the method. | |
| 624 | gen_function_attributes(symbol: Semantic.Symbols.Symbol) is | |
| 625 | gen_purity_attributes(symbol); | |
| 626 | gen_custom_attributes(symbol); | |
| 627 | ||
| 628 | gen_tuple_element_name_attributes(symbol); | |
| 629 | gen_nullability_attributes(symbol); | |
| 630 | gen_purity_param_attributes(symbol); | |
| 631 | ||
| 632 | if let function: Semantic.Symbols.Function = symbol then | |
| 633 | function.gen_argument_custom_attributes(_context); | |
| 634 | fi | |
| 635 | si | |
| 636 | ||
| 637 | // Emit a `.custom` directive for each attribute pragma resolved | |
| 638 | // onto this symbol. The caller has already opened the type / method | |
| 639 | // and indented, so the directive lands inside it. | |
| 640 | gen_custom_attributes(symbol: Semantic.Symbols.Symbol) is | |
| 641 | if !symbol.custom_attributes? then | |
| 642 | return; | |
| 643 | fi | |
| 644 | ||
| 645 | for attribute in symbol.custom_attributes do | |
| 646 | println(attribute.gen_il_line()); | |
| 647 | od | |
| 648 | si | |
| 649 | ||
| 650 | visit(`class: Definitions.CLASS) is | |
| 651 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`class))!; | |
| 652 | ||
| 653 | let closures = symbol.get_closures(); | |
| 654 | ||
| 655 | gen_delegates(closures); | |
| 656 | ||
| 657 | gen_ienumerable_boilerplate(symbol); | |
| 658 | gen_ienumerator_boilerplate(symbol); | |
| 659 | ||
| 660 | _context.outdent(); | |
| 661 | println("}}"); | |
| 662 | ||
| 663 | leave_class(symbol); | |
| 664 | ||
| 665 | leave_scope(`class); | |
| 666 | ||
| 667 | gen_anon_functions(closures); | |
| 668 | gen_closures(closures); | |
| 669 | ||
| 670 | emit_pending_state_machines(); | |
| 671 | si | |
| 672 | ||
| 673 | // A `partial` block emits its members inside a fresh `.class Target { … }` | |
| 674 | // wrapper; ilasm merges same-named class blocks into one type. The class | |
| 675 | // header (extends/implements/attributes) is the primary definition's job, | |
| 676 | // so this only re-opens the type for its members; closures declared here | |
| 677 | // are emitted by visit(partial), after their bodies are walked. | |
| 678 | pre(`partial: Definitions.PARTIAL) -> bool is | |
| 679 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`partial))!; | |
| 680 | ||
| 681 | enter_class(symbol); | |
| 682 | ||
| 683 | print_class_def(symbol); | |
| 684 | ||
| 685 | enter_scope(`partial); | |
| 686 | ||
| 687 | _context.indent(); | |
| 688 | ||
| 689 | return false; | |
| 690 | si | |
| 691 | ||
| 692 | visit(`partial: Definitions.PARTIAL) is | |
| 693 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`partial))!; | |
| 694 | ||
| 695 | // Closures declared in this block are tracked on its injection | |
| 696 | // scope and emitted here, after the member walk has generated | |
| 697 | // their bodies - the target's primary definition runs before | |
| 698 | // this block is walked and would emit them bodyless. | |
| 699 | let injection = cast Semantic.INJECTION_SCOPE?(scope_for(`partial)); | |
| 700 | let closures = injection?.get_closures(); | |
| 701 | ||
| 702 | gen_delegates(closures); | |
| 703 | ||
| 704 | _context.outdent(); | |
| 705 | println("}}"); | |
| 706 | ||
| 707 | leave_class(symbol); | |
| 708 | ||
| 709 | leave_scope(`partial); | |
| 710 | ||
| 711 | gen_anon_functions(closures); | |
| 712 | gen_closures(closures); | |
| 713 | ||
| 714 | emit_pending_state_machines(); | |
| 715 | si | |
| 716 | ||
| 717 | // An `impl` block emits its members inside a fresh `.class Target { … }` | |
| 718 | // wrapper, exactly like `partial`; ilasm merges same-named class blocks. | |
| 719 | // The interface the target now implements is carried on the target | |
| 720 | // symbol, so its `implements` clause is emitted by the primary definition. | |
| 721 | pre(`impl: Definitions.IMPL) -> bool is | |
| 722 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`impl))!; | |
| 723 | ||
| 724 | enter_class(symbol); | |
| 725 | ||
| 726 | print_class_def(symbol); | |
| 727 | ||
| 728 | enter_scope(`impl); | |
| 729 | ||
| 730 | _context.indent(); | |
| 731 | ||
| 732 | return false; | |
| 733 | si | |
| 734 | ||
| 735 | visit(`impl: Definitions.IMPL) is | |
| 736 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`impl))!; | |
| 737 | ||
| 738 | // As visit(partial): closures declared in this block are tracked | |
| 739 | // on its injection scope and emitted here, after the member walk | |
| 740 | // has generated their bodies. | |
| 741 | let injection = cast Semantic.INJECTION_SCOPE?(scope_for(`impl)); | |
| 742 | let closures = injection?.get_closures(); | |
| 743 | ||
| 744 | gen_delegates(closures); | |
| 745 | ||
| 746 | _context.outdent(); | |
| 747 | println("}}"); | |
| 748 | ||
| 749 | leave_class(symbol); | |
| 750 | ||
| 751 | leave_scope(`impl); | |
| 752 | ||
| 753 | gen_anon_functions(closures); | |
| 754 | gen_closures(closures); | |
| 755 | ||
| 756 | emit_pending_state_machines(); | |
| 757 | si | |
| 758 | ||
| 759 | pre(`trait: Definitions.TRAIT) -> bool is | |
| 760 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`trait))!; | |
| 761 | let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!; | |
| 762 | ||
| 763 | enter_class(symbol); | |
| 764 | ||
| 765 | print_class_def(symbol); | |
| 766 | ||
| 767 | enter_scope(`trait); | |
| 768 | ||
| 769 | _context.indent(); | |
| 770 | ||
| 771 | gen_custom_attributes(symbol); | |
| 772 | ||
| 773 | return false; | |
| 774 | si | |
| 775 | ||
| 776 | visit(`trait: Definitions.TRAIT) is | |
| 777 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`trait))!; | |
| 778 | ||
| 779 | let closures = symbol.get_closures(); | |
| 780 | ||
| 781 | gen_delegates(closures); | |
| 782 | ||
| 783 | _context.outdent(); | |
| 784 | println("}}"); | |
| 785 | ||
| 786 | leave_class(symbol); | |
| 787 | ||
| 788 | leave_scope(`trait); | |
| 789 | ||
| 790 | gen_anon_functions(closures); | |
| 791 | gen_closures(closures); | |
| 792 | ||
| 793 | emit_pending_state_machines(); | |
| 794 | si | |
| 795 | ||
| 796 | pre(`struct: Definitions.STRUCT) -> bool is | |
| 797 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`struct))!; | |
| 798 | let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!; | |
| 799 | ||
| 800 | enter_class(symbol); | |
| 801 | ||
| 802 | print_class_def(symbol); | |
| 803 | ||
| 804 | _context.indent(); | |
| 805 | ||
| 806 | enter_scope(`struct); | |
| 807 | ||
| 808 | gen_custom_attributes(symbol); | |
| 809 | ||
| 810 | return false; | |
| 811 | si | |
| 812 | ||
| 813 | visit(`struct: Definitions.STRUCT) is | |
| 814 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`struct))!; | |
| 815 | ||
| 816 | let closures = symbol.get_closures(); | |
| 817 | ||
| 818 | gen_ienumerable_boilerplate(symbol); | |
| 819 | gen_ienumerator_boilerplate(symbol); | |
| 820 | gen_anon_functions(closures); | |
| 821 | ||
| 822 | _context.outdent(); | |
| 823 | println("}}"); | |
| 824 | ||
| 825 | leave_class(symbol); | |
| 826 | ||
| 827 | leave_scope(`struct); | |
| 828 | ||
| 829 | gen_closures(closures); | |
| 830 | si | |
| 831 | ||
| 832 | pre(`union: Definitions.UNION) -> bool is | |
| 833 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(`union))!; | |
| 834 | ||
| 835 | enter_class(symbol); | |
| 836 | ||
| 837 | let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!; | |
| 838 | ||
| 839 | print_class_def(symbol); | |
| 840 | ||
| 841 | enter_scope(`union); | |
| 842 | _context.indent(); | |
| 843 | ||
| 844 | println(".custom instance void ['ghul-runtime']Ghul.Internal.UNION_ATTRIBUTE::.ctor() = ( 01 00 00 00 )"); | |
| 845 | ||
| 846 | gen_custom_attributes(symbol); | |
| 847 | ||
| 848 | `union.name.walk(self); | |
| 849 | ||
| 850 | if `union.arguments? then | |
| 851 | `union.arguments.walk(self); | |
| 852 | fi | |
| 853 | ||
| 854 | `union.modifiers.walk(self); | |
| 855 | ||
| 856 | for member in `union.body |> filter(m => !isa Definitions.VARIANT(m)) do | |
| 857 | member.walk(self); | |
| 858 | od | |
| 859 | ||
| 860 | _context.indent(); | |
| 861 | ||
| 862 | gen_ienumerable_boilerplate(symbol); | |
| 863 | gen_ienumerator_boilerplate(symbol); | |
| 864 | ||
| 865 | _context.outdent(); | |
| 866 | println("}}"); | |
| 867 | ||
| 868 | println("// variants for {`union.name}"); | |
| 869 | ||
| 870 | _context.indent(); | |
| 871 | ||
| 872 | leave_class(symbol); | |
| 873 | ||
| 874 | leave_scope(`union); | |
| 875 | ||
| 876 | for member in `union.body |> filter(m => isa Definitions.VARIANT(m)) do | |
| 877 | member.walk(self); | |
| 878 | od | |
| 879 | ||
| 880 | _context.outdent(); | |
| 881 | ||
| 882 | return true; | |
| 883 | si | |
| 884 | ||
| 885 | visit(`union: Definitions.UNION) is | |
| 886 | // all done in pre | |
| 887 | si | |
| 888 | ||
| 889 | pre(variant: Definitions.VARIANT) -> bool is | |
| 890 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(variant))!; | |
| 891 | ||
| 892 | enter_class(symbol); | |
| 893 | ||
| 894 | let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!; | |
| 895 | ||
| 896 | print_class_def(symbol); | |
| 897 | ||
| 898 | enter_scope(variant); | |
| 899 | ||
| 900 | _context.indent(); | |
| 901 | ||
| 902 | println(".custom instance void ['ghul-runtime']Ghul.Internal.VARIANT_ATTRIBUTE::.ctor() = ( 01 00 00 00 )"); | |
| 903 | ||
| 904 | // Mark the union's default variant so cross-assembly | |
| 905 | // consumers can lower `?`/`!` to the same isa/cast as | |
| 906 | // source-defined unions. `SYMBOL_FACTORY.materialize_variant` | |
| 907 | // reads this back on import and sets `default_variant` | |
| 908 | // on the parent union symbol. Uses the union's | |
| 909 | // `default_variant` pointer (set in declare_symbols), | |
| 910 | // so implicit defaults — the sole-non-unit-variant | |
| 911 | // fallback — get marked too. | |
| 912 | if let variant_symbol: Semantic.Symbols.VARIANT = symbol then | |
| 913 | if let owner_union: Semantic.Symbols.UNION = variant_symbol.owner then | |
| 914 | if owner_union.default_variant == variant_symbol then | |
| 915 | println(".custom instance void ['ghul-runtime']Ghul.Internal.DEFAULT_VARIANT_ATTRIBUTE::.ctor() = ( 01 00 00 00 )"); | |
| 916 | fi | |
| 917 | fi | |
| 918 | fi | |
| 919 | ||
| 920 | gen_custom_attributes(symbol); | |
| 921 | ||
| 922 | return false; | |
| 923 | si | |
| 924 | ||
| 925 | visit(variant: Definitions.VARIANT) is | |
| 926 | let symbol = cast Semantic.Symbols.Classy?(symbol_for(variant))!; | |
| 927 | ||
| 928 | let closures = symbol.get_closures(); | |
| 929 | ||
| 930 | gen_ienumerable_boilerplate(symbol); | |
| 931 | gen_ienumerator_boilerplate(symbol); | |
| 932 | gen_unit_variant_singleton(symbol); | |
| 933 | gen_anon_functions(closures); | |
| 934 | ||
| 935 | _context.outdent(); | |
| 936 | println("}}"); | |
| 937 | ||
| 938 | leave_class(symbol); | |
| 939 | ||
| 940 | leave_scope(variant); | |
| 941 | ||
| 942 | gen_closures(closures); | |
| 943 | si | |
| 944 | ||
| 945 | // For a unit variant (no constructor parameters / no | |
| 946 | // instance fields), emit a static `_instance` field plus a | |
| 947 | // `.cctor` that allocates exactly one instance. `IR.Values.NEW` | |
| 948 | // redirects construction sites for unit variants to a `ldsfld` | |
| 949 | // of this field, so `RED()` (or `NONE[int]()`) returns the | |
| 950 | // cached singleton instead of allocating each call. | |
| 951 | gen_unit_variant_singleton(symbol: Semantic.Symbols.Classy) is | |
| 952 | if let variant_symbol: Semantic.Symbols.VARIANT = symbol then | |
| 953 | if !variant_symbol.is_unit_variant then | |
| 954 | return; | |
| 955 | fi | |
| 956 | ||
| 957 | let ref_buffer = StringBuilder(); | |
| 958 | variant_symbol.gen_reference(ref_buffer); | |
| 959 | let variant_ref = ref_buffer.to_string(); | |
| 960 | ||
| 961 | println(".field public static {variant_ref}'_instance'"); | |
| 962 | println(".method private hidebysig specialname rtspecialname static default void '.cctor'() cil managed"); | |
| 963 | println("{{"); | |
| 964 | _context.indent(); | |
| 965 | println(".maxstack 1"); | |
| 966 | println("newobj instance void {variant_ref}::'.ctor'()"); | |
| 967 | println("stsfld {variant_ref}{variant_ref}::'_instance'"); | |
| 968 | println("ret"); | |
| 969 | _context.outdent(); | |
| 970 | println("}}"); | |
| 971 | fi | |
| 972 | si | |
| 973 | ||
| 974 | gen_ienumerable_boilerplate(symbol: Semantic.Symbols.Classy) is | |
| 975 | if symbol.is_derived_from_iterable_trait then | |
| 976 | // FIXME: check if class implements these methods and don't emit them if they're not needed: | |
| 977 | ||
| 978 | _boilerplate_generator.gen("ienumerable-boilerplate"); | |
| 979 | fi | |
| 980 | si | |
| 981 | ||
| 982 | gen_ienumerator_boilerplate(symbol: Semantic.Symbols.Classy) is | |
| 983 | if symbol.is_derived_from_iterator_trait then | |
| 984 | // FIXME: check if class implements these methods and don't emit them if they're not needed: | |
| 985 | ||
| 986 | _boilerplate_generator.gen("ienumerator-boilerplate"); | |
| 987 | fi | |
| 988 | si | |
| 989 | ||
| 990 | gen_delegates(closures: Collections.Iterable[Semantic.Symbols.Closure]?) is | |
| 991 | if !closures? then | |
| 992 | return; | |
| 993 | fi | |
| 994 | ||
| 995 | for anon_function in closures |> filter(c => c.is_delegate) do | |
| 996 | let buffer = StringBuilder(); | |
| 997 | anon_function.gen_definition_header(buffer); | |
| 998 | _context.write_line(buffer); | |
| 999 | ||
| 1000 | _context.write_line("{{"); | |
| 1001 | _context.indent(); | |
| 1002 | ||
| 1003 | anon_function.gen_argument_custom_attributes(_context); | |
| 1004 | ||
| 1005 | _context.write_line(anon_function.il_body); | |
| 1006 | ||
| 1007 | _context.outdent(); | |
| 1008 | _context.write_line("}}"); | |
| 1009 | ||
| 1010 | gen_delegate_cache_field(anon_function); | |
| 1011 | od | |
| 1012 | si | |
| 1013 | ||
| 1014 | ||
| 1015 | gen_anon_functions(closures: Collections.Iterable[Semantic.Symbols.Closure]?) is | |
| 1016 | if !closures? then | |
| 1017 | return; | |
| 1018 | fi | |
| 1019 | ||
| 1020 | for anon_function in closures |> filter(c => c.is_anon_func) do | |
| 1021 | let buffer = StringBuilder(); | |
| 1022 | anon_function.gen_definition_header(buffer); | |
| 1023 | _context.write_line(buffer); | |
| 1024 | ||
| 1025 | _context.write_line("{{"); | |
| 1026 | _context.indent(); | |
| 1027 | ||
| 1028 | anon_function.gen_argument_custom_attributes(_context); | |
| 1029 | ||
| 1030 | _context.write_line(anon_function.il_body); | |
| 1031 | ||
| 1032 | _context.outdent(); | |
| 1033 | _context.write_line("}}"); | |
| 1034 | ||
| 1035 | gen_delegate_cache_field(anon_function); | |
| 1036 | od | |
| 1037 | si | |
| 1038 | ||
| 1039 | // Emit the static cache field backing a memoized stateless | |
| 1040 | // delegate. A static/global anonymous function caches on the | |
| 1041 | // enclosing namespace's `$globals` class, emitted in its own | |
| 1042 | // `$globals` wrapper (ilasm merges them); a stateless instance- | |
| 1043 | // context delegate caches on its enclosing class, whose block is | |
| 1044 | // already open at the call site, so the field is emitted directly. | |
| 1045 | // Keyed on the load site having actually referenced the field, so | |
| 1046 | // definition and frozen load-site IL can't disagree. | |
| 1047 | gen_delegate_cache_field(closure: Semantic.Symbols.Closure) is | |
| 1048 | if !closure.has_delegate_cache_field then | |
| 1049 | return; | |
| 1050 | fi | |
| 1051 | ||
| 1052 | let `field = closure.delegate_cache_field; | |
| 1053 | ||
| 1054 | let field_definition = StringBuilder(); | |
| 1055 | `field.gen_definition_header(field_definition); | |
| 1056 | ||
| 1057 | if let owner_namespace: Semantic.Symbols.NAMESPACE = `field.owner then | |
| 1058 | gen_globals_class_open(owner_namespace); | |
| 1059 | _context.write_line(field_definition); | |
| 1060 | gen_globals_class_close(); | |
| 1061 | else | |
| 1062 | _context.write_line(field_definition); | |
| 1063 | fi | |
| 1064 | si | |
| 1065 | ||
| 1066 | gen_closures(closures: Collections.Iterable[Semantic.Symbols.Closure]?) is | |
| 1067 | if !closures? then | |
| 1068 | return; | |
| 1069 | fi | |
| 1070 | ||
| 1071 | for closure in closures |> filter(c => !c.is_anon_func /\ !c.is_delegate) do | |
| 1072 | closure.gen_frame(_context, _symbol_loader); | |
| 1073 | od | |
| 1074 | si | |
| 1075 | ||
| 1076 | pre(`enum: Definitions.ENUM) -> bool is | |
| 1077 | let symbol = cast Semantic.Symbols.Symbol?(symbol_for(`enum))!; | |
| 1078 | ||
| 1079 | let owner = cast Semantic.Symbols.Symbol?(symbol.owner)!; | |
| 1080 | ||
| 1081 | print_class_def(symbol); | |
| 1082 | ||
| 1083 | enter_scope(`enum); | |
| 1084 | ||
| 1085 | _context.indent(); | |
| 1086 | ||
| 1087 | gen_custom_attributes(symbol); | |
| 1088 | ||
| 1089 | _context.write_line(".field public specialname rtspecialname int32 value__ "); | |
| 1090 | return false; | |
| 1091 | si | |
| 1092 | ||
| 1093 | visit(`enum: Definitions.ENUM) is | |
| 1094 | _context.outdent(); | |
| 1095 | println("}}"); | |
| 1096 | ||
| 1097 | leave_scope(`enum); | |
| 1098 | si | |
| 1099 | ||
| 1100 | pre(enum_member: Definitions.ENUM_MEMBER) -> bool is | |
| 1101 | let symbol = current_scope.find_member(enum_member.name.name); | |
| 1102 | ||
| 1103 | let buffer = System.Text.StringBuilder(); | |
| 1104 | symbol!.gen_definition_header(buffer); | |
| 1105 | println(buffer); | |
| 1106 | return false; | |
| 1107 | si | |
| 1108 | ||
| 1109 | pre(function: Definitions.FUNCTION) -> bool is | |
| 1110 | let symbol = function_for(function)!; | |
| 1111 | ||
| 1112 | // Generator branch: a `*_GENERATOR_*` function expands | |
| 1113 | // into TWO IL methods (the outer stub returning a fresh | |
| 1114 | // state-machine instance + the state machine class with | |
| 1115 | // its MoveNext / get_Current / GetEnumerator / Dispose / | |
| 1116 | // Reset implementations). The split is handled by the | |
| 1117 | // gen-generator-function path so the rest of generate_il | |
| 1118 | // doesn't need to know. | |
| 1119 | let state_machine = Semantic.Symbols.state_machine_for(cast Semantic.Symbols.Function?(symbol)!); | |
| 1120 | ||
| 1121 | if state_machine? then | |
| 1122 | return _pre_generator_function(function, cast Semantic.Symbols.Function?(symbol)!, state_machine); | |
| 1123 | fi | |
| 1124 | ||
| 1125 | // Async branch (parallel to the generator branch above). | |
| 1126 | let async_sm = Semantic.Symbols.async_state_machine_for(cast Semantic.Symbols.Function?(symbol)!); | |
| 1127 | ||
| 1128 | if async_sm? then | |
| 1129 | return _pre_async_function(function, cast Semantic.Symbols.Function?(symbol)!, async_sm); | |
| 1130 | fi | |
| 1131 | ||
| 1132 | if let global_function: Semantic.Symbols.GLOBAL_FUNCTION = symbol then | |
| 1133 | gen_globals_class_open(cast Semantic.Symbols.NAMESPACE?(global_function.owner)!); | |
| 1134 | fi | |
| 1135 | ||
| 1136 | println_comment("define {symbol.description}"); | |
| 1137 | ||
| 1138 | let buffer = System.Text.StringBuilder(); | |
| 1139 | symbol.gen_definition_header(buffer); | |
| 1140 | println(buffer); | |
| 1141 | ||
| 1142 | println("{{"); | |
| 1143 | _context.indent(); | |
| 1144 | ||
| 1145 | gen_function_attributes(symbol); | |
| 1146 | ||
| 1147 | enter_scope(function); | |
| 1148 | ||
| 1149 | symbol.gen_body_header(_context); | |
| 1150 | ||
| 1151 | enter_block(); | |
| 1152 | return false; | |
| 1153 | si | |
| 1154 | ||
| 1155 | // Emit TupleElementNamesAttribute on the return and on any | |
| 1156 | // parameter whose type is a flat named tuple, so a C# or ghūl | |
| 1157 | // consumer recovers the element names — the ValueTuple type | |
| 1158 | // alone carries none. | |
| 1159 | gen_tuple_element_name_attributes(symbol: Semantic.Symbols.Symbol) is | |
| 1160 | let function = cast Semantic.Symbols.Function?(symbol); | |
| 1161 | ||
| 1162 | if !function? then | |
| 1163 | return; | |
| 1164 | fi | |
| 1165 | ||
| 1166 | gen_tuple_element_name_attribute(function.return_type!, 0); | |
| 1167 | ||
| 1168 | let arguments = function.arguments; | |
| 1169 | ||
| 1170 | for i in 0..arguments.count do | |
| 1171 | gen_tuple_element_name_attribute(arguments[i], i + 1); | |
| 1172 | od | |
| 1173 | si | |
| 1174 | ||
| 1175 | gen_tuple_element_name_attribute(type: Semantic.Types.Type, parameter_index: int) is | |
| 1176 | let line = Semantic.DotNet.TUPLE_ELEMENT_NAMES.gen_attribute_line_for_type(type); | |
| 1177 | ||
| 1178 | if !line? then | |
| 1179 | return; | |
| 1180 | fi | |
| 1181 | ||
| 1182 | _context.write_line(".param [{parameter_index}]"); | |
| 1183 | _context.write_line(line); | |
| 1184 | si | |
| 1185 | ||
| 1186 | // Emit `NullableAttribute` on the return slot and on any | |
| 1187 | // parameter whose tree carries a reference-`?` position, so | |
| 1188 | // the annotation survives cross-assembly reflection. The CLR | |
| 1189 | // has no native carrier for reference nullability — `Foo` | |
| 1190 | // and `Foo?` are the same IL type — so the marker rides on a | |
| 1191 | // per-slot custom attribute. ILASM's `.param [N]` attaches | |
| 1192 | // the attribute to parameter `N`; `[0]` is the return. The | |
| 1193 | // attribute's argument is a single byte for a one-position | |
| 1194 | // slot, or a byte[] walking the type tree pre-order for | |
| 1195 | // nested cases (`List[string?]` -> `[1, 2]`). | |
| 1196 | gen_nullability_attributes(symbol: Semantic.Symbols.Symbol) is | |
| 1197 | let function = cast Semantic.Symbols.Function?(symbol); | |
| 1198 | ||
| 1199 | if !function? then | |
| 1200 | return; | |
| 1201 | fi | |
| 1202 | ||
| 1203 | gen_nullability_param_attribute(function.return_type!, 0); | |
| 1204 | ||
| 1205 | let arguments = function.arguments; | |
| 1206 | ||
| 1207 | for i in 0..arguments.count do | |
| 1208 | gen_nullability_param_attribute(arguments[i], i + 1); | |
| 1209 | od | |
| 1210 | si | |
| 1211 | ||
| 1212 | gen_nullability_param_attribute(type: Semantic.Types.Type, parameter_index: int) is | |
| 1213 | let line = Semantic.DotNet.NULLABILITY.gen_attribute_line_for_type(type); | |
| 1214 | ||
| 1215 | if !line? then | |
| 1216 | return; | |
| 1217 | fi | |
| 1218 | ||
| 1219 | _context.write_line(".param [{parameter_index}]"); | |
| 1220 | _context.write_line(" {line}"); | |
| 1221 | si | |
| 1222 | ||
| 1223 | // Purity round-trip: the marker on the method carries the | |
| 1224 | // declared-pure contract to importing assemblies; the marker | |
| 1225 | // on a parameter or return slot marks that slot's top-level | |
| 1226 | // function type pure. Nested pure function types do not | |
| 1227 | // round-trip — a slot whose pureness sits inside a generic | |
| 1228 | // argument imports as the plain shape, which is conservative | |
| 1229 | // on both sides. | |
| 1230 | gen_purity_attributes(symbol: Semantic.Symbols.Symbol) is | |
| 1231 | let function = cast Semantic.Symbols.Function?(symbol); | |
| 1232 | ||
| 1233 | if !function? then | |
| 1234 | return; | |
| 1235 | fi | |
| 1236 | ||
| 1237 | if function.is_declared_pure then | |
| 1238 | _context.write_line(".custom instance void ['ghul-runtime']Ghul.Internal.PURE_ATTRIBUTE::.ctor() = ( 01 00 00 00 )"); | |
| 1239 | fi | |
| 1240 | si | |
| 1241 | ||
| 1242 | gen_purity_param_attributes(symbol: Semantic.Symbols.Symbol) is | |
| 1243 | let function = cast Semantic.Symbols.Function?(symbol); | |
| 1244 | ||
| 1245 | if !function? then | |
| 1246 | return; | |
| 1247 | fi | |
| 1248 | ||
| 1249 | gen_pure_param_attribute(function.return_type, 0); | |
| 1250 | ||
| 1251 | let arguments = function.arguments; | |
| 1252 | ||
| 1253 | for i in 0..arguments.count do | |
| 1254 | gen_pure_param_attribute(arguments[i], i + 1); | |
| 1255 | od | |
| 1256 | si | |
| 1257 | ||
| 1258 | gen_pure_param_attribute(type: Semantic.Types.Type?, parameter_index: int) is | |
| 1259 | if !type? \/ !type.is_pure_function then | |
| 1260 | return; | |
| 1261 | fi | |
| 1262 | ||
| 1263 | _context.write_line(".param [{parameter_index}]"); | |
| 1264 | _context.write_line(".custom instance void ['ghul-runtime']Ghul.Internal.PURE_ATTRIBUTE::.ctor() = ( 01 00 00 00 )"); | |
| 1265 | si | |
| 1266 | ||
| 1267 | visit(function: Definitions.FUNCTION) is | |
| 1268 | let symbol = symbol_for(function); | |
| 1269 | ||
| 1270 | let state_machine = Semantic.Symbols.state_machine_for(cast Semantic.Symbols.Function?(symbol)!); | |
| 1271 | ||
| 1272 | if state_machine? then | |
| 1273 | _visit_generator_function(function, cast Semantic.Symbols.Function?(symbol)!, state_machine); | |
| 1274 | ||
| 1275 | return; | |
| 1276 | fi | |
| 1277 | ||
| 1278 | let async_sm = Semantic.Symbols.async_state_machine_for(cast Semantic.Symbols.Function?(symbol)!); | |
| 1279 | ||
| 1280 | if async_sm? then | |
| 1281 | // _pre_async_function already emitted the outer | |
| 1282 | // method and stashed the move_next block onto | |
| 1283 | // _pending_async_state_machines. Nothing else to | |
| 1284 | // do at AST visit time. | |
| 1285 | return; | |
| 1286 | fi | |
| 1287 | ||
| 1288 | _context.reset_line_tracking(); | |
| 1289 | current_block.gen(_context); | |
| 1290 | leave_block(); | |
| 1291 | ||
| 1292 | if symbol? /\ isa Semantic.Symbols.Function(symbol) then | |
| 1293 | let function_symbol = symbol; | |
| 1294 | ||
| 1295 | if !function_symbol.is_abstract then | |
| 1296 | if function.body? /\ isa Bodies.NULL(function.body) then | |
| 1297 | _context.write_line("newobj instance void class [System.Runtime]System.NotImplementedException::'.ctor'()"); | |
| 1298 | _context.write_line("throw"); | |
| 1299 | elif | |
| 1300 | function_symbol.return_type? /\ | |
| 1301 | function_symbol.return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME | |
| 1302 | then | |
| 1303 | _context.write_line(".locals init ({function_symbol.return_type!.get_il_type()} '.default')"); | |
| 1304 | _context.write_line("ldloc '.default'"); | |
| 1305 | fi | |
| 1306 | ||
| 1307 | _context.write_line("ret"); | |
| 1308 | fi | |
| 1309 | fi | |
| 1310 | ||
| 1311 | leave_scope(function); | |
| 1312 | ||
| 1313 | _context.outdent(); | |
| 1314 | println("}}"); | |
| 1315 | ||
| 1316 | if isa Semantic.Symbols.GLOBAL_FUNCTION(symbol) then | |
| 1317 | gen_globals_class_close(); | |
| 1318 | fi | |
| 1319 | si | |
| 1320 | ||
| 1321 | // Generator IL emission: | |
| 1322 | // | |
| 1323 | // 1. The outer user-facing method gets a stub body | |
| 1324 | // (`newobj $StateMachine.ctor(); ret`) emitted inline, | |
| 1325 | // so the AST visit order is unchanged for the caller. | |
| 1326 | // 2. The function body is walked here (recursively, via | |
| 1327 | // self) into a fresh IR.Values.BLOCK that represents | |
| 1328 | // MoveNext's body. Yield statements emit their state- | |
| 1329 | // machine IL into this block as they are walked. | |
| 1330 | // 3. The state-machine class itself is deferred — captured | |
| 1331 | // in `_pending_state_machines` and emitted at the SAME | |
| 1332 | // level as the user's class (after `visit(`class)` | |
| 1333 | // closes the user class), since CIL doesn't allow | |
| 1334 | // classes nested inside method bodies. | |
| 1335 | _pre_generator_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, state_machine: Semantic.Symbols.STATE_MACHINE) -> bool is | |
| 1336 | let frame = state_machine.frame; | |
| 1337 | ||
| 1338 | if !frame? then | |
| 1339 | _logger.error(function.location, "generator must return Pipe[T]"); | |
| 1340 | ||
| 1341 | return true; | |
| 1342 | fi | |
| 1343 | ||
| 1344 | // Realise the frame's symbol-level declarations (fields | |
| 1345 | // + ctor) once. Required for both the outer method's | |
| 1346 | // newobj reference and the state-machine class itself. | |
| 1347 | frame.declare(); | |
| 1348 | ||
| 1349 | let owner_is_global = isa Semantic.Symbols.GLOBAL_FUNCTION(symbol); | |
| 1350 | ||
| 1351 | if owner_is_global then | |
| 1352 | gen_globals_class_open(cast Semantic.Symbols.NAMESPACE?(symbol.owner)!); | |
| 1353 | fi | |
| 1354 | ||
| 1355 | // --- Outer user-facing method --- | |
| 1356 | println_comment("define {symbol.description}"); | |
| 1357 | ||
| 1358 | let outer_buffer = StringBuilder(); | |
| 1359 | symbol.gen_definition_header(outer_buffer); | |
| 1360 | println(outer_buffer); | |
| 1361 | ||
| 1362 | println("{{"); | |
| 1363 | _context.indent(); | |
| 1364 | ||
| 1365 | gen_function_attributes(symbol); | |
| 1366 | ||
| 1367 | symbol.gen_body_header(_context); | |
| 1368 | ||
| 1369 | _context.write_line(".maxstack 64"); | |
| 1370 | ||
| 1371 | // Instance generators pass `this` as the first ctor | |
| 1372 | // argument; the .ctor stores it into _outer_self so the | |
| 1373 | // body's `self`/instance-member access can read through | |
| 1374 | // the frame. | |
| 1375 | if frame.outer_self_field? then | |
| 1376 | _context.write_line("ldarg.0"); | |
| 1377 | fi | |
| 1378 | ||
| 1379 | // Load each user-declared parameter and pass it through | |
| 1380 | // to the state-machine .ctor. The frame's declare() ran | |
| 1381 | // above so the ctor's signature already matches. | |
| 1382 | for arg_name in symbol.argument_names do | |
| 1383 | _context.write_line("ldarg '{arg_name}'"); | |
| 1384 | od | |
| 1385 | ||
| 1386 | let ctor_reference = _build_specialized_ctor_reference( | |
| 1387 | frame, | |
| 1388 | frame.constructor, | |
| 1389 | state_machine.get_construction_type_arguments(), | |
| 1390 | symbol.location, | |
| 1391 | "while building ctor reference for generator outer method" | |
| 1392 | ); | |
| 1393 | ||
| 1394 | _context.write_line("newobj {ctor_reference}"); | |
| 1395 | _context.write_line("ret"); | |
| 1396 | ||
| 1397 | _context.outdent(); | |
| 1398 | println("}}"); | |
| 1399 | ||
| 1400 | if owner_is_global then | |
| 1401 | gen_globals_class_close(); | |
| 1402 | fi | |
| 1403 | ||
| 1404 | // --- Walk the body into MoveNext's block --- | |
| 1405 | // | |
| 1406 | // The block lands captured on PENDING_STATE_MACHINE | |
| 1407 | // alongside the state machine; `_emit_pending_state_ | |
| 1408 | // machines` writes it out inside the class definition | |
| 1409 | // once the enclosing user class has closed. | |
| 1410 | // | |
| 1411 | // For a generic generator, install gen_type_override on | |
| 1412 | // function-T symbols so any IR.Values inside MoveNext | |
| 1413 | // that baked in function-T (callvirt owner types on the | |
| 1414 | // for-each dispatch, locals init for the iterator | |
| 1415 | // current, etc.) emit `!N` (class-level — MoveNext has | |
| 1416 | // no own type params) rather than `!!N`. | |
| 1417 | state_machine.install_body_emission_overrides(); | |
| 1418 | ||
| 1419 | enter_scope(function); | |
| 1420 | ||
| 1421 | let move_next_block = Values.BLOCK(); | |
| 1422 | ||
| 1423 | enter_block(move_next_block); | |
| 1424 | ||
| 1425 | // `.maxstack` must precede any IL opcodes in the | |
| 1426 | // method body. Add it before the dispatch placeholder | |
| 1427 | // so the populated dispatch IL lands after the | |
| 1428 | // directive at the start of MoveNext. | |
| 1429 | add(".maxstack 64"); | |
| 1430 | ||
| 1431 | // Cache the state field to a CLR local at MoveNext | |
| 1432 | // entry. Every yield keeps it in sync via `dup; stloc; | |
| 1433 | // stfld` so per-region dispatches and state-guarded | |
| 1434 | // finally bodies read a stable value. Same pattern as | |
| 1435 | // the async path — needed to allow yield inside `.try` | |
| 1436 | // without branching into a protected region from | |
| 1437 | // outside (ECMA-335 forbids that, and the previous | |
| 1438 | // centralized-dispatch shape had exactly that bug). | |
| 1439 | let state_field_ref = frame.state_field.get_il_reference(); | |
| 1440 | let outer_dispatch_holder = _open_state_machine_entry(state_field_ref, "'.gen_state'"); | |
| 1441 | ||
| 1442 | // Stash the previous generator-yield-return label so | |
| 1443 | // nested generator functions (rare) don't trip over | |
| 1444 | // each other. visit(YIELD) lazily allocates a label the | |
| 1445 | // first time it needs to `leave` from inside a `.try`. | |
| 1446 | let prev_yield_return = _current_generator_yield_return_label; | |
| 1447 | _current_generator_yield_return_label = null; | |
| 1448 | ||
| 1449 | // Walk the body. Visitors fire pre/visit on every | |
| 1450 | // node; visit(YIELD) is the interesting one — it | |
| 1451 | // appends state-machine IL into current_block | |
| 1452 | // (which IS move_next_block). | |
| 1453 | if function.body? then | |
| 1454 | function.body.walk(self); | |
| 1455 | fi | |
| 1456 | ||
| 1457 | // Fell-off-end trailer: mark done, return false. Plain | |
| 1458 | // `ret` since we're at the MoveNext outer scope (no | |
| 1459 | // open `.try`). | |
| 1460 | add("ldarg.0"); | |
| 1461 | add("ldc.i4.m1"); | |
| 1462 | add("stfld {state_field_ref}"); | |
| 1463 | add("ldc.i4.0"); | |
| 1464 | add("ret"); | |
| 1465 | ||
| 1466 | // If any yield inside an inner `.try` used the | |
| 1467 | // leave-return-true label, emit it now (outside any try) | |
| 1468 | // followed by `ldc.i4.1; ret`. Reached only via `leave | |
| 1469 | // L:` from inside a protected region. | |
| 1470 | if _current_generator_yield_return_label? then | |
| 1471 | add("{_current_generator_yield_return_label}:"); | |
| 1472 | add("ldc.i4.1"); | |
| 1473 | add("ret"); | |
| 1474 | fi | |
| 1475 | ||
| 1476 | _current_generator_yield_return_label = prev_yield_return; | |
| 1477 | ||
| 1478 | _pop_async_dispatch_holder(outer_dispatch_holder); | |
| 1479 | ||
| 1480 | leave_block(); | |
| 1481 | leave_scope(function); | |
| 1482 | ||
| 1483 | // Uninstall — re-installed by _emit_state_machine_class | |
| 1484 | // around move_next_block.gen() so deferred IR.Value.gen() | |
| 1485 | // calls inside MoveNext still see the override. Between | |
| 1486 | // here and there, other class members emit normally. | |
| 1487 | state_machine.uninstall_body_emission_overrides(); | |
| 1488 | ||
| 1489 | _pending_state_machines.add(PENDING_STATE_MACHINE(state_machine, move_next_block)); | |
| 1490 | ||
| 1491 | // Skip the framework's default body walk + the framework's | |
| 1492 | // visit(function) close-out path. Our visit(function) | |
| 1493 | // sees the state-machine and returns early. | |
| 1494 | return true; | |
| 1495 | si | |
| 1496 | ||
| 1497 | _visit_generator_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, state_machine: Semantic.Symbols.STATE_MACHINE) is | |
| 1498 | // _pre_generator_function emitted the outer method and | |
| 1499 | // captured the move_next block onto _pending_state_machines. | |
| 1500 | // Nothing more to do at AST visit time. | |
| 1501 | si | |
| 1502 | ||
| 1503 | // `newobj` operand for the SM frame's constructor. For a | |
| 1504 | // generic frame, constructs a specialized type so the | |
| 1505 | // class-T's appear in the signature; for a non-generic frame, | |
| 1506 | // just the default ctor's IL reference. Exceptions in lookup | |
| 1507 | // are logged and a sentinel returned. | |
| 1508 | _build_specialized_ctor_reference( | |
| 1509 | frame: Semantic.Symbols.Classy, | |
| 1510 | default_ctor: Semantic.Symbols.Method, | |
| 1511 | construction_type_args: Collections.List[Semantic.Types.Type]?, | |
| 1512 | location: Source.LOCATION, | |
| 1513 | error_context: string | |
| 1514 | ) -> string is | |
| 1515 | let ctor_reference: string? mut = null; | |
| 1516 | ||
| 1517 | try | |
| 1518 | if let specialized_frame_type = _specialized_frame_type(frame, construction_type_args, location) then | |
| 1519 | let specialized_ctor_symbol = specialized_frame_type.find_member("init"); | |
| 1520 | ||
| 1521 | if let function: Semantic.Symbols.Function = specialized_ctor_symbol then | |
| 1522 | ctor_reference = function.get_il_reference(); | |
| 1523 | fi | |
| 1524 | fi | |
| 1525 | ||
| 1526 | if !ctor_reference? then | |
| 1527 | ctor_reference = default_ctor.get_il_reference(); | |
| 1528 | fi | |
| 1529 | catch e: System.Exception | |
| 1530 | _logger.exception(location, e, error_context); | |
| 1531 | ctor_reference = "/* error */"; | |
| 1532 | yrt | |
| 1533 | ||
| 1534 | return ctor_reference!; | |
| 1535 | si | |
| 1536 | ||
| 1537 | // The frame type as it must be named in IL: constructed over the | |
| 1538 | // type arguments visible inside the owning function, so a frame | |
| 1539 | // declared in a generic owner is never referred to by its open | |
| 1540 | // form. Null when the frame is not generic. | |
| 1541 | _specialized_frame_type( | |
| 1542 | frame: Semantic.Symbols.Classy, | |
| 1543 | construction_type_args: Collections.List[Semantic.Types.Type]?, | |
| 1544 | location: Source.LOCATION | |
| 1545 | ) -> Semantic.Types.Type? is | |
| 1546 | if !construction_type_args? then | |
| 1547 | return null; | |
| 1548 | fi | |
| 1549 | ||
| 1550 | return Semantic.Types.GENERIC(location, frame, construction_type_args); | |
| 1551 | si | |
| 1552 | ||
| 1553 | // Async-function IL emission. Parallels | |
| 1554 | // `_pre_generator_function`: emits the outer method (newobj | |
| 1555 | // SM, builder.Start, return Task) and stashes a MoveNext | |
| 1556 | // BLOCK to be flushed when the enclosing class closes. | |
| 1557 | // MoveNext wraps the body in a try/catch routing exceptions | |
| 1558 | // through `builder.SetException` and success through | |
| 1559 | // `builder.SetResult`. | |
| 1560 | _pre_async_function(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function, async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE) -> bool is | |
| 1561 | let frame = async_state_machine.frame; | |
| 1562 | ||
| 1563 | if !frame? then | |
| 1564 | _logger.error(function.location, "async function must return Tasks.TASK or Tasks.TASK[T]"); | |
| 1565 | return true; | |
| 1566 | fi | |
| 1567 | ||
| 1568 | frame.declare(); | |
| 1569 | ||
| 1570 | let owner_is_global = isa Semantic.Symbols.GLOBAL_FUNCTION(symbol); | |
| 1571 | ||
| 1572 | if owner_is_global then | |
| 1573 | gen_globals_class_open(cast Semantic.Symbols.NAMESPACE?(symbol.owner)!); | |
| 1574 | fi | |
| 1575 | ||
| 1576 | // --- Outer method --- | |
| 1577 | println_comment("define {symbol.description}"); | |
| 1578 | ||
| 1579 | let outer_buffer = StringBuilder(); | |
| 1580 | symbol.gen_definition_header(outer_buffer); | |
| 1581 | println(outer_buffer); | |
| 1582 | ||
| 1583 | println("{{"); | |
| 1584 | _context.indent(); | |
| 1585 | ||
| 1586 | gen_function_attributes(symbol); | |
| 1587 | ||
| 1588 | symbol.gen_body_header(_context); | |
| 1589 | ||
| 1590 | _context.write_line(".maxstack 64"); | |
| 1591 | ||
| 1592 | let construction_type_args = async_state_machine.get_construction_type_arguments(); | |
| 1593 | ||
| 1594 | let ctor_reference = _build_specialized_ctor_reference( | |
| 1595 | frame, | |
| 1596 | frame.constructor, | |
| 1597 | construction_type_args, | |
| 1598 | symbol.location, | |
| 1599 | "while building ctor reference for async outer method" | |
| 1600 | ); | |
| 1601 | ||
| 1602 | // The local holding the state machine, and the type argument | |
| 1603 | // to builder.Start, both name the frame type. A frame in a | |
| 1604 | // generic owner is generic too, so both need the constructed | |
| 1605 | // form — the open form does not resolve at run time. | |
| 1606 | let state_machine_type = | |
| 1607 | _specialized_frame_type(frame, construction_type_args, symbol.location) ?? frame.type!; | |
| 1608 | ||
| 1609 | // ctor args: instance methods prepend `ldarg.0` for | |
| 1610 | // _outer_self, then each user-declared parameter via | |
| 1611 | // `ldarg '<name>'`. Build them as a sequence Value so | |
| 1612 | // ASYNC_METHOD_LAUNCH composes them in the right order. | |
| 1613 | let ctor_args_block = Values.BLOCK(); | |
| 1614 | if frame.outer_self_field? then | |
| 1615 | ctor_args_block.add(IR.RAW("ldarg.0")); | |
| 1616 | fi | |
| 1617 | for arg_name in symbol.argument_names do | |
| 1618 | ctor_args_block.add(IR.RAW("ldarg '{arg_name}'")); | |
| 1619 | od | |
| 1620 | ctor_args_block.close(); | |
| 1621 | ||
| 1622 | let task_type = symbol.return_type!; | |
| 1623 | ||
| 1624 | // Open-T forms — only meaningful for the generic | |
| 1625 | // (value-async) builder/Task. For void-async, the | |
| 1626 | // builder type is non-generic and Create / get_Task | |
| 1627 | // signatures have no class-T to resolve, so pass null | |
| 1628 | // (ASYNC_METHOD_LAUNCH falls back to the constructed | |
| 1629 | // owner type for the signature). | |
| 1630 | let unspec_builder: Semantic.Types.Type? mut = null; | |
| 1631 | let unspec_task: Semantic.Types.Type? mut = null; | |
| 1632 | if !frame.is_void then | |
| 1633 | unspec_builder = _innate_symbol_lookup.get_unspecialized_async_task_method_builder_type(); | |
| 1634 | unspec_task = _innate_symbol_lookup.get_unspecialized_task_type(); | |
| 1635 | fi | |
| 1636 | ||
| 1637 | let launch = IR.Values.ASYNC_METHOD_LAUNCH( | |
| 1638 | task_type, | |
| 1639 | state_machine_type, | |
| 1640 | ctor_reference, | |
| 1641 | ctor_args_block, | |
| 1642 | frame.state_field, | |
| 1643 | frame.builder_field!, | |
| 1644 | unspec_builder, | |
| 1645 | unspec_task, | |
| 1646 | "'.sm'" | |
| 1647 | ); | |
| 1648 | ||
| 1649 | launch.gen(_context); | |
| 1650 | _context.write_line("ret"); | |
| 1651 | ||
| 1652 | _context.outdent(); | |
| 1653 | println("}}"); | |
| 1654 | ||
| 1655 | if owner_is_global then | |
| 1656 | gen_globals_class_close(); | |
| 1657 | fi | |
| 1658 | ||
| 1659 | // --- Walk the body into MoveNext's block --- | |
| 1660 | async_state_machine.install_body_emission_overrides(); | |
| 1661 | ||
| 1662 | enter_scope(function); | |
| 1663 | ||
| 1664 | let move_next_block = Values.BLOCK(); | |
| 1665 | ||
| 1666 | enter_block(move_next_block); | |
| 1667 | ||
| 1668 | add(".maxstack 64"); | |
| 1669 | ||
| 1670 | // Cache the state field to a CLR local at MoveNext entry. | |
| 1671 | // Every AWAIT_SUSPEND keeps this local in sync via | |
| 1672 | // `dup; stloc; stfld` so the per-region dispatch (below) | |
| 1673 | // and the finally-body state guards read a stable value. | |
| 1674 | // | |
| 1675 | // Finally bodies (let-use auto-dispose and user-written | |
| 1676 | // `finally`) state-guard their work on the same local: | |
| 1677 | // when V_state >= 0 the function is mid-suspend (the | |
| 1678 | // `leave` from the AwaitUnsafeOnCompleted path fires the | |
| 1679 | // finally before exiting), and side effects must be | |
| 1680 | // skipped. | |
| 1681 | let state_field_ref = frame.state_field.get_il_reference(); | |
| 1682 | let outer_dispatch_holder = _open_state_machine_entry(state_field_ref, "'.async_state'"); | |
| 1683 | ||
| 1684 | // Labels used by the trailer: success fall-through and | |
| 1685 | // the end of the wrapping try/catch. The entire MoveNext | |
| 1686 | // body lives inside a `.try` whose handler routes | |
| 1687 | // exceptions through `builder.SetException(e)`. | |
| 1688 | let success_label = IR.LABEL(); | |
| 1689 | let end_label = IR.LABEL(); | |
| 1690 | ||
| 1691 | // Stash the emission context so visit(LET-await), | |
| 1692 | // visit(AWAIT), and visit(RETURN) inside the body know | |
| 1693 | // they're inside this state machine and can reach the | |
| 1694 | // frame / labels they need to emit. Cleared after the | |
| 1695 | // body walk so anything else emitted at this nesting | |
| 1696 | // level (other functions, nested classes' visits) | |
| 1697 | // goes through the standard paths. | |
| 1698 | let prev_async_sm = _current_async_state_machine; | |
| 1699 | let prev_success = _current_async_success_label; | |
| 1700 | let prev_end = _current_async_end_label; | |
| 1701 | _current_async_state_machine = async_state_machine; | |
| 1702 | _current_async_success_label = success_label; | |
| 1703 | _current_async_end_label = end_label; | |
| 1704 | ||
| 1705 | // Walk the body. visit(AWAIT) appends AWAIT_SUSPEND IR | |
| 1706 | // Values into move_next_block; other statements emit | |
| 1707 | // normally. | |
| 1708 | if function.body? then | |
| 1709 | function.body.walk(self); | |
| 1710 | fi | |
| 1711 | ||
| 1712 | // Body fell off the end: leave success_label so the | |
| 1713 | // trailer fires. | |
| 1714 | add("leave {success_label}"); | |
| 1715 | ||
| 1716 | _current_async_state_machine = prev_async_sm; | |
| 1717 | _current_async_success_label = prev_success; | |
| 1718 | _current_async_end_label = prev_end; | |
| 1719 | ||
| 1720 | // Populate the outer dispatch from the holder that | |
| 1721 | // accumulated awaits not enclosed in any inner `.try`. | |
| 1722 | _pop_async_dispatch_holder(outer_dispatch_holder); | |
| 1723 | ||
| 1724 | leave_block(); | |
| 1725 | leave_scope(function); | |
| 1726 | ||
| 1727 | async_state_machine.uninstall_body_emission_overrides(); | |
| 1728 | ||
| 1729 | _pending_async_state_machines.add( | |
| 1730 | PENDING_ASYNC_STATE_MACHINE( | |
| 1731 | async_state_machine, | |
| 1732 | move_next_block, | |
| 1733 | success_label, | |
| 1734 | end_label | |
| 1735 | ) | |
| 1736 | ); | |
| 1737 | ||
| 1738 | return true; | |
| 1739 | si | |
| 1740 | ||
| 1741 | // The state-local IL spelling of the innermost open | |
| 1742 | // dispatch holder — `'.async_state'` for async state | |
| 1743 | // machines, `'.gen_state'` for generators, null when no | |
| 1744 | // state machine is open. Used by `.try` emission and the | |
| 1745 | // finally-body state guard so the same code paths emit the | |
| 1746 | // right local name without each caller having to know. | |
| 1747 | _current_state_local_il() -> string? is | |
| 1748 | if _async_dispatch_stack.count == 0 then | |
| 1749 | return null; | |
| 1750 | fi | |
| 1751 | return _async_dispatch_stack[_async_dispatch_stack.count - 1].state_local_il; | |
| 1752 | si | |
| 1753 | ||
| 1754 | // Declare a CLR `int32` local in the MoveNext frame, copy | |
| 1755 | // the state field into it, and push the outer dispatch | |
| 1756 | // holder bound to that local. Returns the pushed holder so | |
| 1757 | // the caller can pop it at the end of the body walk via | |
| 1758 | // `_pop_async_dispatch_holder`. Used by every state-machine | |
| 1759 | // entry point (named function, lambda closure, generator). | |
| 1760 | // `state_local_il` is the local's IL spelling — | |
| 1761 | // `'.async_state'` for async, `'.gen_state'` for | |
| 1762 | // generators — and travels with the holder so inner | |
| 1763 | // dispatch sites pick it up. | |
| 1764 | _open_state_machine_entry( | |
| 1765 | state_field_ref: string, | |
| 1766 | state_local_il: string | |
| 1767 | ) -> ASYNC_DISPATCH_HOLDER is | |
| 1768 | add(".locals init (int32 {state_local_il})"); | |
| 1769 | add("ldarg.0"); | |
| 1770 | add("ldfld {state_field_ref}"); | |
| 1771 | add("stloc {state_local_il}"); | |
| 1772 | ||
| 1773 | let block = Values.BLOCK(); | |
| 1774 | add(block); | |
| 1775 | let holder = ASYNC_DISPATCH_HOLDER(block, state_local_il); | |
| 1776 | _async_dispatch_stack.add(holder); | |
| 1777 | return holder; | |
| 1778 | si | |
| 1779 | ||
| 1780 | // Push an inner dispatch holder at the top of a `.try` body | |
| 1781 | // if we're inside a state-machine method. Returns the | |
| 1782 | // holder (to pop later) or null when no state machine is | |
| 1783 | // open — the caller can pass that null back to | |
| 1784 | // `_maybe_pop_dispatch_holder` for symmetric cleanup | |
| 1785 | // without an `if` at every call site. | |
| 1786 | _maybe_push_dispatch_holder() -> ASYNC_DISPATCH_HOLDER? is | |
| 1787 | if !_current_state_local_il()? then | |
| 1788 | return null; | |
| 1789 | fi | |
| 1790 | return _push_async_dispatch_holder(); | |
| 1791 | si | |
| 1792 | ||
| 1793 | _maybe_pop_dispatch_holder(holder: ASYNC_DISPATCH_HOLDER?) is | |
| 1794 | if holder? then | |
| 1795 | _pop_async_dispatch_holder(holder); | |
| 1796 | fi | |
| 1797 | si | |
| 1798 | ||
| 1799 | // The state-machine frame for the current function — async | |
| 1800 | // or generator, whichever applies. Used by the BLOCK-with- | |
| 1801 | // suspend spill in `LIST.visit` / `visit(IF)` / `visit(CASE)` | |
| 1802 | // (in expression position) to allocate spill fields that | |
| 1803 | // survive across MoveNext re-entries. | |
| 1804 | _current_state_machine_frame() -> Semantic.Symbols.STATE_MACHINE_FRAME_BASE? is | |
| 1805 | let casm = _current_async_state_machine; | |
| 1806 | ||
| 1807 | if casm? /\ casm.frame? then | |
| 1808 | return casm.frame; | |
| 1809 | fi | |
| 1810 | ||
| 1811 | let cf = current_function; | |
| 1812 | if cf? then | |
| 1813 | let sm = Semantic.Symbols.state_machine_for(cf); | |
| 1814 | if sm? /\ sm.frame? then | |
| 1815 | return sm.frame; | |
| 1816 | fi | |
| 1817 | fi | |
| 1818 | ||
| 1819 | return null; | |
| 1820 | si | |
| 1821 | ||
| 1822 | // Does `node` contain a state-machine suspend (`await` or | |
| 1823 | // `yield`) outside any nested function literal? Drives the | |
| 1824 | // decision in `LIST.visit` / IF / CASE composite handlers | |
| 1825 | // whether to spill their BLOCK value to a frame field. | |
| 1826 | _contains_suspend(node: Trees.Node) -> bool => | |
| 1827 | val | |
| 1828 | let scanner = Syntax.Process.CONTAINS_SUSPEND_SCANNER(); | |
| 1829 | node.walk(scanner); | |
| 1830 | scanner.found | |
| 1831 | lav; | |
| 1832 | ||
| 1833 | // Build the IR Value that loads from `field` on the current | |
| 1834 | // state-machine frame — used as the spilled BLOCK's stand-in | |
| 1835 | // value. The consumer that does `add(block)` reads this | |
| 1836 | // load when the BLOCK gens, in place of the original body | |
| 1837 | // IL. | |
| 1838 | _build_frame_field_load(frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE, spill_field: Semantic.Symbols.Field) -> IR.Values.Value => | |
| 1839 | IR.Values.Load.INSTANCE_FIELD( | |
| 1840 | IR.Values.Load.REFERENCE_SELF(frame, frame.type), | |
| 1841 | spill_field | |
| 1842 | ); | |
| 1843 | ||
| 1844 | // BLOCK-with-suspend spill path for `Statements.LIST` in | |
| 1845 | // expression position. The capture-mode LIST.visit wraps | |
| 1846 | // its body in lots of state-machine plumbing (dispatch | |
| 1847 | // holders, exception-handler temps, `.try` for `want | |
| 1848 | // _dispose`) that the spill path doesn't need — the body | |
| 1849 | // emits inline in outer current_block, the result lands | |
| 1850 | // in a frame field, and the BLOCK ends up holding just a | |
| 1851 | // load. Returns true if the spill path ran (caller skips | |
| 1852 | // the ordinary capture path); false otherwise. | |
| 1853 | _try_spill_block_with_suspend(list: Statements.LIST) -> bool is | |
| 1854 | // `want_dispose` lists need their `.try` body inside | |
| 1855 | // the protected region — bypass spill and let the | |
| 1856 | // capture path handle it. | |
| 1857 | if list.want_dispose then | |
| 1858 | return false; | |
| 1859 | fi | |
| 1860 | ||
| 1861 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 1862 | self, | |
| 1863 | list.value, | |
| 1864 | _current_state_machine_frame(), | |
| 1865 | _contains_suspend(list) | |
| 1866 | ); | |
| 1867 | ||
| 1868 | if !spiller.is_spilling then | |
| 1869 | return false; | |
| 1870 | fi | |
| 1871 | ||
| 1872 | // Walk body in outer current_block. Statement IL — | |
| 1873 | // including visit(AWAIT) / visit(YIELD) suspend | |
| 1874 | // emissions and nested composites' own spills — lands | |
| 1875 | // in outer, in the same flat stream as the SM | |
| 1876 | // dispatcher. | |
| 1877 | for s in list.statements do | |
| 1878 | enter_node(s); | |
| 1879 | try | |
| 1880 | s.walk(self); | |
| 1881 | finally | |
| 1882 | leave_node(s); | |
| 1883 | yrt | |
| 1884 | od | |
| 1885 | ||
| 1886 | // Tail-evaluate-and-spill. By now the tail's value is a | |
| 1887 | // clean load (any tail-internal suspends already | |
| 1888 | // hoisted by visit(AWAIT) / visit(YIELD), and any | |
| 1889 | // tail-internal composites already spilled themselves), | |
| 1890 | // so the ldarg.0 / value-IL / stfld sequence has no | |
| 1891 | // suspend between receiver push and stfld. | |
| 1892 | if let list.last?, last.value? then | |
| 1893 | spiller.emit_value(value); | |
| 1894 | fi | |
| 1895 | ||
| 1896 | spiller.leave(); | |
| 1897 | ||
| 1898 | return true; | |
| 1899 | si | |
| 1900 | ||
| 1901 | ||
| 1902 | // Record a (state, resume_label) pair with the innermost | |
| 1903 | // open dispatch holder, so its dispatch block (at the top | |
| 1904 | // of its `.try` body OR at MoveNext outer scope) routes | |
| 1905 | // cold-resume entries to {resume_label}. No-op outside a | |
| 1906 | // state machine. | |
| 1907 | _register_resume_with_dispatch(state: int, resume_label: IR.LABEL) is | |
| 1908 | if _async_dispatch_stack.count == 0 then | |
| 1909 | return; | |
| 1910 | fi | |
| 1911 | let holder = _async_dispatch_stack[_async_dispatch_stack.count - 1]; | |
| 1912 | holder.register(state, resume_label); | |
| 1913 | si | |
| 1914 | ||
| 1915 | // Emit the `if V_state >= 0 → skip` guard at the top of a | |
| 1916 | // finally body in a state-machine method. The `leave` that | |
| 1917 | // ends a suspend (AwaitUnsafeOnCompleted or yield) fires the | |
| 1918 | // surrounding finally with state >= 0; the user's code must | |
| 1919 | // not run then. Real exits (state == -1) and exceptional | |
| 1920 | // unwinds run the finally normally. | |
| 1921 | // | |
| 1922 | // Returns the skip label so the caller can plant it at the | |
| 1923 | // end of the guarded code via `_close_finally_state_guard`. | |
| 1924 | // Returns null when not inside a state machine — caller | |
| 1925 | // emits the finally body unguarded. | |
| 1926 | _open_finally_state_guard() -> LABEL? is | |
| 1927 | let state_local_il = _current_state_local_il(); | |
| 1928 | ||
| 1929 | if !state_local_il? then | |
| 1930 | return null; | |
| 1931 | fi | |
| 1932 | ||
| 1933 | let skip = LABEL(); | |
| 1934 | add("ldloc {state_local_il}"); | |
| 1935 | add("ldc.i4.0"); | |
| 1936 | add("bge {skip}"); | |
| 1937 | ||
| 1938 | return skip; | |
| 1939 | si | |
| 1940 | ||
| 1941 | _close_finally_state_guard(skip: LABEL?) is | |
| 1942 | if skip? then | |
| 1943 | get_brancher_for_block().label(skip); | |
| 1944 | fi | |
| 1945 | si | |
| 1946 | ||
| 1947 | // Push a new dispatch holder onto the stack and emit its | |
| 1948 | // placeholder block at the current emission point. Returns | |
| 1949 | // the holder so the caller can pop it once the protected | |
| 1950 | // region's body has been walked. Use when opening a `.try` | |
| 1951 | // body inside an async function or generator so the | |
| 1952 | // AWAIT_SUSPENDs / YIELDs that fire in the body register | |
| 1953 | // here and the dispatch ends up inside the same region as | |
| 1954 | // the targets it reaches. The new holder inherits its | |
| 1955 | // state-local IL spelling from the top of the stack — the | |
| 1956 | // outer holder (pushed at MoveNext entry) chose the name | |
| 1957 | // (`'.async_state'` for async, `'.gen_state'` for | |
| 1958 | // generators) so inner holders just match. | |
| 1959 | _push_async_dispatch_holder() -> ASYNC_DISPATCH_HOLDER is | |
| 1960 | assert _async_dispatch_stack.count > 0 else "no outer dispatch holder to inherit state-local name from"; | |
| 1961 | let outer = _async_dispatch_stack[_async_dispatch_stack.count - 1]; | |
| 1962 | let block = Values.BLOCK(); | |
| 1963 | add(block); | |
| 1964 | let holder = ASYNC_DISPATCH_HOLDER(block, outer.state_local_il); | |
| 1965 | _async_dispatch_stack.add(holder); | |
| 1966 | return holder; | |
| 1967 | si | |
| 1968 | ||
| 1969 | // Populate the holder's dispatch block from registered | |
| 1970 | // (state, cold_resume_label) entries and pop. No-op if no | |
| 1971 | // entries — keeps emitted IL minimal when a `.try` body | |
| 1972 | // contains no awaits. | |
| 1973 | _pop_async_dispatch_holder(holder: ASYNC_DISPATCH_HOLDER) is | |
| 1974 | _populate_async_dispatch(holder); | |
| 1975 | assert _async_dispatch_stack.count > 0 else "dispatch holder stack underflow"; | |
| 1976 | _async_dispatch_stack.remove_at(_async_dispatch_stack.count - 1); | |
| 1977 | si | |
| 1978 | ||
| 1979 | // Fill the holder's placeholder block with one | |
| 1980 | // `ldloc V_state; ldc.i4 N; beq cold_resume_N` entry per | |
| 1981 | // registered await. The placeholder was inserted into the | |
| 1982 | // surrounding emission stream when the holder was pushed — | |
| 1983 | // these instructions now sit at the top of the protected | |
| 1984 | // region's body, before any user code. | |
| 1985 | _populate_async_dispatch(holder: ASYNC_DISPATCH_HOLDER) is | |
| 1986 | for entry in holder.entries do | |
| 1987 | holder.block.add("ldloc {holder.state_local_il}"); | |
| 1988 | holder.block.add("ldc.i4 {entry.state}"); | |
| 1989 | holder.block.add("beq {entry.cold_resume_label}"); | |
| 1990 | od | |
| 1991 | si | |
| 1992 | ||
| 1993 | // Emit all pending state-machine classes accumulated during | |
| 1994 | // the walk of an enclosing class or namespace. Called at the | |
| 1995 | // tail of visit(`class) / visit(`namespace) so the classes | |
| 1996 | // land at namespace scope, parallel to the user's class. | |
| 1997 | emit_pending_state_machines() is | |
| 1998 | if _pending_state_machines.count > 0 then | |
| 1999 | let pending = _pending_state_machines; | |
| 2000 | _pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE](); | |
| 2001 | ||
| 2002 | for psm in pending do | |
| 2003 | _emit_state_machine_class(psm.state_machine, psm.move_next_block); | |
| 2004 | od | |
| 2005 | fi | |
| 2006 | ||
| 2007 | if _pending_async_state_machines.count > 0 then | |
| 2008 | let pending = _pending_async_state_machines; | |
| 2009 | _pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE](); | |
| 2010 | ||
| 2011 | for pasm in pending do | |
| 2012 | _emit_async_state_machine_class( | |
| 2013 | pasm.async_state_machine, | |
| 2014 | pasm.move_next_block, | |
| 2015 | pasm.success_label, | |
| 2016 | pasm.end_label | |
| 2017 | ); | |
| 2018 | od | |
| 2019 | fi | |
| 2020 | si | |
| 2021 | ||
| 2022 | // Emits the synthesised IAsyncStateMachine class — parallels | |
| 2023 | // `_emit_state_machine_class` for generators. Class members: | |
| 2024 | // `$state`, `$builder`, `$result` (value-async), `$outer_self` | |
| 2025 | // (instance), `$arg_*`, `$local_*`, `$awaiter_*` fields; ctor; | |
| 2026 | // MoveNext (with entry-dispatch and try/catch routing through | |
| 2027 | // the builder); trivial SetStateMachine. | |
| 2028 | _emit_async_state_machine_class( | |
| 2029 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE, | |
| 2030 | move_next_block: Values.BLOCK, | |
| 2031 | success_label: IR.LABEL, | |
| 2032 | end_label: IR.LABEL | |
| 2033 | ) is | |
| 2034 | let frame = async_state_machine.frame; | |
| 2035 | ||
| 2036 | assert frame? else "async state machine has no frame at IL emission"; | |
| 2037 | ||
| 2038 | async_state_machine.install_body_emission_overrides(); | |
| 2039 | ||
| 2040 | println_comment("define async state-machine class for {async_state_machine.function.description}"); | |
| 2041 | ||
| 2042 | let class_buffer = StringBuilder(); | |
| 2043 | frame.gen_definition_header(class_buffer); | |
| 2044 | println(class_buffer); | |
| 2045 | ||
| 2046 | println("{{"); | |
| 2047 | _context.indent(); | |
| 2048 | ||
| 2049 | // Fields. | |
| 2050 | let state_buf = StringBuilder(); | |
| 2051 | frame.state_field.gen_definition_header(state_buf); | |
| 2052 | println(state_buf); | |
| 2053 | ||
| 2054 | let builder_field = frame.builder_field; | |
| 2055 | if builder_field? then | |
| 2056 | let builder_buf = StringBuilder(); | |
| 2057 | builder_field.gen_definition_header(builder_buf); | |
| 2058 | println(builder_buf); | |
| 2059 | fi | |
| 2060 | ||
| 2061 | let result_field = frame.result_field; | |
| 2062 | if result_field? then | |
| 2063 | let result_buf = StringBuilder(); | |
| 2064 | result_field.gen_definition_header(result_buf); | |
| 2065 | println(result_buf); | |
| 2066 | fi | |
| 2067 | ||
| 2068 | let outer_self_field = frame.outer_self_field; | |
| 2069 | if outer_self_field? then | |
| 2070 | let outer_buf = StringBuilder(); | |
| 2071 | outer_self_field.gen_definition_header(outer_buf); | |
| 2072 | println(outer_buf); | |
| 2073 | fi | |
| 2074 | ||
| 2075 | for arg_field in frame.argument_fields do | |
| 2076 | let arg_buf = StringBuilder(); | |
| 2077 | arg_field.gen_definition_header(arg_buf); | |
| 2078 | println(arg_buf); | |
| 2079 | od | |
| 2080 | ||
| 2081 | for local_field in frame.local_fields do | |
| 2082 | let local_buf = StringBuilder(); | |
| 2083 | local_field.gen_definition_header(local_buf); | |
| 2084 | println(local_buf); | |
| 2085 | od | |
| 2086 | ||
| 2087 | for awaiter_field in frame.awaiter_fields do | |
| 2088 | let awaiter_buf = StringBuilder(); | |
| 2089 | awaiter_field.gen_definition_header(awaiter_buf); | |
| 2090 | println(awaiter_buf); | |
| 2091 | od | |
| 2092 | ||
| 2093 | // .ctor — same shape as the generator's: chain to | |
| 2094 | // Object::.ctor, then store each ctor arg into its frame | |
| 2095 | // field. Builder isn't initialised here; the outer | |
| 2096 | // method's body calls AsyncTaskMethodBuilder.Create() | |
| 2097 | // and stfld's it after newobj. | |
| 2098 | _gen_async_state_machine_ctor(frame); | |
| 2099 | ||
| 2100 | // MoveNext — wraps the user's body in try/catch so | |
| 2101 | // exceptions route through builder.SetException. | |
| 2102 | _gen_async_move_next(async_state_machine, move_next_block, success_label, end_label); | |
| 2103 | ||
| 2104 | // SetStateMachine — trivial no-op for class-based SM. | |
| 2105 | _boilerplate_generator.gen("async-set-state-machine"); | |
| 2106 | ||
| 2107 | _context.outdent(); | |
| 2108 | println("}}"); | |
| 2109 | ||
| 2110 | async_state_machine.uninstall_body_emission_overrides(); | |
| 2111 | si | |
| 2112 | ||
| 2113 | _gen_async_state_machine_ctor(frame: Semantic.Symbols.ASYNC_STATE_MACHINE_FRAME) is | |
| 2114 | let ctor_buf = StringBuilder(); | |
| 2115 | frame.constructor.gen_definition_header(ctor_buf); | |
| 2116 | println(ctor_buf); | |
| 2117 | ||
| 2118 | println("{{"); | |
| 2119 | _context.indent(); | |
| 2120 | _context.write_line(".maxstack 8"); | |
| 2121 | _context.write_line("ldarg.0"); | |
| 2122 | _context.write_line("call instance void ['System.Runtime']'System'.'Object'::'.ctor'()"); | |
| 2123 | ||
| 2124 | let arg_names = frame.constructor.argument_names; | |
| 2125 | let i mut = 0; | |
| 2126 | ||
| 2127 | let outer_self_field = frame.outer_self_field; | |
| 2128 | if outer_self_field? then | |
| 2129 | _context.write_line("ldarg.0"); | |
| 2130 | _context.write_line("ldarg '{arg_names[i]}'"); | |
| 2131 | _context.write_line("stfld {outer_self_field.get_il_reference()}"); | |
| 2132 | i = i + 1; | |
| 2133 | fi | |
| 2134 | ||
| 2135 | for arg_field in frame.argument_fields do | |
| 2136 | _context.write_line("ldarg.0"); | |
| 2137 | _context.write_line("ldarg '{arg_names[i]}'"); | |
| 2138 | _context.write_line("stfld {arg_field.get_il_reference()}"); | |
| 2139 | i = i + 1; | |
| 2140 | od | |
| 2141 | ||
| 2142 | _context.write_line("ret"); | |
| 2143 | _context.outdent(); | |
| 2144 | println("}}"); | |
| 2145 | si | |
| 2146 | ||
| 2147 | _gen_async_move_next( | |
| 2148 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE, | |
| 2149 | move_next_block: Values.BLOCK, | |
| 2150 | success_label: IR.LABEL, | |
| 2151 | end_label: IR.LABEL | |
| 2152 | ) is | |
| 2153 | let frame = async_state_machine.frame; | |
| 2154 | ||
| 2155 | assert frame? else "async state machine has no frame at IL emission"; | |
| 2156 | ||
| 2157 | let builder_field = frame.builder_field!; | |
| 2158 | ||
| 2159 | println(".method public final hidebysig newslot virtual instance default void 'MoveNext'() cil managed"); | |
| 2160 | println("{{"); | |
| 2161 | _context.indent(); | |
| 2162 | _context.write_line(".override class ['System.Runtime']System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext"); | |
| 2163 | ||
| 2164 | // Wrap the move_next_block in a try/catch that routes | |
| 2165 | // exceptions through builder.SetException. The success | |
| 2166 | // fall-through path (leave success_label) calls | |
| 2167 | // builder.SetResult and falls through to end_label/ret. | |
| 2168 | _context.write_line(".try {{"); | |
| 2169 | _context.indent(); | |
| 2170 | ||
| 2171 | _context.reset_line_tracking(); | |
| 2172 | move_next_block.gen(_context); | |
| 2173 | ||
| 2174 | _context.outdent(); | |
| 2175 | _context.write_line("}}"); | |
| 2176 | ||
| 2177 | let exception_il = "class ['System.Runtime']System.Exception"; | |
| 2178 | _context.write_line("catch {exception_il} {{"); | |
| 2179 | _context.indent(); | |
| 2180 | _context.write_line(".locals init ({exception_il} '.exception')"); | |
| 2181 | _context.write_line("stloc '.exception'"); | |
| 2182 | _context.write_line("ldarg.0"); | |
| 2183 | _context.write_line("ldc.i4.m1"); | |
| 2184 | _context.write_line("stfld {frame.state_field.get_il_reference()}"); | |
| 2185 | _context.write_line("ldarg.0"); | |
| 2186 | _context.write_line("ldflda {builder_field.get_il_reference()}"); | |
| 2187 | _context.write_line("ldloc '.exception'"); | |
| 2188 | let builder_il = builder_field.type!.get_il_type(); | |
| 2189 | _context.write_line("call instance void {builder_il}::'SetException'({exception_il})"); | |
| 2190 | _context.write_line("leave {end_label}"); | |
| 2191 | _context.outdent(); | |
| 2192 | _context.write_line("}}"); | |
| 2193 | ||
| 2194 | // Success: state = -1; builder.SetResult(<result>). | |
| 2195 | // | |
| 2196 | // `SetResult(T)` on `AsyncTaskMethodBuilder<T>` takes | |
| 2197 | // class-T; the methodref's arg type slot must be `!0` | |
| 2198 | // (open class-T), not the substituted concrete T. The | |
| 2199 | // non-generic `AsyncTaskMethodBuilder`'s SetResult takes | |
| 2200 | // no value-async result and has no class-T issue. | |
| 2201 | _context.write_line("{success_label}:"); | |
| 2202 | _context.write_line("ldarg.0"); | |
| 2203 | _context.write_line("ldc.i4.m1"); | |
| 2204 | _context.write_line("stfld {frame.state_field.get_il_reference()}"); | |
| 2205 | _context.write_line("ldarg.0"); | |
| 2206 | _context.write_line("ldflda {builder_field.get_il_reference()}"); | |
| 2207 | let result_field = frame.result_field; | |
| 2208 | if result_field? then | |
| 2209 | _context.write_line("ldarg.0"); | |
| 2210 | _context.write_line("ldfld {result_field.get_il_reference()}"); | |
| 2211 | _context.write_line("call instance void {builder_il}::'SetResult'(!0)"); | |
| 2212 | else | |
| 2213 | _context.write_line("call instance void {builder_il}::'SetResult'()"); | |
| 2214 | fi | |
| 2215 | ||
| 2216 | _context.write_line("{end_label}:"); | |
| 2217 | _context.write_line("ret"); | |
| 2218 | ||
| 2219 | _context.outdent(); | |
| 2220 | println("}}"); | |
| 2221 | si | |
| 2222 | ||
| 2223 | _emit_state_machine_class(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: Values.BLOCK) is | |
| 2224 | let frame = state_machine.frame; | |
| 2225 | ||
| 2226 | assert frame? else "state machine has no frame at IL emission"; | |
| 2227 | ||
| 2228 | // Re-install the body-emission override so deferred | |
| 2229 | // IR.Value.gen() inside MoveNext renders function-T as | |
| 2230 | // class-level `!N`. Uninstalled at the bottom of this | |
| 2231 | // method. | |
| 2232 | state_machine.install_body_emission_overrides(); | |
| 2233 | ||
| 2234 | println_comment("define state-machine class for {state_machine.function.description}"); | |
| 2235 | ||
| 2236 | let class_buffer = StringBuilder(); | |
| 2237 | frame.gen_definition_header(class_buffer); | |
| 2238 | println(class_buffer); | |
| 2239 | ||
| 2240 | println("{{"); | |
| 2241 | _context.indent(); | |
| 2242 | ||
| 2243 | // Fields. | |
| 2244 | let state_buf = StringBuilder(); | |
| 2245 | frame.state_field.gen_definition_header(state_buf); | |
| 2246 | println(state_buf); | |
| 2247 | ||
| 2248 | let current_buf = StringBuilder(); | |
| 2249 | frame.current_field.gen_definition_header(current_buf); | |
| 2250 | println(current_buf); | |
| 2251 | ||
| 2252 | let outer_self_field = frame.outer_self_field; | |
| 2253 | if outer_self_field? then | |
| 2254 | let outer_buf = StringBuilder(); | |
| 2255 | outer_self_field.gen_definition_header(outer_buf); | |
| 2256 | println(outer_buf); | |
| 2257 | fi | |
| 2258 | ||
| 2259 | for arg_field in frame.argument_fields do | |
| 2260 | let arg_buf = StringBuilder(); | |
| 2261 | arg_field.gen_definition_header(arg_buf); | |
| 2262 | println(arg_buf); | |
| 2263 | od | |
| 2264 | ||
| 2265 | for local_field in frame.local_fields do | |
| 2266 | let local_buf = StringBuilder(); | |
| 2267 | local_field.gen_definition_header(local_buf); | |
| 2268 | println(local_buf); | |
| 2269 | od | |
| 2270 | ||
| 2271 | // .ctor — parameterless, chains to Object::.ctor. | |
| 2272 | _gen_state_machine_ctor(frame); | |
| 2273 | ||
| 2274 | // MoveNext — populated during the body walk. | |
| 2275 | println(".method public final hidebysig newslot virtual instance default bool 'MoveNext'() cil managed"); | |
| 2276 | println("{{"); | |
| 2277 | _context.indent(); | |
| 2278 | _context.reset_line_tracking(); | |
| 2279 | move_next_block.gen(_context); | |
| 2280 | _context.outdent(); | |
| 2281 | println("}}"); | |
| 2282 | ||
| 2283 | // Other Iterator[T] / IDisposable members. | |
| 2284 | _gen_state_machine_accessors(frame); | |
| 2285 | ||
| 2286 | gen_ienumerable_boilerplate(frame); | |
| 2287 | gen_ienumerator_boilerplate(frame); | |
| 2288 | ||
| 2289 | _context.outdent(); | |
| 2290 | println("}}"); | |
| 2291 | ||
| 2292 | state_machine.uninstall_body_emission_overrides(); | |
| 2293 | si | |
| 2294 | ||
| 2295 | _gen_state_machine_ctor(frame: Semantic.Symbols.STATE_MACHINE_FRAME) is | |
| 2296 | let ctor_buf = StringBuilder(); | |
| 2297 | frame.constructor.gen_definition_header(ctor_buf); | |
| 2298 | println(ctor_buf); | |
| 2299 | ||
| 2300 | println("{{"); | |
| 2301 | _context.indent(); | |
| 2302 | _context.write_line(".maxstack 8"); | |
| 2303 | _context.write_line("ldarg.0"); | |
| 2304 | _context.write_line("call instance void ['System.Runtime']'System'.'Object'::'.ctor'()"); | |
| 2305 | ||
| 2306 | // Constructor parameter layout (mirrors what | |
| 2307 | // STATE_MACHINE_FRAME.declare set up): | |
| 2308 | // [$outer_self,] arg0, arg1, ... | |
| 2309 | // The instance-generator outer method passes `this` as | |
| 2310 | // the leading argument; static / global generators | |
| 2311 | // skip that prefix. | |
| 2312 | let arg_names = frame.constructor.argument_names; | |
| 2313 | let i mut = 0; | |
| 2314 | ||
| 2315 | let outer_self_field = frame.outer_self_field; | |
| 2316 | if outer_self_field? then | |
| 2317 | _context.write_line("ldarg.0"); | |
| 2318 | _context.write_line("ldarg '{arg_names[i]}'"); | |
| 2319 | _context.write_line("stfld {outer_self_field.get_il_reference()}"); | |
| 2320 | i = i + 1; | |
| 2321 | fi | |
| 2322 | ||
| 2323 | // Copy each user-parameter ctor argument into its | |
| 2324 | // frame field. Parameter order matches argument_fields | |
| 2325 | // by construction. | |
| 2326 | for arg_field in frame.argument_fields do | |
| 2327 | _context.write_line("ldarg.0"); | |
| 2328 | _context.write_line("ldarg '{arg_names[i]}'"); | |
| 2329 | _context.write_line("stfld {arg_field.get_il_reference()}"); | |
| 2330 | ||
| 2331 | i = i + 1; | |
| 2332 | od | |
| 2333 | ||
| 2334 | _context.write_line("ret"); | |
| 2335 | _context.outdent(); | |
| 2336 | println("}}"); | |
| 2337 | si | |
| 2338 | ||
| 2339 | _gen_state_machine_accessors(frame: Semantic.Symbols.STATE_MACHINE_FRAME) is | |
| 2340 | let element_il = StringBuilder(); | |
| 2341 | frame.class_element_type.gen_type(element_il); | |
| 2342 | let element_il_text = element_il.to_string(); | |
| 2343 | ||
| 2344 | let current_field_ref = frame.current_field.get_il_reference(); | |
| 2345 | ||
| 2346 | // get_Current — IEnumerator<T>.get_Current | |
| 2347 | println(".method public final hidebysig newslot virtual specialname instance default {element_il_text} 'get_Current'() cil managed"); | |
| 2348 | println("{{"); | |
| 2349 | _context.indent(); | |
| 2350 | _context.write_line(".maxstack 1"); | |
| 2351 | _context.write_line("ldarg.0"); | |
| 2352 | _context.write_line("ldfld {current_field_ref}"); | |
| 2353 | _context.write_line("ret"); | |
| 2354 | _context.outdent(); | |
| 2355 | println("}}"); | |
| 2356 | ||
| 2357 | // GetEnumerator — IEnumerable<T>.GetEnumerator returns | |
| 2358 | // self (we implement IEnumerator<T> directly). | |
| 2359 | println(".method public final hidebysig newslot virtual specialname instance default class ['System.Runtime']'System'.'Collections'.'Generic'.'IEnumerator`1'<{element_il_text}> 'GetEnumerator'() cil managed"); | |
| 2360 | println("{{"); | |
| 2361 | _context.indent(); | |
| 2362 | _context.write_line(".maxstack 1"); | |
| 2363 | _context.write_line("ldarg.0"); | |
| 2364 | _context.write_line("ret"); | |
| 2365 | _context.outdent(); | |
| 2366 | println("}}"); | |
| 2367 | ||
| 2368 | // Dispose — no-op. Known gap: if a generator with | |
| 2369 | // yield inside a try-finally is abandoned mid-flight | |
| 2370 | // (the consumer stops calling MoveNext before | |
| 2371 | // exhaustion), the user's finally body never runs. C# | |
| 2372 | // handles this via per-state finally tables walked by | |
| 2373 | // Dispose(); ghūl would need the same wiring. Until | |
| 2374 | // then, generators that own resources should be | |
| 2375 | // consumed to completion or with an explicit | |
| 2376 | // try/finally at the call site. | |
| 2377 | println(".method public final hidebysig newslot virtual instance default void 'Dispose'() cil managed"); | |
| 2378 | println("{{"); | |
| 2379 | _context.indent(); | |
| 2380 | _context.write_line(".maxstack 1"); | |
| 2381 | _context.write_line("ret"); | |
| 2382 | _context.outdent(); | |
| 2383 | println("}}"); | |
| 2384 | ||
| 2385 | // Reset — rewind to the initial state. The next MoveNext | |
| 2386 | // re-enters at state 0 and re-runs the body from the top, | |
| 2387 | // which re-initializes the body locals (only the captured | |
| 2388 | // parameters and $outer_self persist). C# throws here | |
| 2389 | // because its consumers re-iterate via a fresh GetEnumerator; | |
| 2390 | // ghūl's pipes rewind via reset(), so a resettable generator | |
| 2391 | // iterator is the consistent choice. | |
| 2392 | println(".method public final hidebysig newslot virtual instance default void 'Reset'() cil managed"); | |
| 2393 | println("{{"); | |
| 2394 | _context.indent(); | |
| 2395 | _context.write_line(".maxstack 2"); | |
| 2396 | _context.write_line("ldarg.0"); | |
| 2397 | _context.write_line("ldc.i4.0"); | |
| 2398 | _context.write_line("stfld {frame.state_field.get_il_reference()}"); | |
| 2399 | _context.write_line("ret"); | |
| 2400 | _context.outdent(); | |
| 2401 | println("}}"); | |
| 2402 | ||
| 2403 | // to_string — a generator is a Pipe[T], so stringify it the | |
| 2404 | // way every other Pipe does: join the elements. The Pipe | |
| 2405 | // trait's default to_string is a default interface method, | |
| 2406 | // which does not override Object.ToString, so each | |
| 2407 | // implementing class (here, the state machine) must provide | |
| 2408 | // its own ToString — same as ADAPTOR_PIPE / MAP_PIPE / etc. | |
| 2409 | println(".method public virtual hidebysig instance default class ['System.Runtime']'System'.'String' 'ToString'() cil managed"); | |
| 2410 | println("{{"); | |
| 2411 | _context.indent(); | |
| 2412 | _context.write_line(".maxstack 8"); | |
| 2413 | _context.write_line("ldarg.0"); | |
| 2414 | _context.write_line("ldstr \", \""); | |
| 2415 | _context.write_line("callvirt instance class ['System.Runtime']'System'.'String' class ['ghul-runtime']'Ghul'.'Pipes'.'Pipe'<{element_il_text}>::'join'(class ['System.Runtime']'System'.'String')"); | |
| 2416 | _context.write_line("ret"); | |
| 2417 | _context.outdent(); | |
| 2418 | println("}}"); | |
| 2419 | si | |
| 2420 | ||
| 2421 | pre(body: Bodies.BLOCK) -> bool is | |
| 2422 | add(".maxstack 64"); | |
| 2423 | ||
| 2424 | return super.pre(body); | |
| 2425 | si | |
| 2426 | ||
| 2427 | pre(expression_body: Bodies.EXPRESSION) -> bool is | |
| 2428 | add(".maxstack 64"); | |
| 2429 | ||
| 2430 | return super.pre(expression_body); | |
| 2431 | si | |
| 2432 | ||
| 2433 | visit(expression_body: Bodies.EXPRESSION) is | |
| 2434 | let body_value = expression_body.expression.value; | |
| 2435 | if body_value? /\ body_value.type? then | |
| 2436 | let value = | |
| 2437 | _boxer.box_if_needed( | |
| 2438 | body_value, | |
| 2439 | current_function!.return_type! | |
| 2440 | ); | |
| 2441 | ||
| 2442 | add(value); | |
| 2443 | elif let | |
| 2444 | self.current_function? /\ | |
| 2445 | current_function.return_type? /\ | |
| 2446 | !current_function.return_type.is_void | |
| 2447 | then | |
| 2448 | // Diverging body (`=> throw E`): the body's own IL | |
| 2449 | // already terminates control flow. Emit a default-value | |
| 2450 | // trailer so the unreachable fall-off is verifiable, | |
| 2451 | // matching statement-bodied functions that end in throw. | |
| 2452 | add(".locals init ({current_function.return_type!.get_il_type()} '.default')"); | |
| 2453 | add("ldloc '.default'"); | |
| 2454 | fi | |
| 2455 | ||
| 2456 | add("ret"); | |
| 2457 | ||
| 2458 | super.visit(expression_body); | |
| 2459 | si | |
| 2460 | ||
| 2461 | pre(property: Definitions.PROPERTY) -> bool is | |
| 2462 | let symbol = symbol_for(property); | |
| 2463 | ||
| 2464 | if !symbol? then | |
| 2465 | return true; | |
| 2466 | fi | |
| 2467 | ||
| 2468 | // Property AST node carries either a Property symbol or a Variable | |
| 2469 | // symbol (declare_symbols collapses `_name: T;` to a variable). Both | |
| 2470 | // need wrapping when at namespace scope. | |
| 2471 | let globals_namespace: Semantic.Symbols.NAMESPACE? mut = null; | |
| 2472 | ||
| 2473 | if let global_property: Semantic.Symbols.GLOBAL_PROPERTY = symbol then | |
| 2474 | globals_namespace = cast Semantic.Symbols.NAMESPACE?(global_property.owner)!; | |
| 2475 | else | |
| 2476 | if let global_variable: Semantic.Symbols.GLOBAL_VARIABLE = symbol then | |
| 2477 | globals_namespace = cast Semantic.Symbols.NAMESPACE?(global_variable.owner)!; | |
| 2478 | fi | |
| 2479 | fi | |
| 2480 | ||
| 2481 | if globals_namespace? then | |
| 2482 | gen_globals_class_open(globals_namespace); | |
| 2483 | fi | |
| 2484 | ||
| 2485 | let buffer = System.Text.StringBuilder(); | |
| 2486 | symbol.gen_definition_header(buffer); | |
| 2487 | ||
| 2488 | // Attribute directives that must associate with the | |
| 2489 | // just-emitted declaration: NullableAttribute for a slot whose | |
| 2490 | // tree carries a reference-`?` position, plus any attribute | |
| 2491 | // pragmas resolved onto the symbol. Two emission shapes hit | |
| 2492 | // this path: | |
| 2493 | // - `.property ... { .get .set }` — splice the .custom | |
| 2494 | // inside the braces (where C# puts it, and where ILASM | |
| 2495 | // associates it). | |
| 2496 | // - `.field ... 'name'` (declare_symbols collapsed a | |
| 2497 | // `_name: T;` declaration to a Variable) — append the | |
| 2498 | // .custom on a following line; ILASM associates a | |
| 2499 | // `.custom` immediately after a `.field` with it. | |
| 2500 | let attribute_lines = Collections.LIST[string](); | |
| 2501 | ||
| 2502 | let slot_type: Semantic.Types.Type? mut = null; | |
| 2503 | ||
| 2504 | if let property_symbol: Semantic.Symbols.Property = symbol then | |
| 2505 | slot_type = property_symbol.type; | |
| 2506 | else | |
| 2507 | if let variable_symbol: Semantic.Symbols.Variable = symbol then | |
| 2508 | slot_type = variable_symbol.type; | |
| 2509 | fi | |
| 2510 | fi | |
| 2511 | ||
| 2512 | if let type = slot_type then | |
| 2513 | let nullable_line = Semantic.DotNet.NULLABILITY.gen_attribute_line_for_type(type); | |
| 2514 | ||
| 2515 | if nullable_line? then | |
| 2516 | attribute_lines.add(nullable_line); | |
| 2517 | fi | |
| 2518 | ||
| 2519 | let tuple_names_line = Semantic.DotNet.TUPLE_ELEMENT_NAMES.gen_attribute_line_for_type(type); | |
| 2520 | ||
| 2521 | if tuple_names_line? then | |
| 2522 | attribute_lines.add(tuple_names_line); | |
| 2523 | fi | |
| 2524 | fi | |
| 2525 | ||
| 2526 | if symbol.custom_attributes? then | |
| 2527 | for attribute in symbol.custom_attributes do | |
| 2528 | attribute_lines.add(attribute.gen_il_line()); | |
| 2529 | od | |
| 2530 | fi | |
| 2531 | ||
| 2532 | if attribute_lines.count > 0 then | |
| 2533 | if isa Semantic.Symbols.Property(symbol) then | |
| 2534 | let rendered = buffer.to_string(); | |
| 2535 | let close = rendered.last_index_of('}'); | |
| 2536 | ||
| 2537 | if close >= 0 then | |
| 2538 | buffer.clear(); | |
| 2539 | buffer.append(rendered.substring(0, close)); | |
| 2540 | ||
| 2541 | for line in attribute_lines do | |
| 2542 | buffer.append(' '); | |
| 2543 | buffer.append(line); | |
| 2544 | od | |
| 2545 | ||
| 2546 | buffer.append(" }}"); | |
| 2547 | fi | |
| 2548 | else | |
| 2549 | for line in attribute_lines do | |
| 2550 | buffer.append('\n'); | |
| 2551 | buffer.append(line); | |
| 2552 | od | |
| 2553 | fi | |
| 2554 | fi | |
| 2555 | ||
| 2556 | println(buffer.to_string()); | |
| 2557 | ||
| 2558 | if globals_namespace? then | |
| 2559 | gen_globals_class_close(); | |
| 2560 | fi | |
| 2561 | ||
| 2562 | return true; | |
| 2563 | si | |
| 2564 | ||
| 2565 | pre(pragma: Definitions.PRAGMA) -> bool is | |
| 2566 | process_pragma(pragma.pragma, true); | |
| 2567 | return false; | |
| 2568 | si | |
| 2569 | ||
| 2570 | visit(pragma: Definitions.PRAGMA) is | |
| 2571 | process_pragma(pragma.pragma, false); | |
| 2572 | si | |
| 2573 | ||
| 2574 | process_pragma(pragma: Pragmas.PRAGMA, is_enter: bool) is | |
| 2575 | let name = pragma.name.to_string(); | |
| 2576 | ||
| 2577 | if name =~ "IL.output" then | |
| 2578 | if pragma.arguments.expressions.count != 1 then | |
| 2579 | _logger.error(pragma.arguments.location, "expected 1 argument"); | |
| 2580 | return; | |
| 2581 | fi | |
| 2582 | ||
| 2583 | let argument = pragma.arguments.expressions[0]; | |
| 2584 | ||
| 2585 | if !isa Expressions.Literals.STRING(argument) then | |
| 2586 | _logger.error(pragma.arguments.location, "expected a string literal argument"); | |
| 2587 | return; | |
| 2588 | fi | |
| 2589 | ||
| 2590 | let file_name = argument.value_string; | |
| 2591 | ||
| 2592 | if is_enter then | |
| 2593 | _il_output_depth = _il_output_depth + 1; | |
| 2594 | ||
| 2595 | if _block_context.is_in_block then | |
| 2596 | current_block.add(Values.ENTER_FILE(file_name)); | |
| 2597 | else | |
| 2598 | _context.enter_file(file_name, false); | |
| 2599 | fi | |
| 2600 | else | |
| 2601 | _il_output_depth = _il_output_depth - 1; | |
| 2602 | ||
| 2603 | if _block_context.is_in_block then | |
| 2604 | current_block.add(Values.LEAVE_FILE(file_name)); | |
| 2605 | else | |
| 2606 | _context.leave_file(file_name); | |
| 2607 | fi | |
| 2608 | fi | |
| 2609 | elif name =~ "IL.entrypoint" \/ name =~ "entry" then | |
| 2610 | if is_enter then | |
| 2611 | if _context.seen_entrypoint then | |
| 2612 | _logger.error(pragma.location, "duplicate entrypoint"); | |
| 2613 | else | |
| 2614 | _context.seen_entrypoint = true; | |
| 2615 | _context.write_line(".entrypoint"); | |
| 2616 | fi | |
| 2617 | fi | |
| 2618 | elif name =~ "test" then | |
| 2619 | if is_enter then | |
| 2620 | if current_function? then | |
| 2621 | _boilerplate_generator.gen("test-method"); | |
| 2622 | else | |
| 2623 | _boilerplate_generator.gen("test-class"); | |
| 2624 | fi | |
| 2625 | fi | |
| 2626 | elif name =~ "test_class" then | |
| 2627 | if is_enter then | |
| 2628 | _boilerplate_generator.gen("test-class"); | |
| 2629 | fi | |
| 2630 | elif name =~ "test_method" then | |
| 2631 | if is_enter then | |
| 2632 | _boilerplate_generator.gen("test-method"); | |
| 2633 | fi | |
| 2634 | fi | |
| 2635 | si | |
| 2636 | ||
| 2637 | pre(function: Trees.Expressions.FUNCTION) -> bool is | |
| 2638 | super.pre(function); | |
| 2639 | ||
| 2640 | let closure = cast Semantic.Symbols.Closure?(scope_for(function)); | |
| 2641 | ||
| 2642 | if !closure? then | |
| 2643 | return true; | |
| 2644 | fi | |
| 2645 | ||
| 2646 | closure.map_type_arguments(); | |
| 2647 | ||
| 2648 | return true; | |
| 2649 | si | |
| 2650 | ||
| 2651 | visit(function: Trees.Expressions.FUNCTION) is | |
| 2652 | let closure = cast Semantic.Symbols.Closure?(scope_for(function))!; | |
| 2653 | ||
| 2654 | // Async closure dispatch (parallel to the named-function | |
| 2655 | // path in pre(Definitions.FUNCTION)). The lambda's $anon | |
| 2656 | // method body becomes a state-machine launch sequence | |
| 2657 | // (Create + MoveNext + return Task); the lambda body | |
| 2658 | // walks into a separate move_next_block; the SM class is | |
| 2659 | // queued for emission at class/namespace tail. | |
| 2660 | let async_sm = Semantic.Symbols.async_state_machine_for(closure); | |
| 2661 | if async_sm? then | |
| 2662 | _emit_async_closure(function, closure, async_sm); | |
| 2663 | super.visit(function); | |
| 2664 | closure.unmap_type_arguments(); | |
| 2665 | return; | |
| 2666 | fi | |
| 2667 | ||
| 2668 | enter_block(); | |
| 2669 | ||
| 2670 | // Safety net: if a closure's return type resolved to ERROR | |
| 2671 | // (e.g. its body referenced a variable that itself never got | |
| 2672 | // a type), the consume-side check that should have surfaced | |
| 2673 | // a diagnostic missed this path — but `get_il_type()` will | |
| 2674 | // throw NotImplementedException as soon as we ask it to emit | |
| 2675 | // ERROR. Report a located diagnostic at the function's site | |
| 2676 | // and skip IL emission for this body. The function symbol | |
| 2677 | // itself remains valid; any caller will already have its | |
| 2678 | // own ERROR-typed result and will be handled by its own | |
| 2679 | // visit. (See error.ghul:30-34 for the underlying rule.) | |
| 2680 | if closure.return_type? /\ closure.return_type.is_error then | |
| 2681 | _logger.error(function.location, "cannot infer return type here"); | |
| 2682 | add("ret"); | |
| 2683 | _context.enter_buffer(false); | |
| 2684 | _context.reset_line_tracking(); | |
| 2685 | current_block.gen(_context); | |
| 2686 | leave_block(); | |
| 2687 | closure.il_body = _context.leave_buffer(); | |
| 2688 | super.visit(function); | |
| 2689 | closure.unmap_type_arguments(); | |
| 2690 | return; | |
| 2691 | fi | |
| 2692 | ||
| 2693 | _gen_closure_destructure_prologue(function, closure); | |
| 2694 | ||
| 2695 | function.body.walk(self); | |
| 2696 | ||
| 2697 | if | |
| 2698 | closure.return_type? /\ | |
| 2699 | closure.return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME | |
| 2700 | then | |
| 2701 | add(".locals init ({closure.return_type!.get_il_type()} '.default')"); | |
| 2702 | add("ldloc '.default'"); | |
| 2703 | fi | |
| 2704 | ||
| 2705 | add("// end IL body {closure}"); | |
| 2706 | ||
| 2707 | add("ret"); | |
| 2708 | ||
| 2709 | _context.enter_buffer(false); | |
| 2710 | ||
| 2711 | _context.reset_line_tracking(); | |
| 2712 | current_block.gen(_context); | |
| 2713 | ||
| 2714 | leave_block(); | |
| 2715 | ||
| 2716 | let body_assembler = _context.leave_buffer(); | |
| 2717 | ||
| 2718 | closure.il_body = body_assembler; | |
| 2719 | ||
| 2720 | super.visit(function); | |
| 2721 | ||
| 2722 | closure.unmap_type_arguments(); | |
| 2723 | si | |
| 2724 | ||
| 2725 | // Declare and fill the names bound by any destructured lambda | |
| 2726 | // parameter, before the body that reads them runs. The | |
| 2727 | // parameter itself is one physical argument holding the | |
| 2728 | // aggregate; its leaves are ordinary locals, so they need both | |
| 2729 | // a `.locals init` slot and the unpacking stores - the same | |
| 2730 | // pair `pre(Variables.VARIABLE)` and `visit(Variables.VARIABLE)` | |
| 2731 | // emit for a named function's destructured formal argument. | |
| 2732 | _gen_closure_destructure_prologue( | |
| 2733 | function: Trees.Expressions.FUNCTION, | |
| 2734 | closure: Semantic.Symbols.Closure | |
| 2735 | ) is | |
| 2736 | for a in function.arguments.expressions do | |
| 2737 | let argument = cast Trees.Expressions.VARIABLE?(a); | |
| 2738 | ||
| 2739 | if !argument? \/ !argument.left? then | |
| 2740 | continue; | |
| 2741 | fi | |
| 2742 | ||
| 2743 | let left = argument.left!; | |
| 2744 | ||
| 2745 | let buffer = System.Text.StringBuilder(); | |
| 2746 | ||
| 2747 | for name in left.names! do | |
| 2748 | let leaf = closure.find_direct(name.name); | |
| 2749 | ||
| 2750 | if leaf? then | |
| 2751 | leaf.gen_definition_header(buffer); | |
| 2752 | fi | |
| 2753 | od | |
| 2754 | ||
| 2755 | if buffer.length > 0 then | |
| 2756 | add(buffer.to_string()); | |
| 2757 | fi | |
| 2758 | ||
| 2759 | let group_symbol = closure.find_direct(argument.name.name); | |
| 2760 | ||
| 2761 | if group_symbol? then | |
| 2762 | gen_destructuring_initialize(left, group_symbol.load(argument.location, null, _symbol_loader)); | |
| 2763 | fi | |
| 2764 | od | |
| 2765 | si | |
| 2766 | ||
| 2767 | // Async closure: the lambda's $anon body becomes a builder- | |
| 2768 | // create / Start / return-Task launch sequence stashed in | |
| 2769 | // `closure.il_body`; the user body walks into a separate | |
| 2770 | // move_next_block; the SM class is queued for class-tail | |
| 2771 | // emission alongside named-function SMs. | |
| 2772 | _emit_async_closure( | |
| 2773 | function: Trees.Expressions.FUNCTION, | |
| 2774 | closure: Semantic.Symbols.Closure, | |
| 2775 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE | |
| 2776 | ) is | |
| 2777 | let frame = async_state_machine.frame; | |
| 2778 | ||
| 2779 | if !frame? then | |
| 2780 | _logger.error(function.location, "async function must return Tasks.TASK or Tasks.TASK[T]"); | |
| 2781 | return; | |
| 2782 | fi | |
| 2783 | ||
| 2784 | frame.declare(); | |
| 2785 | ||
| 2786 | // Body walk before launch IL — populates the frame's | |
| 2787 | // await labels / locals / awaiters before launch | |
| 2788 | // references any of them. | |
| 2789 | async_state_machine.install_body_emission_overrides(); | |
| 2790 | ||
| 2791 | enter_scope(function); | |
| 2792 | ||
| 2793 | let move_next_block = Values.BLOCK(); | |
| 2794 | ||
| 2795 | enter_block(move_next_block); | |
| 2796 | ||
| 2797 | add(".maxstack 64"); | |
| 2798 | ||
| 2799 | // Same V_state caching + per-region dispatch pattern as | |
| 2800 | // `_pre_async_function` (see its commentary). Awaits in | |
| 2801 | // the closure body register with the outer holder unless | |
| 2802 | // an inner `.try` pushes its own. | |
| 2803 | let state_field_ref = frame.state_field.get_il_reference(); | |
| 2804 | let outer_dispatch_holder = _open_state_machine_entry(state_field_ref, "'.async_state'"); | |
| 2805 | ||
| 2806 | let success_label = IR.LABEL(); | |
| 2807 | let end_label = IR.LABEL(); | |
| 2808 | ||
| 2809 | let prev_async_sm = _current_async_state_machine; | |
| 2810 | let prev_success = _current_async_success_label; | |
| 2811 | let prev_end = _current_async_end_label; | |
| 2812 | _current_async_state_machine = async_state_machine; | |
| 2813 | _current_async_success_label = success_label; | |
| 2814 | _current_async_end_label = end_label; | |
| 2815 | ||
| 2816 | function.body.walk(self); | |
| 2817 | ||
| 2818 | add("leave {success_label}"); | |
| 2819 | ||
| 2820 | _current_async_state_machine = prev_async_sm; | |
| 2821 | _current_async_success_label = prev_success; | |
| 2822 | _current_async_end_label = prev_end; | |
| 2823 | ||
| 2824 | _pop_async_dispatch_holder(outer_dispatch_holder); | |
| 2825 | ||
| 2826 | leave_block(); | |
| 2827 | leave_scope(function); | |
| 2828 | ||
| 2829 | async_state_machine.uninstall_body_emission_overrides(); | |
| 2830 | ||
| 2831 | // --- Build launch IL into closure.il_body --- | |
| 2832 | enter_block(); | |
| 2833 | add(".maxstack 64"); | |
| 2834 | ||
| 2835 | let construction_type_args = async_state_machine.get_construction_type_arguments(); | |
| 2836 | ||
| 2837 | let ctor_reference = _build_specialized_ctor_reference( | |
| 2838 | frame, | |
| 2839 | frame.constructor, | |
| 2840 | construction_type_args, | |
| 2841 | function.location, | |
| 2842 | "while building ctor reference for async closure" | |
| 2843 | ); | |
| 2844 | ||
| 2845 | let state_machine_type = | |
| 2846 | _specialized_frame_type(frame, construction_type_args, function.location) ?? frame.type!; | |
| 2847 | ||
| 2848 | // Ctor args: instance closures prepend ldarg.0 (the | |
| 2849 | // closure-frame instance, which holds captures), then | |
| 2850 | // each user-declared lambda parameter via ldarg '<name>'. | |
| 2851 | let ctor_args_block = Values.BLOCK(); | |
| 2852 | if frame.outer_self_field? then | |
| 2853 | ctor_args_block.add(IR.RAW("ldarg.0")); | |
| 2854 | fi | |
| 2855 | for arg_name in closure.argument_names do | |
| 2856 | ctor_args_block.add(IR.RAW("ldarg '{arg_name}'")); | |
| 2857 | od | |
| 2858 | ctor_args_block.close(); | |
| 2859 | ||
| 2860 | let task_type = closure.return_type!; | |
| 2861 | ||
| 2862 | let unspec_builder: Semantic.Types.Type? mut = null; | |
| 2863 | let unspec_task: Semantic.Types.Type? mut = null; | |
| 2864 | if !frame.is_void then | |
| 2865 | unspec_builder = _innate_symbol_lookup.get_unspecialized_async_task_method_builder_type(); | |
| 2866 | unspec_task = _innate_symbol_lookup.get_unspecialized_task_type(); | |
| 2867 | fi | |
| 2868 | ||
| 2869 | let launch = IR.Values.ASYNC_METHOD_LAUNCH( | |
| 2870 | task_type, | |
| 2871 | state_machine_type, | |
| 2872 | ctor_reference, | |
| 2873 | ctor_args_block, | |
| 2874 | frame.state_field, | |
| 2875 | frame.builder_field!, | |
| 2876 | unspec_builder, | |
| 2877 | unspec_task, | |
| 2878 | "'.sm'" | |
| 2879 | ); | |
| 2880 | ||
| 2881 | add(launch); | |
| 2882 | add("ret"); | |
| 2883 | ||
| 2884 | _context.enter_buffer(false); | |
| 2885 | _context.reset_line_tracking(); | |
| 2886 | current_block.gen(_context); | |
| 2887 | leave_block(); | |
| 2888 | closure.il_body = _context.leave_buffer(); | |
| 2889 | ||
| 2890 | _pending_async_state_machines.add( | |
| 2891 | PENDING_ASYNC_STATE_MACHINE( | |
| 2892 | async_state_machine, | |
| 2893 | move_next_block, | |
| 2894 | success_label, | |
| 2895 | end_label | |
| 2896 | ) | |
| 2897 | ); | |
| 2898 | si | |
| 2899 | ||
| 2900 | pre(statement: Expressions.STATEMENT) -> bool is | |
| 2901 | super.pre(statement); | |
| 2902 | ||
| 2903 | return false; | |
| 2904 | si | |
| 2905 | ||
| 2906 | visit(statement: Expressions.STATEMENT) is | |
| 2907 | si | |
| 2908 | ||
| 2909 | // `val ... lav` emits as the IL contained in block.value | |
| 2910 | // (the BLOCK Value set by compile-expressions). The shape | |
| 2911 | // mirrors how `if`/`case`-in-expression emit: | |
| 2912 | // | |
| 2913 | // <body statements' IL> | |
| 2914 | // <tail value's IL> ; pushes value (or empty for void) | |
| 2915 | // <return-targeted return inside body>: push value; br end | |
| 2916 | // end: ; value (or empty) on the stack | |
| 2917 | // | |
| 2918 | // The natural fall-through emits the tail's value at the end | |
| 2919 | // of the body and falls into the label. Return-targeted | |
| 2920 | // returns push their value and `br` to the same label. No | |
| 2921 | // CLR local or frame field carries the value across the | |
| 2922 | // join — so an async/yield suspend point inside the body | |
| 2923 | // composes with the existing state-machine rewriting | |
| 2924 | // without bespoke handling. | |
| 2925 | pre(block: Trees.Expressions.VAL_BLOCK) -> bool is | |
| 2926 | super.pre(block); | |
| 2927 | ||
| 2928 | if !block.value? \/ !isa Values.BLOCK(block.value) then | |
| 2929 | // No usable value from compile-expressions (a | |
| 2930 | // poisoned block, or a void-tolerant block whose | |
| 2931 | // BLOCK has been set with void type — both still | |
| 2932 | // need their body's IL to run for side effects). | |
| 2933 | // Fall back to a plain walk; returns inside will | |
| 2934 | // dispatch via val_block_target either way. | |
| 2935 | return false; | |
| 2936 | fi | |
| 2937 | ||
| 2938 | let value_block = cast Values.BLOCK(block.value); | |
| 2939 | ||
| 2940 | // Capture-vs-spill decision: in a state machine whose | |
| 2941 | // body contains a suspend, the val-block's result | |
| 2942 | // routes through a frame field instead of being | |
| 2943 | // captured inline in value_block. Body IL (including | |
| 2944 | // suspend emission and val-targeted-return stores) | |
| 2945 | // flows to outer current_block; value_block ends up | |
| 2946 | // holding just a `ldfld` so consumers see a clean load. | |
| 2947 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 2948 | self, | |
| 2949 | block.value, | |
| 2950 | _current_state_machine_frame(), | |
| 2951 | _contains_suspend(block) | |
| 2952 | ); | |
| 2953 | ||
| 2954 | spiller.enter(); | |
| 2955 | ||
| 2956 | try | |
| 2957 | let end_label = LABEL(); | |
| 2958 | block.end_label = end_label; | |
| 2959 | ||
| 2960 | let result_type = value_block.type; | |
| 2961 | let is_void = result_type.matches(_innate_symbol_lookup.get_void_type()); | |
| 2962 | ||
| 2963 | let frame_il = VAL_BLOCK_IL( | |
| 2964 | block, | |
| 2965 | end_label, | |
| 2966 | result_type, | |
| 2967 | is_void, | |
| 2968 | _loops.open_try_count, | |
| 2969 | value_block | |
| 2970 | ); | |
| 2971 | frame_il.spill_field = spiller.spill_field; | |
| 2972 | ||
| 2973 | // Eagerly allocate the capture-mode cross-try TEMP | |
| 2974 | // so its `.locals init` appears at the head of | |
| 2975 | // value_block's IL stream, before any inner `.try` | |
| 2976 | // the body might open. ilasm scopes locals | |
| 2977 | // declared inside `.try {}` to that block; lazy | |
| 2978 | // allocation mid-body would leave the post-body | |
| 2979 | // ldloc without a visible declaration. The TEMP is | |
| 2980 | // unused (and the join block un-emitted) when no | |
| 2981 | // val-targeted return crosses an inner try — set | |
| 2982 | // `cross_try_used` then drives the post-body | |
| 2983 | // emission. Spill mode doesn't need either: the | |
| 2984 | // frame field handles cross-try directly. | |
| 2985 | if !is_void /\ !spiller.is_spilling then | |
| 2986 | frame_il.cross_try_temp = IR.TEMP(value_block, "val_cross_try", value_block.type); | |
| 2987 | frame_il.cross_try_join_label = LABEL(); | |
| 2988 | fi | |
| 2989 | ||
| 2990 | _val_block_il_stack.add(frame_il); | |
| 2991 | ||
| 2992 | try | |
| 2993 | for s in block.body.statements do | |
| 2994 | self.enter_node(s); | |
| 2995 | try | |
| 2996 | s.walk(self); | |
| 2997 | finally | |
| 2998 | self.leave_node(s); | |
| 2999 | yrt | |
| 3000 | od | |
| 3001 | ||
| 3002 | // Natural fall-through. The tail statement is | |
| 3003 | // the last EXPRESSION in the body; in want_value | |
| 3004 | // mode visit(Statements.EXPRESSION) suppresses | |
| 3005 | // its own emission so the consuming context can | |
| 3006 | // emit the tail value. Val-block bypasses LIST, | |
| 3007 | // so we emit it here — the spiller picks spill- | |
| 3008 | // to-field or push-on-stack based on the mode | |
| 3009 | // chosen above. Val-targeted returns use the | |
| 3010 | // same emit_value path (see _gen_val_block_ | |
| 3011 | // targeted_return), so all paths converge at | |
| 3012 | // end_label with the value on the stack (or in | |
| 3013 | // the spill field) consistently. | |
| 3014 | if let block.body.last?, last.value? then | |
| 3015 | spiller.emit_value(value); | |
| 3016 | fi | |
| 3017 | finally | |
| 3018 | assert _val_block_il_stack.count > 0 else "val_block_il_stack underflow"; | |
| 3019 | _val_block_il_stack.remove_at(_val_block_il_stack.count - 1); | |
| 3020 | yrt | |
| 3021 | ||
| 3022 | // Capture-mode cross-try paths stashed their value | |
| 3023 | // into cross_try_temp and `leave`d to cross_try_ | |
| 3024 | // join_label. Skip over the join block on the | |
| 3025 | // natural path (tail value already on the stack) | |
| 3026 | // and emit it before end_label so the join paths | |
| 3027 | // ldloc the value back on and fall through. Only | |
| 3028 | // emit when the join was actually targeted — | |
| 3029 | // otherwise the unconditional ldloc would push an | |
| 3030 | // uninitialised value onto end_label's stack on | |
| 3031 | // divergent natural paths. | |
| 3032 | if frame_il.cross_try_used then | |
| 3033 | get_brancher_for_block().branch(end_label); | |
| 3034 | get_brancher_for_block().label(frame_il.cross_try_join_label!); | |
| 3035 | add(frame_il.cross_try_temp!.load()); | |
| 3036 | fi | |
| 3037 | ||
| 3038 | get_brancher_for_block().label(end_label); | |
| 3039 | finally | |
| 3040 | spiller.leave(); | |
| 3041 | yrt | |
| 3042 | ||
| 3043 | return true; | |
| 3044 | si | |
| 3045 | ||
| 3046 | visit(block: Trees.Expressions.VAL_BLOCK) is | |
| 3047 | si | |
| 3048 | ||
| 3049 | // Override CALL.walk's default args-first order so SPILLs in | |
| 3050 | // the receiver position emit before AWAIT_SUSPENDs in the | |
| 3051 | // argument position — needed for source-order side effects | |
| 3052 | // across instance-call receivers. | |
| 3053 | pre(call: Trees.Expressions.CALL) -> bool is | |
| 3054 | super.pre(call); | |
| 3055 | ||
| 3056 | call.function.walk(self); | |
| 3057 | call.arguments.walk(self); | |
| 3058 | ||
| 3059 | return true; | |
| 3060 | si | |
| 3061 | ||
| 3062 | visit(`await: Trees.Expressions.AWAIT) is | |
| 3063 | // Emit AWAIT_SUSPEND eagerly, stash the result into a | |
| 3064 | // fresh frame field (NOT a CLR local — CLR locals reset | |
| 3065 | // on every MoveNext re-entry), and rebind the wrapper to | |
| 3066 | // a `ldfld <field>` load. The CLR-local TEMP is a | |
| 3067 | // one-MoveNext bridge: we can't `ldarg.0` before | |
| 3068 | // AWAIT_SUSPEND (would push across its internal labels), | |
| 3069 | // so the suspend's result goes to a tmp first, then | |
| 3070 | // `ldarg.0 / ldloc / stfld`. | |
| 3071 | let await_wrapper = cast Values.WRAPPER?(`await.value); | |
| 3072 | ||
| 3073 | if !await_wrapper? then | |
| 3074 | return; | |
| 3075 | fi | |
| 3076 | ||
| 3077 | let casm = _current_async_state_machine; | |
| 3078 | ||
| 3079 | if !casm? \/ current_function != casm.function then | |
| 3080 | _logger.error(`await.location, "await outside async state-machine context"); | |
| 3081 | return; | |
| 3082 | fi | |
| 3083 | ||
| 3084 | let operand_value = `await.operand.value; | |
| 3085 | if !operand_value? then return; fi | |
| 3086 | let operand_type = operand_value.type; | |
| 3087 | if !operand_type? then return; fi | |
| 3088 | ||
| 3089 | let suspend = | |
| 3090 | _build_await_suspend(operand_value, operand_type); | |
| 3091 | ||
| 3092 | if !suspend? then | |
| 3093 | return; | |
| 3094 | fi | |
| 3095 | ||
| 3096 | let frame = casm.frame; | |
| 3097 | ||
| 3098 | assert frame? else "async state machine has no frame at await emission"; | |
| 3099 | ||
| 3100 | let result_type = await_wrapper.type; | |
| 3101 | ||
| 3102 | assert result_type? else "await wrapper has no result type at IL generation"; | |
| 3103 | ||
| 3104 | let suspend_type = suspend.type; | |
| 3105 | ||
| 3106 | if !suspend_type.is_void then | |
| 3107 | let result_temp = TEMP(current_block, "await_bridge", suspend); | |
| 3108 | let result_field = frame.declare_spill_field(result_type); | |
| 3109 | add(IR.RAW("ldarg.0")); | |
| 3110 | add(result_temp.load()); | |
| 3111 | add("stfld {result_field.get_il_reference()}"); | |
| 3112 | await_wrapper.value = | |
| 3113 | IR.Values.Load.INSTANCE_FIELD( | |
| 3114 | IR.Values.Load.REFERENCE_SELF(frame, frame.type), | |
| 3115 | result_field | |
| 3116 | ); | |
| 3117 | else | |
| 3118 | // void-async — emit the suspend IL eagerly. The wrapper | |
| 3119 | // already holds a void-typed DUMMY which surrounding | |
| 3120 | // emit paths must skip (the type check in | |
| 3121 | // visit(Statements.EXPRESSION) handles the only place | |
| 3122 | // this matters in practice — bare `await E;`). | |
| 3123 | add(suspend); | |
| 3124 | fi | |
| 3125 | si | |
| 3126 | ||
| 3127 | visit(spill: Trees.Expressions.SPILL) is | |
| 3128 | // Eagerly emit the operand's IL, then `stfld` to a fresh | |
| 3129 | // frame field. The wrapper's value becomes a | |
| 3130 | // `ldarg.0; ldfld <field>` load that surrounding IR | |
| 3131 | // consumes lazily. Used by the SPILL_AWAITS pass to | |
| 3132 | // persist intermediate values evaluated to the left of an | |
| 3133 | // `await` so they survive the suspend. | |
| 3134 | let spill_wrapper = cast Values.WRAPPER?(spill.value); | |
| 3135 | ||
| 3136 | if !spill_wrapper? then | |
| 3137 | return; | |
| 3138 | fi | |
| 3139 | ||
| 3140 | let casm = _current_async_state_machine; | |
| 3141 | ||
| 3142 | if !casm? \/ current_function != casm.function then | |
| 3143 | _logger.error(spill.location, "SPILL outside async state-machine context"); | |
| 3144 | return; | |
| 3145 | fi | |
| 3146 | ||
| 3147 | let operand_value = spill.operand.value; | |
| 3148 | if !operand_value? then return; fi | |
| 3149 | let spill_type = operand_value.type; | |
| 3150 | if !spill_type? then return; fi | |
| 3151 | ||
| 3152 | let frame = casm.frame; | |
| 3153 | ||
| 3154 | assert frame? else "async state machine has no frame at spill emission"; | |
| 3155 | ||
| 3156 | let spill_field = frame.declare_spill_field(spill_type); | |
| 3157 | ||
| 3158 | add(IR.RAW("ldarg.0")); | |
| 3159 | add(operand_value); | |
| 3160 | add("stfld {spill_field.get_il_reference()}"); | |
| 3161 | ||
| 3162 | spill_wrapper.value = | |
| 3163 | IR.Values.Load.INSTANCE_FIELD( | |
| 3164 | IR.Values.Load.REFERENCE_SELF(frame, frame.type), | |
| 3165 | spill_field | |
| 3166 | ); | |
| 3167 | si | |
| 3168 | ||
| 3169 | visit(`cast: Trees.Expressions.CAST) is | |
| 3170 | let cast_wrapper = cast Values.WRAPPER?(`cast.value); | |
| 3171 | ||
| 3172 | if !cast_wrapper? then return; fi | |
| 3173 | ||
| 3174 | let right_value = `cast.right.value; | |
| 3175 | if !right_value? then return; fi | |
| 3176 | let cast_type = cast_wrapper.type; | |
| 3177 | if !cast_type? then return; fi | |
| 3178 | ||
| 3179 | // Use the WRAPPER's type, not `cast.type_expression.type` | |
| 3180 | // — for a bare-variant cast on a generic union (a `cast V(x)` | |
| 3181 | // or the `cast` produced by `if let v: V = x` lowering), | |
| 3182 | // compile_expressions.visit(CAST) already substituted the | |
| 3183 | // receiver-specialized variant type, but the user's | |
| 3184 | // type_expression still reads as the open generic. | |
| 3185 | cast_wrapper.value = | |
| 3186 | _type_caster | |
| 3187 | .cast_value( | |
| 3188 | `cast.location, | |
| 3189 | right_value, | |
| 3190 | cast_type, | |
| 3191 | true | |
| 3192 | ); | |
| 3193 | si | |
| 3194 | ||
| 3195 | visit(assign: Statements.ASSIGNMENT) is | |
| 3196 | super.visit(assign); | |
| 3197 | ||
| 3198 | let v = assign.value; | |
| 3199 | if v? then | |
| 3200 | add(v); | |
| 3201 | fi | |
| 3202 | si | |
| 3203 | ||
| 3204 | visit(expression: Statements.EXPRESSION) is | |
| 3205 | super.visit(expression); | |
| 3206 | ||
| 3207 | try | |
| 3208 | if !expression.want_value /\ expression.expression.value? then | |
| 3209 | let value = expression.expression.value; | |
| 3210 | ||
| 3211 | let is_void = value.type? /\ value.type!.matches(_innate_symbol_lookup.get_void_type()); | |
| 3212 | ||
| 3213 | // Bare `await E;` with void result: visit(AWAIT) | |
| 3214 | // emitted the suspend eagerly; re-emitting the | |
| 3215 | // wrapper would call gen on its unreplaced DUMMY. | |
| 3216 | if !is_void \/ !isa Values.WRAPPER(value) then | |
| 3217 | add(value); | |
| 3218 | fi | |
| 3219 | ||
| 3220 | if !is_void then | |
| 3221 | add("pop"); | |
| 3222 | fi | |
| 3223 | fi | |
| 3224 | catch e: System.Exception | |
| 3225 | _logger.exception(expression.location, e, "caught exception generating IL for expression"); | |
| 3226 | yrt | |
| 3227 | si | |
| 3228 | ||
| 3229 | pre(variable: Variables.VARIABLE) -> bool is | |
| 3230 | super.pre(variable); | |
| 3231 | ||
| 3232 | if variable.want_dispose then | |
| 3233 | // variable will be declared outside the enclosing .try | |
| 3234 | // so don't declare it here | |
| 3235 | return false; | |
| 3236 | fi | |
| 3237 | ||
| 3238 | // Generator / async body locals: `state_machine_field` | |
| 3239 | // was wired up in compile-expressions (so closures | |
| 3240 | // freezing inside the body capture the right | |
| 3241 | // ldarg.0/ldfld IL). Skip emitting `.locals init` here; | |
| 3242 | // load/store routes through the frame field via | |
| 3243 | // state_machine_field. | |
| 3244 | let cf = current_function; | |
| 3245 | if cf? then | |
| 3246 | let sm_for_function = Semantic.Symbols.state_machine_for(cf); | |
| 3247 | ||
| 3248 | if sm_for_function? /\ sm_for_function.frame? then | |
| 3249 | return false; | |
| 3250 | fi | |
| 3251 | ||
| 3252 | let async_sm = Semantic.Symbols.async_state_machine_for(cf); | |
| 3253 | ||
| 3254 | if async_sm? /\ async_sm.frame? then | |
| 3255 | return false; | |
| 3256 | fi | |
| 3257 | fi | |
| 3258 | ||
| 3259 | let buffer = System.Text.StringBuilder(); | |
| 3260 | let globals_namespace: Semantic.Symbols.NAMESPACE? mut = null; | |
| 3261 | ||
| 3262 | for name in variable.names do | |
| 3263 | let symbol = find(name); | |
| 3264 | ||
| 3265 | if !symbol? then | |
| 3266 | continue; | |
| 3267 | fi | |
| 3268 | ||
| 3269 | let global_variable = cast Semantic.Symbols.GLOBAL_VARIABLE?(symbol); | |
| 3270 | ||
| 3271 | if global_variable? /\ !globals_namespace? then | |
| 3272 | globals_namespace = cast Semantic.Symbols.NAMESPACE?(global_variable.owner)!; | |
| 3273 | fi | |
| 3274 | ||
| 3275 | symbol.gen_definition_header(buffer); | |
| 3276 | ||
| 3277 | // For a field whose tree carries a reference-`?` | |
| 3278 | // position, attach NullableAttribute to the just- | |
| 3279 | // emitted .field directive so reflection recovers the | |
| 3280 | // annotation. ILASM associates a `.custom` line that | |
| 3281 | // immediately follows a `.field` with that field; the | |
| 3282 | // leading `\n` keeps the directive on its own line. | |
| 3283 | // Local variables and parameters don't reach here | |
| 3284 | // (the AST node is only used at namespace/class scope | |
| 3285 | // for field-shaped declarations). | |
| 3286 | let field_symbol = cast Semantic.Symbols.Field?(symbol); | |
| 3287 | ||
| 3288 | if field_symbol? then | |
| 3289 | let line = Semantic.DotNet.NULLABILITY.gen_attribute_line_for_type(field_symbol.type!); | |
| 3290 | ||
| 3291 | if line? then | |
| 3292 | buffer.append('\n'); | |
| 3293 | buffer.append(line); | |
| 3294 | fi | |
| 3295 | fi | |
| 3296 | od | |
| 3297 | ||
| 3298 | if buffer.length > 0 then | |
| 3299 | if _block_context.is_in_block then | |
| 3300 | current_block.add(buffer.to_string()); | |
| 3301 | else | |
| 3302 | if globals_namespace? then | |
| 3303 | gen_globals_class_open(globals_namespace); | |
| 3304 | fi | |
| 3305 | ||
| 3306 | println(buffer.to_string()); | |
| 3307 | ||
| 3308 | if globals_namespace? then | |
| 3309 | gen_globals_class_close(); | |
| 3310 | fi | |
| 3311 | fi | |
| 3312 | fi | |
| 3313 | return false; | |
| 3314 | si | |
| 3315 | ||
| 3316 | visit(v: Variables.VARIABLE) is | |
| 3317 | let initializer = v.initializer; | |
| 3318 | if initializer? then | |
| 3319 | let init_value = initializer.value; | |
| 3320 | if init_value? then | |
| 3321 | gen_destructuring_initialize(v.left, init_value); | |
| 3322 | return; | |
| 3323 | fi | |
| 3324 | fi | |
| 3325 | ||
| 3326 | // Destructured formal argument: no initializer expression - | |
| 3327 | // the "source" to destructure is the synthesised physical | |
| 3328 | // parameter declare-members attached to this VARIABLE. | |
| 3329 | if v.is_argument /\ !v.left.is_simple_name then | |
| 3330 | let group_symbol = symbol_for(v); | |
| 3331 | ||
| 3332 | if group_symbol? then | |
| 3333 | gen_destructuring_initialize(v.left, group_symbol.load(v.location, null, _symbol_loader)); | |
| 3334 | fi | |
| 3335 | ||
| 3336 | return; | |
| 3337 | fi | |
| 3338 | ||
| 3339 | // No initializer, but the local is captured by a closure | |
| 3340 | // and reassigned, so `mark-boxed-locals` promoted its slot | |
| 3341 | // to `Ghul.BOX[T]`. Allocate the empty box at declaration — | |
| 3342 | // `Variable.store` routes through `_store_boxed_local`, | |
| 3343 | // which emits `newobj Ghul.BOX[T]::.ctor()` when the value | |
| 3344 | // is null. Without this the slot stays null, then the | |
| 3345 | // closure frame's ctor stashes a null box reference and | |
| 3346 | // every later read or write NREs. | |
| 3347 | if v.left.is_simple_name then | |
| 3348 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 3349 | let name = v.left.name!; | |
| 3350 | let symbol = find(name); | |
| 3351 | ||
| 3352 | if | |
| 3353 | symbol? /\ | |
| 3354 | isa Semantic.Symbols.Variable(symbol) /\ | |
| 3355 | (cast Semantic.Symbols.Variable(symbol)).is_boxed | |
| 3356 | then | |
| 3357 | let store = symbol.store(v.left.location, null, null, _symbol_loader, true); | |
| 3358 | add(store); | |
| 3359 | return; | |
| 3360 | fi | |
| 3361 | fi | |
| 3362 | ||
| 3363 | // Loop-scoped `let x: T;` (no initializer) needs an | |
| 3364 | // explicit default store: .NET only initialises locals | |
| 3365 | // on method entry, so without re-zeroing each iteration | |
| 3366 | // the variable carries its previous iteration's value | |
| 3367 | // (issue #483). Outside a loop, the method-entry | |
| 3368 | // initialisation is correct and we leave the existing | |
| 3369 | // behaviour alone. Catch handlers receive their value | |
| 3370 | // from the runtime — skip the default-store there too. | |
| 3371 | if _loops.is_in_loop /\ !_in_catch_variable then | |
| 3372 | gen_destructuring_default(v.left); | |
| 3373 | fi | |
| 3374 | si | |
| 3375 | ||
| 3376 | // Drill a Type to its underlying Classy (peeling | |
| 3377 | // Symbols.GENERIC wrapping for specialised generics) — same | |
| 3378 | // shape as CONDITION_ANALYZER.get_classy_for_narrowing but | |
| 3379 | // inlined here so generate_il doesn't need a back-reference | |
| 3380 | // to compile-phase services. Returns null when `t` isn't | |
| 3381 | // a NAMED of a Classy. | |
| 3382 | _classy_of_type(t: Semantic.Types.Type) -> Semantic.Symbols.Classy? is | |
| 3383 | if !isa Semantic.Types.NAMED(t) then | |
| 3384 | return null; | |
| 3385 | fi | |
| 3386 | ||
| 3387 | let named = cast Semantic.Types.NAMED(t); | |
| 3388 | let symbol mut = named.symbol; | |
| 3389 | ||
| 3390 | if isa Semantic.Symbols.GENERIC(symbol) then | |
| 3391 | symbol = symbol.symbol; | |
| 3392 | fi | |
| 3393 | ||
| 3394 | return cast Semantic.Symbols.Classy?(symbol)!; | |
| 3395 | si | |
| 3396 | ||
| 3397 | // `if let` clauses' bound names are declared as CLR locals | |
| 3398 | // here — same as `pre(VARIABLE)` does for a plain let. The | |
| 3399 | // store IL itself is emitted later by | |
| 3400 | // `gen_destructuring_initialize` in the if-branch handler. | |
| 3401 | pre(rb: Statements.REFUTABLE_BINDING) -> bool is | |
| 3402 | let cf = current_function; | |
| 3403 | if cf? then | |
| 3404 | let sm_for_function = Semantic.Symbols.state_machine_for(cf); | |
| 3405 | ||
| 3406 | if sm_for_function? /\ sm_for_function.frame? then | |
| 3407 | return false; | |
| 3408 | fi | |
| 3409 | ||
| 3410 | let async_sm = Semantic.Symbols.async_state_machine_for(cf); | |
| 3411 | ||
| 3412 | if async_sm? /\ async_sm.frame? then | |
| 3413 | return false; | |
| 3414 | fi | |
| 3415 | fi | |
| 3416 | ||
| 3417 | let buffer = System.Text.StringBuilder(); | |
| 3418 | ||
| 3419 | for c in rb.clauses do | |
| 3420 | let pattern = c.pattern; | |
| 3421 | ||
| 3422 | for name in pattern.names! do | |
| 3423 | let symbol = find(name); | |
| 3424 | ||
| 3425 | if !symbol? then | |
| 3426 | continue; | |
| 3427 | fi | |
| 3428 | ||
| 3429 | symbol.gen_definition_header(buffer); | |
| 3430 | od | |
| 3431 | od | |
| 3432 | ||
| 3433 | if buffer.length > 0 /\ _block_context.is_in_block then | |
| 3434 | current_block.add(buffer.to_string()); | |
| 3435 | fi | |
| 3436 | ||
| 3437 | return false; | |
| 3438 | si | |
| 3439 | ||
| 3440 | visit(rb: Statements.REFUTABLE_BINDING) is | |
| 3441 | si | |
| 3442 | ||
| 3443 | gen_destructuring_default(left: Variables.VariableLeft) is | |
| 3444 | if left.is_simple_name then | |
| 3445 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 3446 | let name = left.name!; | |
| 3447 | let symbol = find(name); | |
| 3448 | ||
| 3449 | if !symbol? \/ symbol.is_argument then | |
| 3450 | return; | |
| 3451 | fi | |
| 3452 | let symbol_type = symbol.type; | |
| 3453 | if !symbol_type? then return; fi | |
| 3454 | ||
| 3455 | let default_value = IR.Values.DEFAULT(symbol_type); | |
| 3456 | ||
| 3457 | let store = symbol.store(left.location, null, default_value, _symbol_loader, true); | |
| 3458 | ||
| 3459 | add(store); | |
| 3460 | else | |
| 3461 | for e in left.elements! do | |
| 3462 | gen_destructuring_default(e); | |
| 3463 | od | |
| 3464 | fi | |
| 3465 | si | |
| 3466 | ||
| 3467 | // TODO use Pre/Visit instead | |
| 3468 | gen_destructuring_initialize(left: Variables.VariableLeft, value: Value) is | |
| 3469 | gen_destructuring_initialize(left, value, null); | |
| 3470 | si | |
| 3471 | ||
| 3472 | // Per-element narrowing: when `narrow_branch_label` is set | |
| 3473 | // (the `if let` path), an element carrying a `type_expression` | |
| 3474 | // emits `isinst T; brfalse next` before binding. The cast | |
| 3475 | // result is what gets stored / recursively destructured, so | |
| 3476 | // the bound slot is typed T. Plain `let` and `for` pass null | |
| 3477 | // for the label and just store the raw value (the declared | |
| 3478 | // element type is a compile-time assertion only, not a runtime | |
| 3479 | // test). | |
| 3480 | gen_destructuring_initialize( | |
| 3481 | left: Variables.VariableLeft, | |
| 3482 | value: Value, | |
| 3483 | narrow_branch_label: LABEL? | |
| 3484 | ) is | |
| 3485 | // Literal-leaf inside a destructure pattern — runtime | |
| 3486 | // value-equality test against the source position. Only | |
| 3487 | // valid in refutable contexts (`if let` / `case`-when); | |
| 3488 | // a plain `let` with a literal leaf is rejected during | |
| 3489 | // semantic analysis, so by the time we get here a literal | |
| 3490 | // leaf without a narrow_branch_label is a no-op (the | |
| 3491 | // emit-nothing path). | |
| 3492 | if isa Variables.LITERAL_VARIABLE_LEFT(left) then | |
| 3493 | if narrow_branch_label? then | |
| 3494 | let literal_leaf = cast Variables.LITERAL_VARIABLE_LEFT(left); | |
| 3495 | ||
| 3496 | literal_leaf.expression.walk(self); | |
| 3497 | ||
| 3498 | let leaf_value = literal_leaf.expression.value; | |
| 3499 | if leaf_value? then | |
| 3500 | get_brancher_for_block().branch( | |
| 3501 | BRANCH.NE, | |
| 3502 | value, | |
| 3503 | leaf_value, | |
| 3504 | narrow_branch_label, | |
| 3505 | "literal-leaf" | |
| 3506 | ); | |
| 3507 | fi | |
| 3508 | fi | |
| 3509 | ||
| 3510 | return; | |
| 3511 | fi | |
| 3512 | ||
| 3513 | let narrowed mut = value; | |
| 3514 | ||
| 3515 | let type_expression = left.type_expression; | |
| 3516 | if | |
| 3517 | type_expression? /\ | |
| 3518 | type_expression.type? /\ | |
| 3519 | narrow_branch_label? | |
| 3520 | then | |
| 3521 | let cast_value = IR.Values.CAST(type_expression.type!, value); | |
| 3522 | let temp = TEMP(current_block, "narrow", cast_value); | |
| 3523 | ||
| 3524 | get_brancher_for_block().branch(BRANCH.Z, temp.load(), narrow_branch_label, "narrow"); | |
| 3525 | ||
| 3526 | narrowed = temp.load(); | |
| 3527 | fi | |
| 3528 | ||
| 3529 | if left.is_simple_name then | |
| 3530 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 3531 | let name = left.name!; | |
| 3532 | let symbol = find(name); | |
| 3533 | ||
| 3534 | if !symbol? then | |
| 3535 | return; | |
| 3536 | fi | |
| 3537 | ||
| 3538 | let store = symbol.store(left.location, null, narrowed, _symbol_loader, true); | |
| 3539 | ||
| 3540 | add(store); | |
| 3541 | else | |
| 3542 | let from_type = narrowed.type; | |
| 3543 | ||
| 3544 | if !from_type? then return; fi | |
| 3545 | ||
| 3546 | // !is_simple_name => a destructure group, whose elements are non-null | |
| 3547 | let elements = left.elements!; | |
| 3548 | ||
| 3549 | let get_from = | |
| 3550 | if narrowed.is_lightweight_pure then | |
| 3551 | () => narrowed | |
| 3552 | else | |
| 3553 | let temp = TEMP(current_block, "destructure", narrowed); | |
| 3554 | () => temp.load(); | |
| 3555 | fi; | |
| 3556 | ||
| 3557 | let is_named_group = elements.count > 0 /\ elements[0].source_field_name?; | |
| 3558 | ||
| 3559 | let strategy = | |
| 3560 | if is_named_group then | |
| 3561 | let field_names = Collections.LIST[string?](); | |
| 3562 | for e in elements do | |
| 3563 | let sfn = e.source_field_name; | |
| 3564 | field_names.add(if sfn? then sfn.name else null fi); | |
| 3565 | od | |
| 3566 | DESTRUCTURE_RESOLVER.resolve_strategy_by_name(from_type, field_names); | |
| 3567 | else | |
| 3568 | DESTRUCTURE_RESOLVER.resolve_strategy(from_type, elements.count); | |
| 3569 | fi; | |
| 3570 | ||
| 3571 | if strategy.is_deconstruct then | |
| 3572 | // is_deconstruct ⇔ deconstruct_function? per its definition | |
| 3573 | let deconstruct = strategy.deconstruct_function!; | |
| 3574 | let arg_temps = Collections.LIST[IR.TEMP](); | |
| 3575 | let call_args = Collections.LIST[Value](); | |
| 3576 | ||
| 3577 | for i in 0..deconstruct.arguments.count do | |
| 3578 | let ref_type = deconstruct.arguments[i]; | |
| 3579 | let element_type = ref_type.get_element_type(); | |
| 3580 | assert element_type? else "deconstruct arg type has no element type"; | |
| 3581 | let arg_temp = IR.TEMP(current_block, "destructure_arg", i, element_type); | |
| 3582 | ||
| 3583 | arg_temps.add(arg_temp); | |
| 3584 | call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type)); | |
| 3585 | od | |
| 3586 | ||
| 3587 | let call_value = | |
| 3588 | deconstruct.call( | |
| 3589 | left.location, | |
| 3590 | get_from(), | |
| 3591 | call_args, | |
| 3592 | null, | |
| 3593 | _function_caller | |
| 3594 | ); | |
| 3595 | ||
| 3596 | add(call_value); | |
| 3597 | ||
| 3598 | for (i, e) in elements |> index() do | |
| 3599 | gen_destructuring_initialize(e, arg_temps[i].load(), narrow_branch_label); | |
| 3600 | od | |
| 3601 | else | |
| 3602 | let members = strategy.members; | |
| 3603 | ||
| 3604 | for (i, e) in elements |> index() do | |
| 3605 | let member = members[i]; | |
| 3606 | ||
| 3607 | if member? then | |
| 3608 | let member_value = member.load(LOCATION.internal, get_from(), _symbol_loader); | |
| 3609 | ||
| 3610 | gen_destructuring_initialize(e, member_value, narrow_branch_label); | |
| 3611 | fi | |
| 3612 | od | |
| 3613 | fi | |
| 3614 | fi | |
| 3615 | si | |
| 3616 | ||
| 3617 | visit(r: Statements.RETURN) is | |
| 3618 | super.visit(r); | |
| 3619 | ||
| 3620 | // Val-block-targeted return: push the value onto the | |
| 3621 | // evaluation stack and `br` to the block's end_label | |
| 3622 | // rather than emitting `ret`. The val-block frame is on | |
| 3623 | // the IL stack with the same AST node identity stamped | |
| 3624 | // on r.val_block_target. | |
| 3625 | if let target = r.val_block_target then | |
| 3626 | _gen_val_block_targeted_return(r, target); | |
| 3627 | return; | |
| 3628 | fi | |
| 3629 | ||
| 3630 | let function = current_function; | |
| 3631 | ||
| 3632 | // State-machine async return: stash the value into | |
| 3633 | // `_result` (value-async only) and `leave` to the | |
| 3634 | // success label so the MoveNext trailer fires | |
| 3635 | // `builder.SetResult` and returns. The standard | |
| 3636 | // `ret` path below would unwind MoveNext without | |
| 3637 | // signalling completion to the builder. | |
| 3638 | let casm = _current_async_state_machine; | |
| 3639 | let success_label = _current_async_success_label; | |
| 3640 | ||
| 3641 | if | |
| 3642 | casm? /\ | |
| 3643 | success_label? /\ | |
| 3644 | function == casm.function | |
| 3645 | then | |
| 3646 | let frame = casm.frame; | |
| 3647 | ||
| 3648 | assert frame? else "async state machine has no frame at return emission"; | |
| 3649 | ||
| 3650 | let expression = r.expression; | |
| 3651 | if expression? then | |
| 3652 | let expr_value = expression.value; | |
| 3653 | let result_field = frame.result_field; | |
| 3654 | let class_result_type = frame.class_result_type; | |
| 3655 | if expr_value? /\ result_field? /\ class_result_type? then | |
| 3656 | let boxed_value = | |
| 3657 | _boxer.box_if_needed( | |
| 3658 | expr_value, | |
| 3659 | class_result_type | |
| 3660 | ); | |
| 3661 | add("ldarg.0"); | |
| 3662 | add(boxed_value); | |
| 3663 | add("stfld {result_field.get_il_reference()}"); | |
| 3664 | fi | |
| 3665 | fi | |
| 3666 | ||
| 3667 | add("leave {success_label}"); | |
| 3668 | return; | |
| 3669 | fi | |
| 3670 | ||
| 3671 | let value: Value? mut = null; | |
| 3672 | let return_type = if function? then function.return_type else null fi; | |
| 3673 | ||
| 3674 | let expression = r.expression; | |
| 3675 | if | |
| 3676 | expression? /\ | |
| 3677 | return_type? | |
| 3678 | then | |
| 3679 | let expr_value = expression.value; | |
| 3680 | if expr_value? /\ expr_value.type? then | |
| 3681 | value = | |
| 3682 | _boxer.box_if_needed( | |
| 3683 | expr_value, | |
| 3684 | return_type | |
| 3685 | ); | |
| 3686 | fi | |
| 3687 | fi | |
| 3688 | ||
| 3689 | // Bare `return;` from a non-void function: push the | |
| 3690 | // default value of the return type. Matches the fall- | |
| 3691 | // off-end emission below and honours the warning at | |
| 3692 | // compile_bindings.pre_return ("return without value | |
| 3693 | // from non void function returns default value of type | |
| 3694 | // {T}"). Without this, `ret` runs with nothing on the | |
| 3695 | // stack and the JIT rejects the IL with | |
| 3696 | // `InvalidProgramException`. | |
| 3697 | if | |
| 3698 | !value? /\ | |
| 3699 | return_type? /\ | |
| 3700 | return_type.compare(_innate_symbol_lookup.get_void_type()) != Semantic.Types.MATCH.SAME | |
| 3701 | then | |
| 3702 | value = IR.Values.DEFAULT(return_type); | |
| 3703 | fi | |
| 3704 | ||
| 3705 | let current_try = _loops.get_current_try(); | |
| 3706 | ||
| 3707 | if current_try? then | |
| 3708 | if value? then | |
| 3709 | current_try.return_value!.store(value); | |
| 3710 | fi | |
| 3711 | ||
| 3712 | current_try.return_needed!.store(Literal.NUMBER("1", _innate_symbol_lookup.get_bool_type(), "i4")); | |
| 3713 | ||
| 3714 | let brancher = get_brancher_for_block(); | |
| 3715 | ||
| 3716 | if current_try.is_in_finally then | |
| 3717 | brancher.branch(current_try.middle); | |
| 3718 | else | |
| 3719 | brancher.leave(current_try.start); | |
| 3720 | fi | |
| 3721 | else | |
| 3722 | if value? then | |
| 3723 | add(value); | |
| 3724 | fi | |
| 3725 | ||
| 3726 | add("ret"); | |
| 3727 | fi | |
| 3728 | si | |
| 3729 | ||
| 3730 | // Emit IL for a `return E` whose target is a `val ... lav` | |
| 3731 | // block (innermost wins, recorded by compile-expressions on | |
| 3732 | // the RETURN AST node). The frame on `_val_block_il_stack` | |
| 3733 | // matches `target` by identity. Three shapes vary by where | |
| 3734 | // the value lives across the exit: | |
| 3735 | // | |
| 3736 | // Capture mode, no inner `.try`: push the value onto the | |
| 3737 | // evaluation stack and `br` to the frame's end_label — | |
| 3738 | // same shape `if` / `case`-in-expression branches use. | |
| 3739 | // | |
| 3740 | // Spill mode (val-block body contains an `await` / `yield`, | |
| 3741 | // so VAL_BLOCK.pre allocated a spill field on the state- | |
| 3742 | // machine frame): `stfld` the value into that field. The | |
| 3743 | // stack ends up empty so `leave` is well-formed even | |
| 3744 | // across an inner `.try`; the val-block's BLOCK contents | |
| 3745 | // become a `ldfld` of the same field, so all paths | |
| 3746 | // converge with the value in the field. | |
| 3747 | // | |
| 3748 | // Capture mode crossing an inner `.try`: a `br` across a | |
| 3749 | // protected region is invalid IL; `leave` requires an | |
| 3750 | // empty stack we can't deliver while carrying the value. | |
| 3751 | // Mirrors the function-return-from-try pattern — stash | |
| 3752 | // the value into the eagerly-allocated CLR-local TEMP on | |
| 3753 | // the frame, `leave` to a cross-try join label that sits | |
| 3754 | // outside any inner try, `ldloc` the TEMP at the join, | |
| 3755 | // and fall through into end_label with the value on the | |
| 3756 | // stack. Void val-blocks skip the TEMP and `leave` | |
| 3757 | // end_label directly. | |
| 3758 | _gen_val_block_targeted_return(r: Statements.RETURN, target: Trees.Expressions.VAL_BLOCK) is | |
| 3759 | let frame: VAL_BLOCK_IL? mut = null; | |
| 3760 | ||
| 3761 | let i mut = _val_block_il_stack.count - 1; | |
| 3762 | while i >= 0 do | |
| 3763 | let candidate = _val_block_il_stack[i]; | |
| 3764 | if candidate.block == target then | |
| 3765 | frame = candidate; | |
| 3766 | i = -1; | |
| 3767 | else | |
| 3768 | i = i - 1; | |
| 3769 | fi | |
| 3770 | od | |
| 3771 | ||
| 3772 | assert frame? else "val_block_target stamped on return has no matching IL frame"; | |
| 3773 | ||
| 3774 | // A `.try` opened INSIDE the val-block body is still | |
| 3775 | // open at this return — `_loops.open_try_count` | |
| 3776 | // exceeds the count we cached at val-block entry. | |
| 3777 | let crosses_try = _loops.open_try_count > frame.enclosing_try_count; | |
| 3778 | ||
| 3779 | // Resolve the value to deliver. Bare `return;` in a | |
| 3780 | // value-required val-block defers its diagnostic to | |
| 3781 | // visit_return (inferred-return lambdas need the body | |
| 3782 | // walk to settle first); emit the result type's | |
| 3783 | // default so the IL verifies — matches the bare- | |
| 3784 | // return-from-non-void-function path at visit(RETURN). | |
| 3785 | let value: IR.Values.Value? mut = null; | |
| 3786 | if !frame.is_void /\ frame.result_type? then | |
| 3787 | let result_type = frame.result_type; | |
| 3788 | ||
| 3789 | if let expression = r.expression then | |
| 3790 | if let v = expression.value then | |
| 3791 | value = _boxer.box_if_needed(v, result_type); | |
| 3792 | else | |
| 3793 | value = IR.Values.DEFAULT(result_type); | |
| 3794 | fi | |
| 3795 | else | |
| 3796 | value = IR.Values.DEFAULT(result_type); | |
| 3797 | fi | |
| 3798 | fi | |
| 3799 | ||
| 3800 | if let spill_field = frame.spill_field then | |
| 3801 | // Spill mode: stfld into the val-block's spill | |
| 3802 | // field. Stack is empty after stfld so `leave` | |
| 3803 | // and `br` are both valid; use `leave` only when | |
| 3804 | // crossing an inner try (where `br` would be | |
| 3805 | // invalid IL). | |
| 3806 | add(IR.RAW("ldarg.0")); | |
| 3807 | if let v = value then | |
| 3808 | add(v); | |
| 3809 | fi | |
| 3810 | add("stfld {spill_field.get_il_reference()}"); | |
| 3811 | ||
| 3812 | if crosses_try then | |
| 3813 | get_brancher_for_block().leave(frame.end_label); | |
| 3814 | else | |
| 3815 | get_brancher_for_block().branch(frame.end_label); | |
| 3816 | fi | |
| 3817 | elif !crosses_try then | |
| 3818 | // Capture mode no-cross-try: push the value and | |
| 3819 | // `br` end_label — the join shape if / case use. | |
| 3820 | if let v = value then | |
| 3821 | add(v); | |
| 3822 | fi | |
| 3823 | get_brancher_for_block().branch(frame.end_label); | |
| 3824 | elif !value? then | |
| 3825 | // Capture mode cross-try, void val-block: nothing | |
| 3826 | // to carry, `leave end_label` directly. (end_label | |
| 3827 | // sits outside any inner try because the frame | |
| 3828 | // cached enclosing_try_count.) | |
| 3829 | get_brancher_for_block().leave(frame.end_label); | |
| 3830 | else | |
| 3831 | // Capture mode cross-try, value val-block: stash | |
| 3832 | // value into the pre-allocated cross_try_temp, | |
| 3833 | // `leave` to cross_try_join_label. VAL_BLOCK.pre's | |
| 3834 | // post-body emission `ldloc`s the TEMP at the join | |
| 3835 | // and falls into end_label. | |
| 3836 | assert frame.cross_try_temp? else "capture-mode val-block missing cross-try TEMP"; | |
| 3837 | assert frame.cross_try_join_label? else "capture-mode val-block missing cross-try join label"; | |
| 3838 | ||
| 3839 | frame.cross_try_used = true; | |
| 3840 | frame.cross_try_temp!.store(value); | |
| 3841 | get_brancher_for_block().leave(frame.cross_try_join_label!); | |
| 3842 | fi | |
| 3843 | si | |
| 3844 | ||
| 3845 | // Generator yield IL emission: | |
| 3846 | // | |
| 3847 | // ldarg.0 | |
| 3848 | // <X's IR> // expression value (boxed-if-needed | |
| 3849 | // // against the element type) | |
| 3850 | // stfld _current | |
| 3851 | // ldarg.0 | |
| 3852 | // ldc.i4 N // freshly-allocated state number | |
| 3853 | // stfld _state | |
| 3854 | // ldc.i4.1 | |
| 3855 | // ret | |
| 3856 | // Label_N: // resumption point recorded so the | |
| 3857 | // // entry dispatch can jump here on | |
| 3858 | // // a subsequent MoveNext call | |
| 3859 | // | |
| 3860 | // The state number / label pair is recorded on the enclosing | |
| 3861 | // generator function's STATE_MACHINE so `_visit_generator_ | |
| 3862 | // function` can populate the entry-dispatch placeholder | |
| 3863 | // after the body walk completes. | |
| 3864 | visit(`yield: Statements.YIELD) is | |
| 3865 | super.visit(`yield); | |
| 3866 | ||
| 3867 | let function = current_function; | |
| 3868 | let state_machine = Semantic.Symbols.state_machine_for(function); | |
| 3869 | ||
| 3870 | if !state_machine? then | |
| 3871 | return; | |
| 3872 | fi | |
| 3873 | ||
| 3874 | let frame = state_machine.frame; | |
| 3875 | ||
| 3876 | assert frame? else "generator state machine has no frame at IL generation"; | |
| 3877 | ||
| 3878 | let state = state_machine.allocate_state(); | |
| 3879 | let resume = LABEL(); | |
| 3880 | ||
| 3881 | state_machine.record_label(state, resume); | |
| 3882 | ||
| 3883 | _register_resume_with_dispatch(state, resume); | |
| 3884 | ||
| 3885 | let current_field_ref = frame.current_field.get_il_reference(); | |
| 3886 | let state_field_ref = frame.state_field.get_il_reference(); | |
| 3887 | ||
| 3888 | // Store yielded value into _current. | |
| 3889 | add("ldarg.0"); | |
| 3890 | ||
| 3891 | if `yield.expression.value? then | |
| 3892 | let value = | |
| 3893 | _boxer.box_if_needed( | |
| 3894 | `yield.expression.value, | |
| 3895 | frame.element_type | |
| 3896 | ); | |
| 3897 | ||
| 3898 | add(value); | |
| 3899 | fi | |
| 3900 | ||
| 3901 | add("stfld {current_field_ref}"); | |
| 3902 | ||
| 3903 | // Stamp the resumption state — keep the CLR local | |
| 3904 | // `'.gen_state'` in sync so state-guarded finally | |
| 3905 | // bodies see the suspending value when our `leave` | |
| 3906 | // (below) fires them. | |
| 3907 | _emit_state_stamp("ldc.i4 {state}", "'.gen_state'", state_field_ref); | |
| 3908 | ||
| 3909 | // Suspend. From INSIDE an inner `.try`, `ret` is | |
| 3910 | // invalid IL; emit `leave` to a shared return-true label | |
| 3911 | // at MoveNext's outer scope. From OUTSIDE any inner try | |
| 3912 | // (only the outer dispatch holder open), `ret` is fine. | |
| 3913 | if _async_dispatch_stack.count > 1 then | |
| 3914 | if !_current_generator_yield_return_label? then | |
| 3915 | _current_generator_yield_return_label = LABEL(); | |
| 3916 | fi | |
| 3917 | add("leave {_current_generator_yield_return_label}"); | |
| 3918 | else | |
| 3919 | add("ldc.i4.1"); | |
| 3920 | add("ret"); | |
| 3921 | fi | |
| 3922 | ||
| 3923 | add("{resume}:"); | |
| 3924 | ||
| 3925 | // Cold-resume cleanup: clear state to -1 (and the local) | |
| 3926 | // so a subsequent `leave` (next yield's suspend, or the | |
| 3927 | // end-of-try leave on exhaustion) sees the "not | |
| 3928 | // suspending" sentinel. Without this, the finally body's | |
| 3929 | // state guard would still see the previous yield's state | |
| 3930 | // and skip cleanup forever. | |
| 3931 | _emit_state_stamp("ldc.i4.m1", "'.gen_state'", state_field_ref); | |
| 3932 | si | |
| 3933 | ||
| 3934 | // Emit `ldarg.0; <value_load_il>; dup; stloc <state_local_il>; | |
| 3935 | // stfld <state_field_ref>` — write a state value to BOTH the | |
| 3936 | // CLR local cached at MoveNext entry AND the state-machine | |
| 3937 | // frame's state field, so later `leave`s out of `.try` blocks | |
| 3938 | // fire finally bodies that see the correct sentinel via the | |
| 3939 | // local. `value_load_il` is "ldc.i4 N" for a suspend or | |
| 3940 | // "ldc.i4.m1" for the not-suspending sentinel. | |
| 3941 | _emit_state_stamp(value_load_il: string, state_local_il: string, state_field_ref: string) is | |
| 3942 | add("ldarg.0"); | |
| 3943 | add(value_load_il); | |
| 3944 | add("dup"); | |
| 3945 | add("stloc {state_local_il}"); | |
| 3946 | add("stfld {state_field_ref}"); | |
| 3947 | si | |
| 3948 | ||
| 3949 | visit(`throw: Statements.THROW) is | |
| 3950 | super.visit(`throw); | |
| 3951 | ||
| 3952 | add( | |
| 3953 | _boxer.box_if_value( | |
| 3954 | `throw.expression!.value! | |
| 3955 | ) | |
| 3956 | ); | |
| 3957 | ||
| 3958 | add("throw"); | |
| 3959 | si | |
| 3960 | ||
| 3961 | // Build an AWAIT_SUSPEND for `awaited`: allocates a state | |
| 3962 | // number + cold/hot resume labels + a fresh `$awaiter_N` | |
| 3963 | // field, and records the (state, awaiter, label) triple | |
| 3964 | // for entry-dispatch population. Returns null when the | |
| 3965 | // awaiter type cannot be resolved. | |
| 3966 | _build_await_suspend(awaited: Value, awaited_type: Semantic.Types.Type) -> IR.Values.AWAIT_SUSPEND? is | |
| 3967 | let async_sm = _current_async_state_machine!; | |
| 3968 | let frame = async_sm.frame; | |
| 3969 | ||
| 3970 | assert frame? else "async state machine has no frame at await suspend emission"; | |
| 3971 | ||
| 3972 | let element_type = | |
| 3973 | Semantic.Symbols.TYPE_ARGUMENT_EXTRACTOR.extract( | |
| 3974 | awaited_type, | |
| 3975 | _innate_symbol_lookup.get_unspecialized_task_type() | |
| 3976 | ); | |
| 3977 | ||
| 3978 | let awaiter_type_opt: Semantic.Types.Type? mut; | |
| 3979 | let result_type: Semantic.Types.Type mut; | |
| 3980 | ||
| 3981 | if element_type? then | |
| 3982 | awaiter_type_opt = _innate_symbol_lookup.get_task_awaiter_type(element_type); | |
| 3983 | result_type = element_type; | |
| 3984 | else | |
| 3985 | awaiter_type_opt = _innate_symbol_lookup.get_task_awaiter_void_type(); | |
| 3986 | result_type = _innate_symbol_lookup.get_void_type(); | |
| 3987 | fi | |
| 3988 | ||
| 3989 | if !awaiter_type_opt? then | |
| 3990 | return null; | |
| 3991 | fi | |
| 3992 | ||
| 3993 | let awaiter_type = awaiter_type_opt; | |
| 3994 | let state = async_sm.allocate_state(); | |
| 3995 | let cold_resume = IR.LABEL(); | |
| 3996 | let hot_resume = IR.LABEL(); | |
| 3997 | let awaiter_field = frame.declare_awaiter_field(awaiter_type); | |
| 3998 | ||
| 3999 | async_sm.record_label(state, awaiter_field, cold_resume); | |
| 4000 | ||
| 4001 | _register_resume_with_dispatch(state, cold_resume); | |
| 4002 | ||
| 4003 | let unspec_awaiter_type = | |
| 4004 | if element_type? then | |
| 4005 | // reaching async IL emit implies ghul-runtime resolved | |
| 4006 | _innate_symbol_lookup.get_unspecialized_task_awaiter_type()!; | |
| 4007 | else | |
| 4008 | awaiter_type; | |
| 4009 | fi; | |
| 4010 | ||
| 4011 | return IR.Values.AWAIT_SUSPEND( | |
| 4012 | result_type, | |
| 4013 | awaited, | |
| 4014 | awaited_type, | |
| 4015 | awaiter_type, | |
| 4016 | unspec_awaiter_type, | |
| 4017 | state, | |
| 4018 | cold_resume, | |
| 4019 | hot_resume, | |
| 4020 | _current_async_end_label!, | |
| 4021 | _specialized_frame_type(frame, async_sm.get_construction_type_arguments(), awaited.location) ?? frame.type!, | |
| 4022 | frame.state_field, | |
| 4023 | awaiter_field, | |
| 4024 | frame.builder_field! | |
| 4025 | ); | |
| 4026 | si | |
| 4027 | ||
| 4028 | // Emit the conditional-throw IL shared between statement-form | |
| 4029 | // `assert` and the expression-form `assert ... in expr`. Condition | |
| 4030 | // value is loaded by `brancher.branch`; on the failure path the | |
| 4031 | // message (or default condition-printout) is loaded, optionally | |
| 4032 | // wrapped in an AssertFailedException, and thrown. Falls through | |
| 4033 | // at the success label. | |
| 4034 | emit_assertion( | |
| 4035 | location: Source.LOCATION, | |
| 4036 | condition_value: IR.Values.Value, | |
| 4037 | message: Trees.Expressions.Expression?, | |
| 4038 | default_message_source: object | |
| 4039 | ) is | |
| 4040 | let brancher = get_brancher_for_block(); | |
| 4041 | ||
| 4042 | let end = LABEL(); | |
| 4043 | ||
| 4044 | brancher.branch(BRANCH.NZ, condition_value, end); | |
| 4045 | ||
| 4046 | let need_exception_wrapper mut = true; | |
| 4047 | ||
| 4048 | if message? then | |
| 4049 | let message_value = message.value; | |
| 4050 | if !_innate_symbol_lookup.get_exception_type().is_assignable_from(message_value!.type!) then | |
| 4051 | add( | |
| 4052 | Literal.STRING( | |
| 4053 | "{location}: ", | |
| 4054 | _innate_symbol_lookup.get_string_type() | |
| 4055 | ) | |
| 4056 | ); | |
| 4057 | ||
| 4058 | add(message_value!); | |
| 4059 | ||
| 4060 | add("call string string::Concat(object,object)"); | |
| 4061 | else | |
| 4062 | add(message_value!); | |
| 4063 | need_exception_wrapper = false; | |
| 4064 | fi | |
| 4065 | else | |
| 4066 | add( | |
| 4067 | Literal.STRING( | |
| 4068 | "{location}: {default_message_source}", | |
| 4069 | _innate_symbol_lookup.get_string_type() | |
| 4070 | ) | |
| 4071 | ); | |
| 4072 | fi | |
| 4073 | ||
| 4074 | if need_exception_wrapper then | |
| 4075 | add("newobj instance void class ['ghul-runtime']Ghul.AssertFailedException::.ctor(class [System.Runtime]System.String)"); | |
| 4076 | fi | |
| 4077 | ||
| 4078 | add("throw"); | |
| 4079 | ||
| 4080 | brancher.label(end); | |
| 4081 | si | |
| 4082 | ||
| 4083 | visit(`assert: Statements.ASSERT) is | |
| 4084 | super.visit(`assert); | |
| 4085 | ||
| 4086 | emit_assertion( | |
| 4087 | `assert.location, | |
| 4088 | `assert.expression.value!, | |
| 4089 | `assert.message, | |
| 4090 | `assert.expression | |
| 4091 | ); | |
| 4092 | si | |
| 4093 | ||
| 4094 | pre(assert_in: Expressions.ASSERT_IN) -> bool is | |
| 4095 | super.pre(assert_in); | |
| 4096 | ||
| 4097 | assert_in.condition.walk(self); | |
| 4098 | ||
| 4099 | if assert_in.message? then | |
| 4100 | assert_in.message.walk(self); | |
| 4101 | fi | |
| 4102 | ||
| 4103 | emit_assertion( | |
| 4104 | assert_in.location, | |
| 4105 | assert_in.condition.value!, | |
| 4106 | assert_in.message, | |
| 4107 | assert_in.condition | |
| 4108 | ); | |
| 4109 | ||
| 4110 | assert_in.expression.walk(self); | |
| 4111 | ||
| 4112 | return true; | |
| 4113 | si | |
| 4114 | ||
| 4115 | visit(assert_in: Expressions.ASSERT_IN) is | |
| 4116 | assert_in.compile_expressions_state.value = assert_in.expression.value; | |
| 4117 | si | |
| 4118 | ||
| 4119 | pre(list: Statements.LIST) -> bool is | |
| 4120 | super.pre(list); | |
| 4121 | return true; | |
| 4122 | si | |
| 4123 | ||
| 4124 | visit(list: Statements.LIST) is | |
| 4125 | // BLOCK-with-suspend spill. When the LIST is in a value- | |
| 4126 | // required position (list.value non-null) AND we're in | |
| 4127 | // a state machine AND any statement (outside nested | |
| 4128 | // function literals) contains an `await` or `yield`, the | |
| 4129 | // captured-IL pattern would trap the suspend's `leave` / | |
| 4130 | // `ret` inside list.value's BLOCK; when the consumer | |
| 4131 | // later gens that BLOCK, the suspend replays wrapped in | |
| 4132 | // the consumer's stack setup (receiver on stack at leave | |
| 4133 | // is invalid IL; state lost across yield's MoveNext re- | |
| 4134 | // entry breaks the next stfld). Spill the BLOCK's value | |
| 4135 | // to a frame field: body IL (including the suspend) | |
| 4136 | // flows to outer current_block in the same flat MoveNext- | |
| 4137 | // body stream as the dispatcher; the BLOCK is rewritten | |
| 4138 | // to a load of the spill field. The recursive case (val- | |
| 4139 | // block inside `if` in expression position, etc.) works | |
| 4140 | // because each composite's spill produces a clean load | |
| 4141 | // for its enclosing composite. | |
| 4142 | // | |
| 4143 | // The `want_dispose` (RAII) path keeps the existing .try | |
| 4144 | // wrapping — its body must live inside the protected | |
| 4145 | // region. | |
| 4146 | if _try_spill_block_with_suspend(list) then | |
| 4147 | return; | |
| 4148 | fi | |
| 4149 | ||
| 4150 | if list.value? then | |
| 4151 | enter_block(cast IR.Values.BLOCK?(list.value)!) | |
| 4152 | fi; | |
| 4153 | ||
| 4154 | let return_type = current_function!.return_type; | |
| 4155 | ||
| 4156 | let return_needed: TEMP? mut; | |
| 4157 | let return_value: TEMP? mut = _; | |
| 4158 | let label: LOOP_LABELS? mut = _; | |
| 4159 | ||
| 4160 | let outer_try: LOOP_LABELS? mut = _; | |
| 4161 | ||
| 4162 | let brancher = get_brancher_for_block(); | |
| 4163 | ||
| 4164 | // A `.try` inside a state-machine body needs its own | |
| 4165 | // dispatch holder (so awaits/yields inside register here, | |
| 4166 | // keeping the `beq cold_resume_N` and the target label | |
| 4167 | // in the same protected region) plus a state-guarded | |
| 4168 | // finally body (skip dispose / user-cleanup on suspend | |
| 4169 | // `leave`). Both works the same way for let-use and | |
| 4170 | // user-written try; the helpers below no-op outside a | |
| 4171 | // state machine. | |
| 4172 | let dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null; | |
| 4173 | ||
| 4174 | if list.want_dispose then | |
| 4175 | (outer_try, return_needed, return_value) = get_exception_handler_temps(); | |
| 4176 | ||
| 4177 | for v in list.variables_to_dispose do | |
| 4178 | let buffer = System.Text.StringBuilder(); | |
| 4179 | v.gen_definition_header(buffer); | |
| 4180 | ||
| 4181 | add(buffer.to_string()); | |
| 4182 | od | |
| 4183 | ||
| 4184 | label = _loops.enter_try(return_needed, return_value); | |
| 4185 | ||
| 4186 | add(".try {{ // RAII "); | |
| 4187 | ||
| 4188 | dispatch_holder = _maybe_push_dispatch_holder(); | |
| 4189 | fi | |
| 4190 | ||
| 4191 | for s in list.statements do | |
| 4192 | enter_node(s); | |
| 4193 | try | |
| 4194 | s.walk(self); | |
| 4195 | finally | |
| 4196 | leave_node(s); | |
| 4197 | yrt | |
| 4198 | od | |
| 4199 | ||
| 4200 | if list.value? then | |
| 4201 | if let list.last?, last.value? then | |
| 4202 | add(value); | |
| 4203 | fi | |
| 4204 | fi | |
| 4205 | ||
| 4206 | if list.want_dispose then | |
| 4207 | let try_label = label!; | |
| 4208 | ||
| 4209 | _maybe_pop_dispatch_holder(dispatch_holder); | |
| 4210 | ||
| 4211 | ensure_runtime_symbols_are_materialized(); | |
| 4212 | ||
| 4213 | try_label.is_in_finally = true; | |
| 4214 | ||
| 4215 | brancher.leave(try_label.start); | |
| 4216 | ||
| 4217 | add("}}"); | |
| 4218 | add("finally {{"); | |
| 4219 | ||
| 4220 | let skip_dispose_label = _open_finally_state_guard(); | |
| 4221 | ||
| 4222 | let to_dispose = list.variables_to_dispose; | |
| 4223 | ||
| 4224 | let i mut = to_dispose.count - 1; | |
| 4225 | ||
| 4226 | while i >= 0 do | |
| 4227 | let v = to_dispose[i]; | |
| 4228 | ||
| 4229 | let skip_label: LABEL? mut = null; | |
| 4230 | ||
| 4231 | if !v.type!.is_value_type then | |
| 4232 | skip_label = LABEL(); | |
| 4233 | ||
| 4234 | brancher.branch(BRANCH.Z, _symbol_loader.load_local_variable(list.location, v), skip_label); | |
| 4235 | fi | |
| 4236 | ||
| 4237 | let variable_value = v.load(list.location, null, _symbol_loader); // _symbol_loader.load_local_variable(list.location, v); | |
| 4238 | let dispose_call = _dispose.call(list.location, variable_value, System.Array.empty`[Value](), null, _function_caller); | |
| 4239 | ||
| 4240 | add(dispose_call); | |
| 4241 | ||
| 4242 | if skip_label? then | |
| 4243 | brancher.label(skip_label); | |
| 4244 | fi | |
| 4245 | ||
| 4246 | i = i - 1; | |
| 4247 | od | |
| 4248 | ||
| 4249 | _close_finally_state_guard(skip_dispose_label); | |
| 4250 | ||
| 4251 | brancher.label(try_label.middle); | |
| 4252 | ||
| 4253 | add("endfinally"); | |
| 4254 | add("}}"); | |
| 4255 | ||
| 4256 | _loops.leave_loop(); | |
| 4257 | ||
| 4258 | gen_exception_handler_exit(outer_try, try_label, return_value); | |
| 4259 | fi | |
| 4260 | ||
| 4261 | if list.value? then | |
| 4262 | leave_block(); | |
| 4263 | fi | |
| 4264 | si | |
| 4265 | ||
| 4266 | // Emit the scrutinee evaluation, optional isinst cast, the | |
| 4267 | // presence test, and the branch-to-fail for one `if let` | |
| 4268 | // clause. Returns the temp holding the (possibly cast) | |
| 4269 | // scrutinee value so the caller can bind the clause's | |
| 4270 | // pattern from it; returns null when there is nothing to | |
| 4271 | // bind (a missing scrutinee value, defensive). | |
| 4272 | _emit_if_let_clause_test( | |
| 4273 | c: Statements.REFUTABLE_BINDING_CLAUSE, | |
| 4274 | fail: LABEL, | |
| 4275 | is_first: bool, | |
| 4276 | brancher: BLOCK_BRANCHER | |
| 4277 | ) -> TEMP? is | |
| 4278 | c.scrutinee.walk(self); | |
| 4279 | ||
| 4280 | if !c.scrutinee.value? then | |
| 4281 | return null; | |
| 4282 | fi | |
| 4283 | ||
| 4284 | let scrutinee_value = c.scrutinee.value; | |
| 4285 | let typed_value: Value mut = scrutinee_value; | |
| 4286 | ||
| 4287 | if let c.narrow_type_expression?, narrow_type_expression.type? then | |
| 4288 | // The scrutinee's value type already reflects the | |
| 4289 | // then-edge narrowing applied in compile_conditionals | |
| 4290 | // — when the scrutinee is an identifier and the | |
| 4291 | // narrow target is a variant, narrowing specialises | |
| 4292 | // (e.g. `CONS` -> `CONS[int]`) by mutating the | |
| 4293 | // symbol type via NARROWING_FLOW. Use that | |
| 4294 | // specialised form as the isinst target so the | |
| 4295 | // runtime test references the closed-generic | |
| 4296 | // variant. Detect "specialised form" by checking the | |
| 4297 | // value-type and the written narrow type share the | |
| 4298 | // same classy (i.e. the value type is the narrow | |
| 4299 | // target with type args bound). Otherwise — e.g. the | |
| 4300 | // scrutinee is a member access typed wider than the | |
| 4301 | // narrow target — use the written narrow type | |
| 4302 | // itself, so the isinst tests the target. | |
| 4303 | let written_narrow = type; | |
| 4304 | let scrutinee_classy = _classy_of_type(scrutinee_value.type!); | |
| 4305 | let written_classy = _classy_of_type(written_narrow); | |
| 4306 | ||
| 4307 | let narrow_target = | |
| 4308 | if | |
| 4309 | scrutinee_value.type? /\ | |
| 4310 | !scrutinee_value.type!.is_inferred /\ | |
| 4311 | !scrutinee_value.type!.is_error /\ | |
| 4312 | scrutinee_classy? /\ | |
| 4313 | written_classy? /\ | |
| 4314 | scrutinee_classy == written_classy | |
| 4315 | then | |
| 4316 | scrutinee_value.type; | |
| 4317 | else | |
| 4318 | written_narrow; | |
| 4319 | fi; | |
| 4320 | ||
| 4321 | typed_value = | |
| 4322 | _type_caster.cast_value( | |
| 4323 | c.location, | |
| 4324 | scrutinee_value, | |
| 4325 | narrow_target!, | |
| 4326 | true | |
| 4327 | ); | |
| 4328 | fi | |
| 4329 | ||
| 4330 | let temp = TEMP(current_block, "if_let", typed_value); | |
| 4331 | ||
| 4332 | let init_type = typed_value.type!; | |
| 4333 | ||
| 4334 | // The presence test: a reference type is absent when | |
| 4335 | // null; an option-shaped value type is absent when its | |
| 4336 | // `has_value` member is false. | |
| 4337 | let presence: Value? mut = null; | |
| 4338 | ||
| 4339 | if init_type.is_value_type then | |
| 4340 | let has_value_member = init_type.find_member("has_value"); | |
| 4341 | ||
| 4342 | if has_value_member? then | |
| 4343 | presence = has_value_member.load(LOCATION.internal, temp.load(), _symbol_loader); | |
| 4344 | fi | |
| 4345 | else | |
| 4346 | presence = temp.load(); | |
| 4347 | fi | |
| 4348 | ||
| 4349 | if presence? then | |
| 4350 | if is_first then | |
| 4351 | brancher.branch(BRANCH.Z, presence, fail, "if"); | |
| 4352 | else | |
| 4353 | brancher.branch(BRANCH.Z, presence, fail, "elif"); | |
| 4354 | fi | |
| 4355 | fi | |
| 4356 | ||
| 4357 | return temp; | |
| 4358 | si | |
| 4359 | ||
| 4360 | // Bind one clause's pattern from the temp produced by the | |
| 4361 | // clause's test. Reference bindings take the value as-is; | |
| 4362 | // option-shaped value-type bindings unwrap `.value` first. | |
| 4363 | _emit_if_let_clause_bind( | |
| 4364 | c: Statements.REFUTABLE_BINDING_CLAUSE, | |
| 4365 | temp: TEMP, | |
| 4366 | fail: LABEL | |
| 4367 | ) is | |
| 4368 | let init_type = temp.load().type; | |
| 4369 | ||
| 4370 | if init_type.is_value_type then | |
| 4371 | let value_member = init_type.find_member("value"); | |
| 4372 | ||
| 4373 | if value_member? then | |
| 4374 | gen_destructuring_initialize( | |
| 4375 | c.pattern, | |
| 4376 | value_member.load(LOCATION.internal, temp.load(), _symbol_loader), | |
| 4377 | fail | |
| 4378 | ); | |
| 4379 | fi | |
| 4380 | else | |
| 4381 | gen_destructuring_initialize(c.pattern, temp.load(), fail); | |
| 4382 | fi | |
| 4383 | si | |
| 4384 | ||
| 4385 | pre(i: Statements.IF) -> bool is | |
| 4386 | super.pre(i); | |
| 4387 | ||
| 4388 | return true; | |
| 4389 | si | |
| 4390 | ||
| 4391 | visit(`if: Statements.IF) is | |
| 4392 | let is_first mut = true; | |
| 4393 | let seen_else mut = false; | |
| 4394 | ||
| 4395 | let end = LABEL(); | |
| 4396 | ||
| 4397 | // Spill the IF's value through a frame field when the | |
| 4398 | // body contains a suspend; otherwise capture as usual. | |
| 4399 | // See COMPOSITE_VALUE_SPILLER. Recursive: inner | |
| 4400 | // composites (val-blocks, nested IFs) handle their own | |
| 4401 | // suspends the same way. `if.value` is non-null exactly | |
| 4402 | // when `if.want_value` is true (visit_if sets it only | |
| 4403 | // in that case), so the spiller takes the value | |
| 4404 | // directly. | |
| 4405 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 4406 | self, | |
| 4407 | `if.value, | |
| 4408 | _current_state_machine_frame(), | |
| 4409 | _contains_suspend(`if) | |
| 4410 | ); | |
| 4411 | ||
| 4412 | spiller.enter(); | |
| 4413 | ||
| 4414 | let brancher = get_brancher_for_block(); | |
| 4415 | ||
| 4416 | for b in `if.branches do | |
| 4417 | let next = LABEL(); | |
| 4418 | let first_clause_temp: TEMP? mut = null; | |
| 4419 | ||
| 4420 | if let b.condition? /\ !b.binding? then | |
| 4421 | // Enter the arm's own scope before walking the | |
| 4422 | // condition: a `val ... lav` condition can declare | |
| 4423 | // its own locals (`if val let v = e; v lav then`), | |
| 4424 | // and those live in the arm's scope, not the | |
| 4425 | // enclosing one. | |
| 4426 | self.pre(b); | |
| 4427 | ||
| 4428 | condition.walk(self); | |
| 4429 | ||
| 4430 | if is_first then | |
| 4431 | brancher.branch(BRANCH.Z, condition.value!, next, "if"); | |
| 4432 | else | |
| 4433 | brancher.branch(BRANCH.Z, condition.value!, next, "elif"); | |
| 4434 | fi | |
| 4435 | elif let b.binding? then | |
| 4436 | // `if let` branch: for each clause, evaluate its | |
| 4437 | // scrutinee into a temp, emit an isinst for the | |
| 4438 | // type test when the clause carries `: V`, branch | |
| 4439 | // past this arm if the result has no value, then | |
| 4440 | // bind the clause's pattern. Clauses chain left | |
| 4441 | // to right; every clause's test and optional | |
| 4442 | // guard must succeed for the then-arm to fire. | |
| 4443 | // | |
| 4444 | // The first clause's scrutinee evaluates BEFORE | |
| 4445 | // the if-arm scope opens, so its own bindings are | |
| 4446 | // not visible to it (matching the single-clause | |
| 4447 | // shape). Subsequent clauses' scrutinees evaluate | |
| 4448 | // INSIDE the scope after earlier clauses' bindings | |
| 4449 | // are stored — letting `if let x = a, y = x.b` | |
| 4450 | // chain values through. | |
| 4451 | first_clause_temp = _emit_if_let_clause_test(binding.clauses[0], next, is_first, brancher); | |
| 4452 | ||
| 4453 | // enter if block scope: | |
| 4454 | self.pre(b); | |
| 4455 | else | |
| 4456 | seen_else = true; | |
| 4457 | ||
| 4458 | // enter if block scope: | |
| 4459 | self.pre(b); | |
| 4460 | fi | |
| 4461 | ||
| 4462 | if let b.binding? then | |
| 4463 | // Declare CLR locals for every clause's pattern | |
| 4464 | // up front — pre(REFUTABLE_BINDING) iterates all | |
| 4465 | // clauses. | |
| 4466 | self.pre(binding); | |
| 4467 | ||
| 4468 | if first_clause_temp? then | |
| 4469 | _emit_if_let_clause_bind(binding.clauses[0], first_clause_temp, next); | |
| 4470 | fi | |
| 4471 | ||
| 4472 | if binding.clauses[0].guard? then | |
| 4473 | binding.clauses[0].guard!.walk(self); | |
| 4474 | brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, next, "if-let-guard"); | |
| 4475 | fi | |
| 4476 | ||
| 4477 | let i mut = 1; | |
| 4478 | while i < binding.clauses.count do | |
| 4479 | let clause = binding.clauses[i]; | |
| 4480 | ||
| 4481 | let temp = _emit_if_let_clause_test(clause, next, false, brancher); | |
| 4482 | ||
| 4483 | if temp? then | |
| 4484 | _emit_if_let_clause_bind(clause, temp, next); | |
| 4485 | fi | |
| 4486 | ||
| 4487 | if clause.guard? then | |
| 4488 | clause.guard.walk(self); | |
| 4489 | brancher.branch(BRANCH.Z, clause.guard!.value!, next, "if-let-guard"); | |
| 4490 | fi | |
| 4491 | ||
| 4492 | i = i + 1; | |
| 4493 | od | |
| 4494 | fi | |
| 4495 | ||
| 4496 | b.body.walk(self); | |
| 4497 | ||
| 4498 | // no-op? | |
| 4499 | b.accept(self); | |
| 4500 | ||
| 4501 | if `if.want_value then | |
| 4502 | if let b.body.value? then | |
| 4503 | // Coerce the branch value to the IF's result type | |
| 4504 | // so a value-type branch flowing into a wider slot | |
| 4505 | // is boxed (T -> object) or wrapped (T -> T?); the | |
| 4506 | // else branch already lowers null -> default at | |
| 4507 | // compile time, but a present-value branch pushes | |
| 4508 | // its bare value here and must match the join type. | |
| 4509 | let block_type = `if.value?.type; | |
| 4510 | let coerced = | |
| 4511 | if block_type? /\ !block_type.is_void /\ !block_type.is_error then | |
| 4512 | _boxer.box_if_needed(value, block_type) | |
| 4513 | else | |
| 4514 | value | |
| 4515 | fi; | |
| 4516 | ||
| 4517 | spiller.emit_value(coerced); | |
| 4518 | ||
| 4519 | // Discard the capture-mode value when the IF is | |
| 4520 | // in expected-void position but a branch supplied | |
| 4521 | // a non-void value. Spill mode stfld'd into a | |
| 4522 | // typed field so no extra pop is needed. | |
| 4523 | if let | |
| 4524 | `if.expected_type? /\ | |
| 4525 | !spiller.is_spilling /\ | |
| 4526 | expected_type.is_void /\ | |
| 4527 | !value.type!.is_void | |
| 4528 | then | |
| 4529 | add("pop"); | |
| 4530 | fi | |
| 4531 | fi | |
| 4532 | fi | |
| 4533 | ||
| 4534 | brancher.branch(end); | |
| 4535 | brancher.label(next); | |
| 4536 | ||
| 4537 | is_first = false; | |
| 4538 | od | |
| 4539 | ||
| 4540 | brancher.label(end); | |
| 4541 | ||
| 4542 | super.visit(`if); | |
| 4543 | ||
| 4544 | spiller.leave(); | |
| 4545 | si | |
| 4546 | ||
| 4547 | pre(`case: Statements.CASE) -> bool is | |
| 4548 | super.pre(`case); | |
| 4549 | ||
| 4550 | return true; | |
| 4551 | si | |
| 4552 | ||
| 4553 | visit(`case: Statements.CASE) is | |
| 4554 | // Spill the CASE's value through a frame field when the | |
| 4555 | // body contains a suspend; otherwise capture as usual. | |
| 4556 | // See COMPOSITE_VALUE_SPILLER. `case.value` is non-null | |
| 4557 | // exactly when `case.want_value` is true. | |
| 4558 | let spiller = COMPOSITE_VALUE_SPILLER( | |
| 4559 | self, | |
| 4560 | `case.value, | |
| 4561 | _current_state_machine_frame(), | |
| 4562 | _contains_suspend(`case) | |
| 4563 | ); | |
| 4564 | ||
| 4565 | spiller.enter(); | |
| 4566 | ||
| 4567 | let temp = TEMP(current_block, "case", `case.expression.value!); | |
| 4568 | ||
| 4569 | let brancher = get_brancher_for_block(); | |
| 4570 | ||
| 4571 | let labels = _loops.enter_loop(); | |
| 4572 | ||
| 4573 | let has_else mut = false; | |
| 4574 | for m in `case.matches do | |
| 4575 | if !m.expressions? /\ !m.pattern? then | |
| 4576 | has_else = true; | |
| 4577 | fi | |
| 4578 | od | |
| 4579 | ||
| 4580 | for m in `case.matches do | |
| 4581 | let next = LABEL(); | |
| 4582 | ||
| 4583 | if m.expressions? then | |
| 4584 | if m.expressions.expressions.count == 1 then | |
| 4585 | m.expressions.expressions[0].walk(self); | |
| 4586 | brancher.branch(BRANCH.NE, temp.load(), m.expressions!.expressions[0].value!, next); | |
| 4587 | elif m.expressions.expressions.count > 1 then | |
| 4588 | let match = LABEL(); | |
| 4589 | ||
| 4590 | for e in m.expressions! do | |
| 4591 | e.walk(self); | |
| 4592 | ||
| 4593 | brancher.branch(BRANCH.EQ, temp.load(), e.value!, match); | |
| 4594 | od | |
| 4595 | ||
| 4596 | brancher.branch(next); | |
| 4597 | ||
| 4598 | brancher.label(match); | |
| 4599 | fi | |
| 4600 | ||
| 4601 | m.walk(self); | |
| 4602 | elif m.pattern? then | |
| 4603 | // Pattern arm: enter the arm's scope, narrow the | |
| 4604 | // case temp against the pattern's type (ascription | |
| 4605 | // → isinst; option-shape value type → has_value), | |
| 4606 | // branch to `next` on failure, then bind. Mirrors | |
| 4607 | // `if let`'s presence-test + bind shape, applied | |
| 4608 | // against the case's scrutinee TEMP instead of a | |
| 4609 | // per-branch initializer TEMP. | |
| 4610 | self.pre(m); | |
| 4611 | ||
| 4612 | let pattern = m.pattern!; | |
| 4613 | let init_type: Semantic.Types.Type mut = `case.expression.value!.type!; | |
| 4614 | let arm_source: IR.Values.Value mut = temp.load(); | |
| 4615 | ||
| 4616 | if pattern.is_explicit_type /\ pattern.type_expression.type? then | |
| 4617 | init_type = pattern.type_expression.type!; | |
| 4618 | arm_source = IR.Values.CAST(init_type, arm_source); | |
| 4619 | fi | |
| 4620 | ||
| 4621 | let arm_temp = TEMP(current_block, "case_let", arm_source); | |
| 4622 | ||
| 4623 | let presence: IR.Values.Value? mut = null; | |
| 4624 | if init_type.is_value_type then | |
| 4625 | let has_value_member = init_type.find_member("has_value"); | |
| 4626 | if has_value_member? then | |
| 4627 | presence = has_value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader); | |
| 4628 | fi | |
| 4629 | else | |
| 4630 | presence = arm_temp.load(); | |
| 4631 | fi | |
| 4632 | ||
| 4633 | if presence? then | |
| 4634 | brancher.branch(BRANCH.Z, presence, next, "case-let"); | |
| 4635 | fi | |
| 4636 | ||
| 4637 | self.pre(pattern); | |
| 4638 | ||
| 4639 | if init_type.is_value_type then | |
| 4640 | let value_member = init_type.find_member("value"); | |
| 4641 | ||
| 4642 | if value_member? then | |
| 4643 | // Option-shape value type: bind from the | |
| 4644 | // unwrapped `.value` member. | |
| 4645 | gen_destructuring_initialize( | |
| 4646 | pattern.left, | |
| 4647 | value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader), | |
| 4648 | next | |
| 4649 | ); | |
| 4650 | else | |
| 4651 | // Non-option value type (e.g. a plain tuple): | |
| 4652 | // destructure directly against the value. | |
| 4653 | gen_destructuring_initialize(pattern.left, arm_temp.load(), next); | |
| 4654 | fi | |
| 4655 | else | |
| 4656 | gen_destructuring_initialize(pattern.left, arm_temp.load(), next); | |
| 4657 | fi | |
| 4658 | ||
| 4659 | if m.guard? then | |
| 4660 | m.guard.walk(self); | |
| 4661 | brancher.branch(BRANCH.Z, m.guard!.value!, next, "case-when-guard"); | |
| 4662 | fi | |
| 4663 | ||
| 4664 | self.visit(m); | |
| 4665 | else | |
| 4666 | m.walk(self); | |
| 4667 | fi | |
| 4668 | ||
| 4669 | if `case.want_value then | |
| 4670 | if let m.statements.value? then | |
| 4671 | // Coerce the arm value to the CASE's result type | |
| 4672 | // so a value-type arm flowing into a wider slot is | |
| 4673 | // boxed (T -> object) or wrapped (T -> T?), matching | |
| 4674 | // the join type the other arms deliver. | |
| 4675 | let block_type = `case.value?.type; | |
| 4676 | let coerced = | |
| 4677 | if block_type? /\ !block_type.is_void /\ !block_type.is_error then | |
| 4678 | _boxer.box_if_needed(value, block_type) | |
| 4679 | else | |
| 4680 | value | |
| 4681 | fi; | |
| 4682 | ||
| 4683 | spiller.emit_value(coerced); | |
| 4684 | ||
| 4685 | if let | |
| 4686 | `case.expected_type? /\ | |
| 4687 | !spiller.is_spilling /\ | |
| 4688 | expected_type.is_void /\ | |
| 4689 | !value.type!.is_void | |
| 4690 | then | |
| 4691 | add("pop"); | |
| 4692 | fi | |
| 4693 | fi | |
| 4694 | fi | |
| 4695 | ||
| 4696 | brancher.branch(_loops.get_current_loop()!.end); | |
| 4697 | ||
| 4698 | brancher.label(next); | |
| 4699 | od | |
| 4700 | ||
| 4701 | // An exhaustive case-expression without an `else` arm | |
| 4702 | // falls through here with no value pushed when none of | |
| 4703 | // the arms matched — impossible at runtime, but the IL | |
| 4704 | // verifier can't see that. Emit a throw so the stack | |
| 4705 | // invariant holds for the verifier; the throw is dead. | |
| 4706 | if `case.want_value /\ `case.is_exhaustive /\ !has_else then | |
| 4707 | add( | |
| 4708 | Literal.STRING( | |
| 4709 | "case is not exhaustive at runtime", | |
| 4710 | _innate_symbol_lookup.get_string_type() | |
| 4711 | ) | |
| 4712 | ); | |
| 4713 | add("newobj instance void class ['ghul-runtime']Ghul.AssertFailedException::.ctor(class [System.Runtime]System.String)"); | |
| 4714 | add("throw"); | |
| 4715 | elif `case.want_value /\ `case.requires_default_fallthrough /\ !has_else then | |
| 4716 | // Open-domain case-expression with a defaultable | |
| 4717 | // expected type and no `else` arm — push default(T) | |
| 4718 | // for the no-match path. CASE_EXHAUSTIVENESS_CHECKER | |
| 4719 | // has emitted the `case-needs-else` warning. | |
| 4720 | add(IR.Values.DEFAULT(`case.expected_type!)); | |
| 4721 | fi | |
| 4722 | ||
| 4723 | brancher.label(_loops.get_current_loop()!.end); | |
| 4724 | ||
| 4725 | _loops.leave_loop(); | |
| 4726 | ||
| 4727 | super.visit(`case); | |
| 4728 | ||
| 4729 | spiller.leave(); | |
| 4730 | si | |
| 4731 | ||
| 4732 | pre(match: Statements.CASE_MATCH) -> bool is | |
| 4733 | super.pre(match); | |
| 4734 | ||
| 4735 | return true; | |
| 4736 | si | |
| 4737 | ||
| 4738 | visit(match: Statements.CASE_MATCH) is | |
| 4739 | match.statements.accept(self); | |
| 4740 | ||
| 4741 | super.visit(match); | |
| 4742 | si | |
| 4743 | ||
| 4744 | pre(`try: Statements.TRY) -> bool is | |
| 4745 | super.pre(`try); | |
| 4746 | ||
| 4747 | return true; | |
| 4748 | si | |
| 4749 | ||
| 4750 | get_exception_handler_temps() -> (outer_try: LOOP_LABELS?, return_needed: TEMP, return_value: TEMP?) is | |
| 4751 | let return_type = current_function!.return_type; | |
| 4752 | ||
| 4753 | let return_needed: TEMP mut; | |
| 4754 | let return_value: TEMP? mut = _; | |
| 4755 | ||
| 4756 | let outer_try = _loops.get_current_try(); | |
| 4757 | ||
| 4758 | if outer_try? then | |
| 4759 | // get_current_try returns only try-form labels, whose init sets return_needed | |
| 4760 | return_needed = outer_try.return_needed!; | |
| 4761 | return_value = outer_try.return_value; | |
| 4762 | else | |
| 4763 | return_needed = TEMP(current_block, "need_return", 1, _innate_symbol_lookup.get_bool_type()); | |
| 4764 | ||
| 4765 | if return_type? /\ !return_type.matches(_innate_symbol_lookup.get_void_type()) then | |
| 4766 | return_value = TEMP(current_block, "return", return_type); | |
| 4767 | fi | |
| 4768 | fi | |
| 4769 | ||
| 4770 | return (outer_try, return_needed, return_value); | |
| 4771 | si | |
| 4772 | ||
| 4773 | gen_exception_handler_exit(outer_try: LOOP_LABELS?, label: LOOP_LABELS, return_value: TEMP?) is | |
| 4774 | let brancher = get_brancher_for_block(); | |
| 4775 | ||
| 4776 | brancher.label(label.start); | |
| 4777 | brancher.branch(BRANCH.Z, label.return_needed!.load(), label.end); | |
| 4778 | ||
| 4779 | if outer_try? then | |
| 4780 | if return_value? then | |
| 4781 | assert outer_try.return_value? else "outer try has no return value temporary"; | |
| 4782 | outer_try.return_value.store(return_value.load()); | |
| 4783 | fi | |
| 4784 | ||
| 4785 | if outer_try.is_in_finally then | |
| 4786 | outer_try.return_needed!.store(Literal.NUMBER("1", _innate_symbol_lookup.get_bool_type(), "i4")); | |
| 4787 | brancher.branch(outer_try.middle); | |
| 4788 | else | |
| 4789 | brancher.leave(outer_try.start); | |
| 4790 | fi | |
| 4791 | elif | |
| 4792 | _current_async_state_machine? /\ | |
| 4793 | _current_async_success_label? /\ | |
| 4794 | current_function == _current_async_state_machine!.function | |
| 4795 | then | |
| 4796 | // State-machine async: a return inside a try block | |
| 4797 | // accumulates the value in `return_value` and the | |
| 4798 | // "need return" flag. The post-try emit reaches here | |
| 4799 | // — for state-machine async we stash the value to | |
| 4800 | // `_result` and `leave success_label` (NOT `ret`), | |
| 4801 | // so the trailer's `builder.SetResult` fires. | |
| 4802 | let frame = _current_async_state_machine!.frame; | |
| 4803 | ||
| 4804 | assert frame? else "async state machine has no frame at exception handler exit emission"; | |
| 4805 | ||
| 4806 | let result_field = frame.result_field; | |
| 4807 | ||
| 4808 | if return_value? /\ result_field? then | |
| 4809 | add("ldarg.0"); | |
| 4810 | add(return_value.load()); | |
| 4811 | add("stfld {result_field.get_il_reference()}"); | |
| 4812 | fi | |
| 4813 | ||
| 4814 | add("leave {_current_async_success_label!}"); | |
| 4815 | else | |
| 4816 | if return_value? then | |
| 4817 | add(return_value.load()); | |
| 4818 | fi | |
| 4819 | ||
| 4820 | add("ret"); | |
| 4821 | fi | |
| 4822 | ||
| 4823 | brancher.label(label.end); | |
| 4824 | si | |
| 4825 | ||
| 4826 | visit(`try: Statements.TRY) is | |
| 4827 | if | |
| 4828 | `try.catches.count == 0 /\ | |
| 4829 | !`try.`finally? | |
| 4830 | then | |
| 4831 | `try.body.walk(self); | |
| 4832 | ||
| 4833 | super.visit(`try); | |
| 4834 | ||
| 4835 | return; | |
| 4836 | fi | |
| 4837 | ||
| 4838 | let brancher = get_brancher_for_block(); | |
| 4839 | ||
| 4840 | let return_type = current_function!.return_type; | |
| 4841 | ||
| 4842 | let (outer_try, return_needed, return_value) = get_exception_handler_temps(); | |
| 4843 | ||
| 4844 | let label = _loops.enter_try(return_needed, return_value); | |
| 4845 | ||
| 4846 | let need_double_try = `try.catches.count > 0 /\ `try.`finally?; | |
| 4847 | ||
| 4848 | let outer_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null; | |
| 4849 | let body_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null; | |
| 4850 | ||
| 4851 | if need_double_try then | |
| 4852 | add(".try {{"); | |
| 4853 | outer_dispatch_holder = _maybe_push_dispatch_holder(); | |
| 4854 | fi | |
| 4855 | ||
| 4856 | add(".try {{"); | |
| 4857 | ||
| 4858 | body_dispatch_holder = _maybe_push_dispatch_holder(); | |
| 4859 | ||
| 4860 | `try.body.walk(self); | |
| 4861 | ||
| 4862 | _maybe_pop_dispatch_holder(body_dispatch_holder); | |
| 4863 | ||
| 4864 | brancher.leave(label.start); | |
| 4865 | ||
| 4866 | add("}}"); | |
| 4867 | ||
| 4868 | for c in `try.catches do | |
| 4869 | c.walk(self); | |
| 4870 | od | |
| 4871 | ||
| 4872 | if need_double_try then | |
| 4873 | _maybe_pop_dispatch_holder(outer_dispatch_holder); | |
| 4874 | add("}}"); | |
| 4875 | fi | |
| 4876 | ||
| 4877 | let `finally = `try.`finally; | |
| 4878 | ||
| 4879 | if `finally? then | |
| 4880 | label.is_in_finally = true; | |
| 4881 | ||
| 4882 | add("finally {{"); | |
| 4883 | ||
| 4884 | let skip_finally_label = _open_finally_state_guard(); | |
| 4885 | ||
| 4886 | `finally.walk(self); | |
| 4887 | ||
| 4888 | _close_finally_state_guard(skip_finally_label); | |
| 4889 | ||
| 4890 | brancher.label(label.middle); | |
| 4891 | ||
| 4892 | add("endfinally"); | |
| 4893 | add("}}"); | |
| 4894 | fi | |
| 4895 | ||
| 4896 | _loops.leave_loop(); | |
| 4897 | ||
| 4898 | // let outer_try = _loops.get_current_try(); | |
| 4899 | gen_exception_handler_exit(outer_try, label, return_value); | |
| 4900 | ||
| 4901 | super.visit(`try); | |
| 4902 | si | |
| 4903 | ||
| 4904 | pre(`catch: Statements.CATCH) -> bool is | |
| 4905 | super.pre(`catch); | |
| 4906 | ||
| 4907 | return true; | |
| 4908 | si | |
| 4909 | ||
| 4910 | visit(`catch: Statements.CATCH) is | |
| 4911 | let variable = `catch.variable!; | |
| 4912 | ||
| 4913 | add("catch {variable.type_expression.type!.get_il_type()} {{"); | |
| 4914 | ||
| 4915 | let brancher = get_brancher_for_block(); | |
| 4916 | ||
| 4917 | // Catch handler enters with the exception on the stack | |
| 4918 | // and immediately stores it into the catch variable. The | |
| 4919 | // loop-scoped default-store added for issue #483 must be | |
| 4920 | // suppressed here — it would clobber the caught value. | |
| 4921 | _in_catch_variable = true; | |
| 4922 | variable.walk(self); | |
| 4923 | _in_catch_variable = false; | |
| 4924 | ||
| 4925 | let symbol = find(variable.name!); | |
| 4926 | ||
| 4927 | // State-machine-resident catch variable (async function): | |
| 4928 | // the exception's on the stack but `stloc <name>` would | |
| 4929 | // reference an undeclared CLR local since pre(VARIABLE) | |
| 4930 | // skipped the `.locals init` directive. Route through | |
| 4931 | // the frame field via a temp instead. | |
| 4932 | if let v: Semantic.Symbols.Variable = symbol, sm_field = v.state_machine_field then | |
| 4933 | let field_ref = sm_field.get_il_reference(); | |
| 4934 | let exception_il = variable.type_expression.type!.get_il_type(); | |
| 4935 | add(".locals init ({exception_il} '.catch_exc')"); | |
| 4936 | add("stloc '.catch_exc'"); | |
| 4937 | add("ldarg.0"); | |
| 4938 | add("ldloc '.catch_exc'"); | |
| 4939 | add("stfld {field_ref}"); | |
| 4940 | else | |
| 4941 | add("stloc {symbol!.get_il_reference()}"); | |
| 4942 | fi | |
| 4943 | ||
| 4944 | `catch.body.walk(self); | |
| 4945 | ||
| 4946 | brancher.leave(_loops.get_current_try()!.start); | |
| 4947 | ||
| 4948 | add("}}"); | |
| 4949 | ||
| 4950 | super.visit(`catch); | |
| 4951 | si | |
| 4952 | ||
| 4953 | pre(`do: Statements.DO) -> bool is | |
| 4954 | super.pre(`do); | |
| 4955 | ||
| 4956 | return true; | |
| 4957 | si | |
| 4958 | ||
| 4959 | visit(`do: Statements.DO) is | |
| 4960 | let brancher = get_brancher_for_block(); | |
| 4961 | ||
| 4962 | let loop = _loops.enter_loop(); | |
| 4963 | ||
| 4964 | brancher.label(loop.start); | |
| 4965 | ||
| 4966 | if let `do.binding? then | |
| 4967 | // `while let` clauses: emit the same per-clause | |
| 4968 | // test / bind sequence as `if let`, but branch | |
| 4969 | // failures to `loop.end` (loop exit) instead of an | |
| 4970 | // arm-`next` label. Each iteration re-evaluates | |
| 4971 | // every clause's scrutinee and re-binds — the | |
| 4972 | // bound names are freshly stored on each pass. | |
| 4973 | let first = _emit_if_let_clause_test(binding.clauses[0], loop.end, true, brancher); | |
| 4974 | ||
| 4975 | self.pre(binding); | |
| 4976 | ||
| 4977 | if first? then | |
| 4978 | _emit_if_let_clause_bind(binding.clauses[0], first, loop.end); | |
| 4979 | fi | |
| 4980 | ||
| 4981 | if binding.clauses[0].guard? then | |
| 4982 | binding.clauses[0].guard!.walk(self); | |
| 4983 | brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, loop.end, "while-let-guard"); | |
| 4984 | fi | |
| 4985 | ||
| 4986 | let i mut = 1; | |
| 4987 | while i < binding.clauses.count do | |
| 4988 | let clause = binding.clauses[i]; | |
| 4989 | ||
| 4990 | let temp = _emit_if_let_clause_test(clause, loop.end, false, brancher); | |
| 4991 | ||
| 4992 | if temp? then | |
| 4993 | _emit_if_let_clause_bind(clause, temp, loop.end); | |
| 4994 | fi | |
| 4995 | ||
| 4996 | if clause.guard? then | |
| 4997 | clause.guard.walk(self); | |
| 4998 | brancher.branch(BRANCH.Z, clause.guard!.value!, loop.end, "while-let-guard"); | |
| 4999 | fi | |
| 5000 | ||
| 5001 | i = i + 1; | |
| 5002 | od | |
| 5003 | elif `do.condition? then | |
| 5004 | `do.condition.walk(self); | |
| 5005 | ||
| 5006 | brancher.branch(BRANCH.Z, `do.condition!.value!, loop.end, "while"); | |
| 5007 | fi | |
| 5008 | ||
| 5009 | `do.body.walk(self); | |
| 5010 | ||
| 5011 | brancher.branch(loop.start); | |
| 5012 | ||
| 5013 | brancher.label(loop.end); | |
| 5014 | ||
| 5015 | super.visit(`do); | |
| 5016 | ||
| 5017 | _loops.leave_loop(); | |
| 5018 | si | |
| 5019 | ||
| 5020 | pre(`for: Statements.FOR) -> bool is | |
| 5021 | super.pre(`for); | |
| 5022 | ||
| 5023 | return true; | |
| 5024 | si | |
| 5025 | ||
| 5026 | visit(`for: Statements.FOR) is | |
| 5027 | // A recognised, fusible Pipe[T] chain is lowered to one | |
| 5028 | // inline loop over the pinned base, applying the map/filter | |
| 5029 | // stages per element, instead of iterating the built pipe | |
| 5030 | // objects. Not in a state machine, whose iterator must live | |
| 5031 | // on the frame to survive yield/await re-entry - those fall | |
| 5032 | // through to the normal loop below. | |
| 5033 | if | |
| 5034 | `for.fusion? /\ | |
| 5035 | !Semantic.Symbols.state_machine_for(current_function)? /\ | |
| 5036 | !Semantic.Symbols.async_state_machine_for(current_function)? | |
| 5037 | then | |
| 5038 | if `for.fusion!.needs_guard then | |
| 5039 | _visit_guarded_for(`for); | |
| 5040 | else | |
| 5041 | _visit_fused_for(`for); | |
| 5042 | fi | |
| 5043 | ||
| 5044 | return; | |
| 5045 | fi | |
| 5046 | ||
| 5047 | let brancher = get_brancher_for_block(); | |
| 5048 | let loop = _loops.enter_loop(); | |
| 5049 | ||
| 5050 | let expression = `for.expression!; | |
| 5051 | let variable = `for.variable!; | |
| 5052 | let body = `for.body!; | |
| 5053 | ||
| 5054 | let iterator: Value mut; | |
| 5055 | ||
| 5056 | if `for.read_iterator? then | |
| 5057 | iterator = `for.read_iterator.call(expression.location, expression.value!, Collections.LIST[Value](0), null, _function_caller); | |
| 5058 | else | |
| 5059 | iterator = expression.value!; | |
| 5060 | fi | |
| 5061 | ||
| 5062 | variable.walk(self); | |
| 5063 | ||
| 5064 | // Generator / async FOR: the iterator value has to | |
| 5065 | // survive every yield / await in the body. Allocate a | |
| 5066 | // frame field and route both load and store through it; | |
| 5067 | // the regular CLR-local TEMP path would reset on every | |
| 5068 | // MoveNext re-entry. | |
| 5069 | let sm_for_function = Semantic.Symbols.state_machine_for(current_function); | |
| 5070 | let async_sm_for_function: Semantic.Symbols.ASYNC_STATE_MACHINE? mut = null; | |
| 5071 | if !sm_for_function? then | |
| 5072 | async_sm_for_function = Semantic.Symbols.async_state_machine_for(current_function); | |
| 5073 | fi | |
| 5074 | ||
| 5075 | let iterator_field: Semantic.Symbols.Field? mut = null; | |
| 5076 | let iterator_frame: Semantic.Symbols.Classy? mut = null; | |
| 5077 | ||
| 5078 | if sm_for_function? /\ sm_for_function.frame? /\ iterator.type? then | |
| 5079 | let frame = sm_for_function.frame!; | |
| 5080 | iterator_field = frame.declare_anonymous_field("for_iterator", iterator.type!); | |
| 5081 | iterator_frame = frame; | |
| 5082 | elif async_sm_for_function? /\ async_sm_for_function.frame? /\ iterator.type? then | |
| 5083 | let frame = async_sm_for_function.frame!; | |
| 5084 | iterator_field = frame.declare_anonymous_field("for_iterator", iterator.type!); | |
| 5085 | iterator_frame = frame; | |
| 5086 | fi | |
| 5087 | ||
| 5088 | if iterator_field? then | |
| 5089 | add("ldarg.0"); | |
| 5090 | add(iterator); | |
| 5091 | add("stfld {iterator_field.get_il_reference()}"); | |
| 5092 | fi | |
| 5093 | ||
| 5094 | let temp: TEMP? mut = null; | |
| 5095 | ||
| 5096 | if !iterator_field? then | |
| 5097 | temp = TEMP(current_block, "iterator", iterator); | |
| 5098 | fi | |
| 5099 | ||
| 5100 | brancher.label(loop.start); | |
| 5101 | ||
| 5102 | let load_iter_a = | |
| 5103 | if iterator_field? then | |
| 5104 | cast Value(IR.Values.Load.INSTANCE_FIELD( | |
| 5105 | IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type), | |
| 5106 | iterator_field | |
| 5107 | )); | |
| 5108 | else | |
| 5109 | cast Value(temp!.load()); | |
| 5110 | fi; | |
| 5111 | ||
| 5112 | let has_next = `for.move_next!.call(expression.location, load_iter_a, Collections.LIST[Value](0), null, _function_caller); | |
| 5113 | ||
| 5114 | brancher.branch(BRANCH.Z, has_next, loop.end); | |
| 5115 | ||
| 5116 | let load_iter_b = | |
| 5117 | if iterator_field? then | |
| 5118 | cast Value(IR.Values.Load.INSTANCE_FIELD( | |
| 5119 | IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type), | |
| 5120 | iterator_field | |
| 5121 | )); | |
| 5122 | else | |
| 5123 | cast Value(temp!.load()); | |
| 5124 | fi; | |
| 5125 | ||
| 5126 | let current = `for.read_current!.call(expression.location, load_iter_b, Collections.LIST[Value](0), null, _function_caller); | |
| 5127 | ||
| 5128 | gen_destructuring_initialize(variable.left, current); | |
| 5129 | ||
| 5130 | // not to generate code for the expression itself, but to capture IL for any anonymous function bodies: | |
| 5131 | // TODO check that if expressions within the expression are emitted correctly | |
| 5132 | expression.walk(self); | |
| 5133 | ||
| 5134 | body.walk(self); | |
| 5135 | ||
| 5136 | brancher.branch(loop.start); | |
| 5137 | ||
| 5138 | brancher.label(loop.end); | |
| 5139 | ||
| 5140 | super.visit(`for); | |
| 5141 | ||
| 5142 | _loops.leave_loop(); | |
| 5143 | si | |
| 5144 | ||
| 5145 | // Declare the per-stage locals a fused chain needs before its loop, | |
| 5146 | // filling `stage_delegates` / `stage_counters` parallel to the | |
| 5147 | // stages (null where a slot does not apply): an inlined stage's | |
| 5148 | // parameter local, a delegate stage's hoisted function value, an | |
| 5149 | // index stage's running counter initialised to its start. Shared by | |
| 5150 | // the pure and guarded fused-loop emitters. | |
| 5151 | _setup_fused_stages( | |
| 5152 | fusion: Syntax.Process.PIPE_FUSION, | |
| 5153 | stage_delegates: Collections.LIST[TEMP?], | |
| 5154 | stage_counters: Collections.LIST[TEMP?] | |
| 5155 | ) is | |
| 5156 | for stage in fusion.stages_outermost_first do | |
| 5157 | if stage.is_countdown then | |
| 5158 | // take/skip: evaluate the count once into a running | |
| 5159 | // counter the loop decrements per pulled element. | |
| 5160 | let counter = TEMP(current_block, "fused_countdown", _innate_symbol_lookup.get_int_type()); | |
| 5161 | counter.store(stage.argument!.value!); | |
| 5162 | stage_delegates.add(null); | |
| 5163 | stage_counters.add(counter); | |
| 5164 | elif stage.is_inlined then | |
| 5165 | add(".locals init ({stage.param_local!.type!.get_il_type()} {stage.param_local!.il_name})"); | |
| 5166 | stage_delegates.add(null); | |
| 5167 | stage_counters.add(null); | |
| 5168 | elif stage.is_index then | |
| 5169 | let counter = TEMP(current_block, "fused_index", _innate_symbol_lookup.get_int_type()); | |
| 5170 | counter.store(Literal.NUMBER("{stage.index_start}", _innate_symbol_lookup.get_int_type(), "i4")); | |
| 5171 | stage_delegates.add(null); | |
| 5172 | stage_counters.add(counter); | |
| 5173 | else | |
| 5174 | stage_delegates.add(TEMP(current_block, "fused_stage", stage.argument!.value!)); | |
| 5175 | stage_counters.add(null); | |
| 5176 | fi | |
| 5177 | od | |
| 5178 | si | |
| 5179 | ||
| 5180 | // Apply a fused chain's stages to `element` innermost-first, | |
| 5181 | // returning the fully-transformed value. A map result and an index | |
| 5182 | // result each land in a fresh temp so a downstream stage can reload | |
| 5183 | // them; a filter reject branches to `loop_start` (the next pull); an | |
| 5184 | // index stage builds INDEXED_VALUE(counter++, current) directly. | |
| 5185 | // Shared by the pure and guarded fused-loop emitters. | |
| 5186 | _apply_fused_stages( | |
| 5187 | fusion: Syntax.Process.PIPE_FUSION, | |
| 5188 | element: Value, | |
| 5189 | stage_delegates: Collections.List[TEMP?], | |
| 5190 | stage_counters: Collections.List[TEMP?], | |
| 5191 | brancher: BLOCK_BRANCHER, | |
| 5192 | loop_start: LABEL, | |
| 5193 | loop_end: LABEL | |
| 5194 | ) -> Value is | |
| 5195 | let int_type = _innate_symbol_lookup.get_int_type(); | |
| 5196 | ||
| 5197 | let current_value: Value mut = element; | |
| 5198 | ||
| 5199 | let index mut = fusion.stages_outermost_first.count - 1; | |
| 5200 | ||
| 5201 | while index >= 0 do | |
| 5202 | let stage = fusion.stages_outermost_first[index]; | |
| 5203 | ||
| 5204 | if stage.is_take then | |
| 5205 | // Decrement the counter for this element; once it goes | |
| 5206 | // negative the take limit is spent, so leave the loop. | |
| 5207 | // The element passes through unchanged otherwise. | |
| 5208 | brancher.branch(BRANCH.LT, cast Value(IR.Values.PRE_DECREMENT(stage_counters[index]!.il_name, int_type)), cast Value(Literal.NUMBER("0", int_type, "i4")), loop_end); | |
| 5209 | elif stage.is_skip then | |
| 5210 | // Decrement the counter; while it stays non-negative this | |
| 5211 | // is a leading element to drop, so pull the next one. | |
| 5212 | brancher.branch(BRANCH.GE, cast Value(IR.Values.PRE_DECREMENT(stage_counters[index]!.il_name, int_type)), cast Value(Literal.NUMBER("0", int_type, "i4")), loop_start); | |
| 5213 | else | |
| 5214 | let stage_result: Value mut; | |
| 5215 | ||
| 5216 | if stage.is_inlined then | |
| 5217 | // Assign the incoming element to the parameter local; | |
| 5218 | // the pre-harvested body IR reads it (and any captured | |
| 5219 | // outer locals) as ordinary locals - no delegate, no | |
| 5220 | // frame. | |
| 5221 | add(IR.Values.Store.LOCAL_VARIABLE(stage.param_local!, current_value)); | |
| 5222 | ||
| 5223 | stage_result = stage.inline_body!; | |
| 5224 | elif stage.is_index then | |
| 5225 | // INDEXED_VALUE(counter, current) with the counter | |
| 5226 | // post-incremented in place - no INDEX_PIPE object. | |
| 5227 | let arguments = Collections.LIST[Value](); | |
| 5228 | arguments.add(cast Value(IR.Values.POST_INCREMENT(stage_counters[index]!.il_name, int_type))); | |
| 5229 | arguments.add(current_value); | |
| 5230 | ||
| 5231 | stage_result = IR.Values.NEW(stage.indexed_value_type!, stage.indexed_value_constructor!, arguments); | |
| 5232 | else | |
| 5233 | let delegate = stage_delegates[index]!.load(); | |
| 5234 | let func_type = stage.argument!.value!.type!; | |
| 5235 | ||
| 5236 | let result_type = | |
| 5237 | if func_type.is_action then | |
| 5238 | _innate_symbol_lookup.get_void_type(); | |
| 5239 | else | |
| 5240 | func_type.arguments[func_type.arguments.count - 1]; | |
| 5241 | fi; | |
| 5242 | ||
| 5243 | let call_arguments = Collections.LIST[Value](); | |
| 5244 | call_arguments.add(current_value); | |
| 5245 | ||
| 5246 | stage_result = IR.Values.Call.CLOSURE(delegate, result_type, func_type.is_action, func_type, call_arguments); | |
| 5247 | fi | |
| 5248 | ||
| 5249 | if stage.is_filter then | |
| 5250 | // Rejected elements skip straight to the next pull. | |
| 5251 | brancher.branch(BRANCH.Z, stage_result, loop_start); | |
| 5252 | else | |
| 5253 | current_value = TEMP(current_block, "fused_mapped", stage_result).load(); | |
| 5254 | fi | |
| 5255 | fi | |
| 5256 | ||
| 5257 | index = index - 1; | |
| 5258 | od | |
| 5259 | ||
| 5260 | return current_value; | |
| 5261 | si | |
| 5262 | ||
| 5263 | // Lower a recognised fusible Pipe[T] chain to a single loop: | |
| 5264 | // drive the pinned base's own iterator, apply each map/filter/index | |
| 5265 | // stage inline per element (map/index assign a temp, filter skips to | |
| 5266 | // the next element), then bind the loop variable and run the | |
| 5267 | // body. The built MAP_PIPE / FilterPipe / INDEX_PIPE objects and | |
| 5268 | // their per-element virtual dispatch are never emitted. | |
| 5269 | _visit_fused_for(`for: Statements.FOR) is | |
| 5270 | let fusion = `for.fusion!; | |
| 5271 | let source = fusion.source; | |
| 5272 | let variable = `for.variable!; | |
| 5273 | let body = `for.body!; | |
| 5274 | ||
| 5275 | let brancher = get_brancher_for_block(); | |
| 5276 | let loop = _loops.enter_loop(); | |
| 5277 | ||
| 5278 | // Iterate the source directly - the wrapping pipe objects are | |
| 5279 | // never built. read_iterator is null when the source is its | |
| 5280 | // own iterator (e.g. a range). | |
| 5281 | let iterator: Value mut; | |
| 5282 | ||
| 5283 | if fusion.source_read_iterator? then | |
| 5284 | iterator = fusion.source_read_iterator.call(source.location, source.value!, Collections.LIST[Value](0), null, _function_caller); | |
| 5285 | else | |
| 5286 | iterator = source.value!; | |
| 5287 | fi | |
| 5288 | ||
| 5289 | variable.walk(self); | |
| 5290 | ||
| 5291 | let temp = TEMP(current_block, "fused_iterator", iterator); | |
| 5292 | ||
| 5293 | // Per-stage setup: an inlined stage needs a slot for its | |
| 5294 | // parameter local; a delegate stage hoists its function value | |
| 5295 | // into a local so the delegate is built once, not per element; | |
| 5296 | // an index stage needs a running counter, initialised to its | |
| 5297 | // start. | |
| 5298 | let stage_delegates = Collections.LIST[TEMP?](); | |
| 5299 | let stage_counters = Collections.LIST[TEMP?](); | |
| 5300 | ||
| 5301 | _setup_fused_stages(fusion, stage_delegates, stage_counters); | |
| 5302 | ||
| 5303 | brancher.label(loop.start); | |
| 5304 | ||
| 5305 | let has_next = fusion.source_move_next!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller); | |
| 5306 | ||
| 5307 | brancher.branch(BRANCH.Z, has_next, loop.end); | |
| 5308 | ||
| 5309 | let element = fusion.source_read_current!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller); | |
| 5310 | ||
| 5311 | let current_value = _apply_fused_stages(fusion, element, stage_delegates, stage_counters, brancher, loop.start, loop.end); | |
| 5312 | ||
| 5313 | gen_destructuring_initialize(variable.left, current_value); | |
| 5314 | ||
| 5315 | // Emit any anonymous-function bodies in the chain (the | |
| 5316 | // map/filter lambdas, plus anything inside the source). | |
| 5317 | // Walking these nodes emits the nested closure method bodies | |
| 5318 | // without emitting the pipe-construction IL itself. | |
| 5319 | source.walk(self); | |
| 5320 | ||
| 5321 | for stage in fusion.stages_outermost_first do | |
| 5322 | if stage.argument? then | |
| 5323 | stage.argument.walk(self); | |
| 5324 | fi | |
| 5325 | od | |
| 5326 | ||
| 5327 | body.walk(self); | |
| 5328 | ||
| 5329 | brancher.branch(loop.start); | |
| 5330 | ||
| 5331 | brancher.label(loop.end); | |
| 5332 | ||
| 5333 | super.visit(`for); | |
| 5334 | ||
| 5335 | _loops.leave_loop(); | |
| 5336 | si | |
| 5337 | ||
| 5338 | // Lower a fusible chain whose source could dynamically be a user | |
| 5339 | // Pipe with an overridden map/filter (any non-sealed source). The | |
| 5340 | // chain is fused behind a per-site guard that tests the source the | |
| 5341 | // same way pipe() does: `source isa Pipe[element]` true means | |
| 5342 | // pipe() would short-circuit to the source and run its own | |
| 5343 | // map/filter, so take the ordinary pipe-object loop; false means | |
| 5344 | // pipe() wraps the source in the built-in default pipe, so the | |
| 5345 | // fused loop is equivalent. Guard and pipe() agree by construction, | |
| 5346 | // so this is sound with no runtime-library change. | |
| 5347 | // | |
| 5348 | // Both paths share ONE copy of the loop body: generate_il emits | |
| 5349 | // precomputed IR and IR.Values.BLOCK is single-shot, so a | |
| 5350 | // val-block in the body could not be walked twice. Instead a | |
| 5351 | // loop-invariant `use_fused` flag selects the advance mechanism per | |
| 5352 | // element (a perfectly predictable branch). Never reached inside a | |
| 5353 | // state machine - visit(FOR)'s dispatch keeps generator/async loops | |
| 5354 | // on the normal lowering, whose iterator lives on the frame to | |
| 5355 | // survive yield/await. | |
| 5356 | _visit_guarded_for(`for: Statements.FOR) is | |
| 5357 | let fusion = `for.fusion!; | |
| 5358 | let source = fusion.source; | |
| 5359 | let variable = `for.variable!; | |
| 5360 | let body = `for.body!; | |
| 5361 | let expression = `for.expression!; | |
| 5362 | ||
| 5363 | let brancher = get_brancher_for_block(); | |
| 5364 | let loop = _loops.enter_loop(); | |
| 5365 | ||
| 5366 | let bool_type = _innate_symbol_lookup.get_bool_type(); | |
| 5367 | let element_type = `for.read_current!.return_type!; | |
| 5368 | ||
| 5369 | variable.walk(self); | |
| 5370 | ||
| 5371 | // Shared mutable locals, declared once before the guard branch. | |
| 5372 | // `use_fused` is set once (loop-invariant); `element_slot` | |
| 5373 | // carries the per-element value into the shared bind + body. | |
| 5374 | let use_fused = TEMP(current_block, "guard_use_fused", bool_type); | |
| 5375 | let element_slot = TEMP(current_block, "guard_element", element_type); | |
| 5376 | ||
| 5377 | // Fallback iterator (the built pipe chain's) and fused iterator | |
| 5378 | // (the source's own). Each is stored in only one branch; the | |
| 5379 | // other stays default and is never read on that path. | |
| 5380 | let pipe_iterator_type = | |
| 5381 | if `for.read_iterator? then | |
| 5382 | `for.read_iterator.return_type! | |
| 5383 | else | |
| 5384 | expression.value!.type! | |
| 5385 | fi; | |
| 5386 | ||
| 5387 | let source_iterator_type = | |
| 5388 | if fusion.source_read_iterator? then | |
| 5389 | fusion.source_read_iterator.return_type! | |
| 5390 | else | |
| 5391 | source.value!.type! | |
| 5392 | fi; | |
| 5393 | ||
| 5394 | let pipe_iterator = TEMP(current_block, "guard_pipe_iterator", pipe_iterator_type); | |
| 5395 | let source_iterator = TEMP(current_block, "guard_source_iterator", source_iterator_type); | |
| 5396 | ||
| 5397 | // Per-stage setup for the fused path (shared with the pure | |
| 5398 | // fused loop): declared before the guard branch. On the fallback | |
| 5399 | // path these stay unused - a hoisted delegate is simply built | |
| 5400 | // and not read. | |
| 5401 | let stage_delegates = Collections.LIST[TEMP?](); | |
| 5402 | let stage_counters = Collections.LIST[TEMP?](); | |
| 5403 | ||
| 5404 | _setup_fused_stages(fusion, stage_delegates, stage_counters); | |
| 5405 | ||
| 5406 | let fused_setup = LABEL(); | |
| 5407 | let after_setup = LABEL(); | |
| 5408 | ||
| 5409 | // if source isa Pipe[element] then fall back else fuse. | |
| 5410 | let is_pipe = cast Value(IR.Values.ISA(bool_type, fusion.guard_isa_type!, source.value!)); | |
| 5411 | ||
| 5412 | brancher.branch(BRANCH.Z, is_pipe, fused_setup, "pipe-guard"); | |
| 5413 | ||
| 5414 | // Fallback setup: build the pipe chain and iterate it, exactly | |
| 5415 | // as the normal for loop would. | |
| 5416 | use_fused.store(Literal.NUMBER("0", bool_type, "i4")); | |
| 5417 | ||
| 5418 | let pipe_iterator_value = | |
| 5419 | if `for.read_iterator? then | |
| 5420 | `for.read_iterator.call(expression.location, expression.value!, Collections.LIST[Value](0), null, _function_caller) | |
| 5421 | else | |
| 5422 | expression.value! | |
| 5423 | fi; | |
| 5424 | ||
| 5425 | pipe_iterator.store(pipe_iterator_value); | |
| 5426 | ||
| 5427 | brancher.branch(after_setup); | |
| 5428 | ||
| 5429 | // Fused setup: drive the source's own iterator directly - the | |
| 5430 | // pipe objects are never built. | |
| 5431 | brancher.label(fused_setup); | |
| 5432 | ||
| 5433 | use_fused.store(Literal.NUMBER("1", bool_type, "i4")); | |
| 5434 | ||
| 5435 | let source_iterator_value = | |
| 5436 | if fusion.source_read_iterator? then | |
| 5437 | fusion.source_read_iterator.call(source.location, source.value!, Collections.LIST[Value](0), null, _function_caller) | |
| 5438 | else | |
| 5439 | source.value! | |
| 5440 | fi; | |
| 5441 | ||
| 5442 | source_iterator.store(source_iterator_value); | |
| 5443 | ||
| 5444 | brancher.label(after_setup); | |
| 5445 | ||
| 5446 | // --- Merged loop over one shared body. --- | |
| 5447 | brancher.label(loop.start); | |
| 5448 | ||
| 5449 | let fused_advance = LABEL(); | |
| 5450 | let bind = LABEL(); | |
| 5451 | ||
| 5452 | brancher.branch(BRANCH.NZ, use_fused.load(), fused_advance, "use-fused"); | |
| 5453 | ||
| 5454 | // Fallback advance: pull from the pipe chain's iterator. | |
| 5455 | let fallback_has_next = `for.move_next!.call(expression.location, pipe_iterator.load(), Collections.LIST[Value](0), null, _function_caller); | |
| 5456 | ||
| 5457 | brancher.branch(BRANCH.Z, fallback_has_next, loop.end); | |
| 5458 | ||
| 5459 | let fallback_current = `for.read_current!.call(expression.location, pipe_iterator.load(), Collections.LIST[Value](0), null, _function_caller); | |
| 5460 | ||
| 5461 | element_slot.store(fallback_current); | |
| 5462 | ||
| 5463 | brancher.branch(bind); | |
| 5464 | ||
| 5465 | // Fused advance: pull from the source, apply stages inline. | |
| 5466 | brancher.label(fused_advance); | |
| 5467 | ||
| 5468 | let has_next = fusion.source_move_next!.call(source.location, source_iterator.load(), Collections.LIST[Value](0), null, _function_caller); | |
| 5469 | ||
| 5470 | brancher.branch(BRANCH.Z, has_next, loop.end); | |
| 5471 | ||
| 5472 | let element = fusion.source_read_current!.call(source.location, source_iterator.load(), Collections.LIST[Value](0), null, _function_caller); | |
| 5473 | ||
| 5474 | let current_value = _apply_fused_stages(fusion, element, stage_delegates, stage_counters, brancher, loop.start, loop.end); | |
| 5475 | ||
| 5476 | element_slot.store(current_value); | |
| 5477 | ||
| 5478 | // --- Shared bind + body (walked once). --- | |
| 5479 | brancher.label(bind); | |
| 5480 | ||
| 5481 | gen_destructuring_initialize(variable.left, element_slot.load()); | |
| 5482 | ||
| 5483 | body.walk(self); | |
| 5484 | ||
| 5485 | brancher.branch(loop.start); | |
| 5486 | ||
| 5487 | brancher.label(loop.end); | |
| 5488 | ||
| 5489 | // Emit the nested closure method bodies (the map/filter lambdas | |
| 5490 | // and anything inside the source) exactly once. The fallback | |
| 5491 | // path's pipe chain and the fused path's delegate temps both | |
| 5492 | // reference these methods; a second walk would redefine them. | |
| 5493 | source.walk(self); | |
| 5494 | ||
| 5495 | for stage in fusion.stages_outermost_first do | |
| 5496 | if stage.argument? then | |
| 5497 | stage.argument.walk(self); | |
| 5498 | fi | |
| 5499 | od | |
| 5500 | ||
| 5501 | super.visit(`for); | |
| 5502 | ||
| 5503 | _loops.leave_loop(); | |
| 5504 | si | |
| 5505 | ||
| 5506 | pre(pragma: Statements.PRAGMA) -> bool is | |
| 5507 | process_pragma(pragma.pragma, true); | |
| 5508 | return false; | |
| 5509 | si | |
| 5510 | ||
| 5511 | visit(pragma: Statements.PRAGMA) is | |
| 5512 | process_pragma(pragma.pragma, false); | |
| 5513 | si | |
| 5514 | ||
| 5515 | pre(labelled: Statements.LABELLED) -> bool is | |
| 5516 | super.pre(labelled); | |
| 5517 | ||
| 5518 | return true; | |
| 5519 | si | |
| 5520 | ||
| 5521 | visit(labelled: Statements.LABELLED) is | |
| 5522 | _loops.next_name(labelled.label.name); | |
| 5523 | ||
| 5524 | labelled.statement.walk(self); | |
| 5525 | ||
| 5526 | super.visit(labelled); | |
| 5527 | si | |
| 5528 | ||
| 5529 | visit(`break: Statements.BREAK) is | |
| 5530 | let label = `break.label; | |
| 5531 | ||
| 5532 | let target = | |
| 5533 | if label? then | |
| 5534 | _loops.find(label.name) | |
| 5535 | else | |
| 5536 | _loops.get_current_loop() | |
| 5537 | fi; | |
| 5538 | ||
| 5539 | if !target? then | |
| 5540 | if label? then | |
| 5541 | _logger.error(`break.location, "no enclosing loop labelled {label.name}"); | |
| 5542 | else | |
| 5543 | _logger.error(`break.location, "break outside of loop"); | |
| 5544 | fi | |
| 5545 | ||
| 5546 | return; | |
| 5547 | fi | |
| 5548 | ||
| 5549 | let brancher = get_brancher_for_block(); | |
| 5550 | ||
| 5551 | if _loops.is_in_try then | |
| 5552 | brancher.leave(target.end); | |
| 5553 | else | |
| 5554 | brancher.branch(target.end); | |
| 5555 | fi | |
| 5556 | si | |
| 5557 | ||
| 5558 | visit(`continue: Statements.CONTINUE) is | |
| 5559 | let label = `continue.label; | |
| 5560 | ||
| 5561 | let target = | |
| 5562 | if label? then | |
| 5563 | _loops.find(label.name) | |
| 5564 | else | |
| 5565 | _loops.get_current_loop() | |
| 5566 | fi; | |
| 5567 | ||
| 5568 | if !target? then | |
| 5569 | if label? then | |
| 5570 | _logger.error(`continue.location, "no enclosing loop labelled {label.name}"); | |
| 5571 | else | |
| 5572 | _logger.error(`continue.location, "continue outside of loop"); | |
| 5573 | fi | |
| 5574 | ||
| 5575 | return; | |
| 5576 | fi | |
| 5577 | ||
| 5578 | let brancher = get_brancher_for_block(); | |
| 5579 | ||
| 5580 | if _loops.is_in_try then | |
| 5581 | brancher.leave(target.start); | |
| 5582 | else | |
| 5583 | brancher.branch(target.start); | |
| 5584 | fi | |
| 5585 | si | |
| 5586 | ||
| 5587 | visit(interpolation: Expressions.STRING_INTERPOLATION) is | |
| 5588 | ensure_runtime_symbols_are_materialized(); | |
| 5589 | ||
| 5590 | enter_block(cast IR.Values.BLOCK?(interpolation.value)!); | |
| 5591 | ||
| 5592 | let string_type = _innate_symbol_lookup.get_string_type(); | |
| 5593 | ||
| 5594 | let literal_length = Literal.NUMBER("{interpolation.literal_length}", _innate_symbol_lookup.get_int_type(), "i4"); | |
| 5595 | let expression_count = Literal.NUMBER("{interpolation.expression_count}", _innate_symbol_lookup.get_int_type(), "i4"); | |
| 5596 | ||
| 5597 | let interpolator = | |
| 5598 | TEMP( | |
| 5599 | current_block, | |
| 5600 | "interpolator", | |
| 5601 | IR.Values.NEW( | |
| 5602 | _interpolation_handler!, | |
| 5603 | _constructor, [ | |
| 5604 | literal_length, | |
| 5605 | expression_count | |
| 5606 | ]:Value | |
| 5607 | ) | |
| 5608 | ); | |
| 5609 | ||
| 5610 | for eaf in interpolation.values do | |
| 5611 | let expression = eaf.expression; | |
| 5612 | let alignment = eaf.alignment; | |
| 5613 | let format = eaf.format; | |
| 5614 | let expr_value = expression.value!; | |
| 5615 | ||
| 5616 | let args = Collections.LIST[Value](1); | |
| 5617 | ||
| 5618 | args.add(expr_value); | |
| 5619 | ||
| 5620 | if isa Expressions.Literals.STRING(expression) then | |
| 5621 | if expression.value_string.length == 0 then | |
| 5622 | continue; | |
| 5623 | fi | |
| 5624 | ||
| 5625 | let call = _append_literal.call(interpolation.location, interpolator.load(), args, null, _function_caller); | |
| 5626 | ||
| 5627 | add(call); | |
| 5628 | else | |
| 5629 | let append_formatted: Semantic.Symbols.Symbol mut; | |
| 5630 | let args = Collections.LIST[Value](3); | |
| 5631 | ||
| 5632 | args.add(expr_value); | |
| 5633 | ||
| 5634 | if format? /\ alignment? then | |
| 5635 | args.add(alignment.value!); | |
| 5636 | args.add(Literal.STRING(format, string_type)); | |
| 5637 | ||
| 5638 | append_formatted = _append_formatted_generic_alignment_format.specialize([expr_value.type!]); | |
| 5639 | ||
| 5640 | elif format? then | |
| 5641 | args.add(Literal.STRING(format, string_type)); | |
| 5642 | ||
| 5643 | append_formatted = _append_formatted_generic_format.specialize([expr_value.type!]); | |
| 5644 | elif alignment? then | |
| 5645 | args.add(alignment.value!); | |
| 5646 | ||
| 5647 | append_formatted = _append_formatted_generic_alignment.specialize([expr_value.type!]); | |
| 5648 | else | |
| 5649 | append_formatted = _append_formatted_generic.specialize([expr_value.type!]); | |
| 5650 | fi | |
| 5651 | ||
| 5652 | let call = append_formatted.call(interpolation.location, interpolator.load(), args, null, _function_caller); | |
| 5653 | ||
| 5654 | add(call); | |
| 5655 | fi | |
| 5656 | od | |
| 5657 | ||
| 5658 | let result = _to_string_and_clear.call(interpolation.location, interpolator.load(), System.Array.empty`[Value](), null, _function_caller); | |
| 5659 | add(result); | |
| 5660 | ||
| 5661 | leave_block(); | |
| 5662 | si | |
| 5663 | si | |
| 5664 | ||
| 5665 | class LOOP_LABELS is | |
| 5666 | is_try: bool; | |
| 5667 | is_in_finally: bool public; | |
| 5668 | is_loop: bool => !is_try; | |
| 5669 | ||
| 5670 | name: string?; | |
| 5671 | start: LABEL; | |
| 5672 | middle: LABEL; | |
| 5673 | end: LABEL; | |
| 5674 | ||
| 5675 | return_needed: TEMP?; | |
| 5676 | return_value: TEMP?; | |
| 5677 | ||
| 5678 | init(name: string?) is | |
| 5679 | is_try = false; | |
| 5680 | self.name = name; | |
| 5681 | start = LABEL(); | |
| 5682 | end = LABEL(); | |
| 5683 | si | |
| 5684 | ||
| 5685 | init(return_needed: TEMP, return_value: TEMP?) is | |
| 5686 | is_try = true; | |
| 5687 | self.return_needed = return_needed; | |
| 5688 | self.return_value = return_value; | |
| 5689 | start = LABEL(); | |
| 5690 | middle = LABEL(); | |
| 5691 | end = LABEL(); | |
| 5692 | si | |
| 5693 | ||
| 5694 | matches(name: string) -> bool => self.name? /\ self.name! =~ name; | |
| 5695 | si | |
| 5696 | ||
| 5697 | class LOOP_LABEL_STACK is | |
| 5698 | _next_name: string?; | |
| 5699 | ||
| 5700 | loops: Collections.MutableList[LOOP_LABELS]; | |
| 5701 | is_in_loop: bool => get_current_loop() != null; | |
| 5702 | is_in_try: bool => get_current_try() != null; | |
| 5703 | ||
| 5704 | get_current_try() -> LOOP_LABELS? is | |
| 5705 | let index: int mut = loops.count - 1; | |
| 5706 | ||
| 5707 | while index >= 0 do | |
| 5708 | let result = loops[index]; | |
| 5709 | ||
| 5710 | if result.is_try then | |
| 5711 | return result; | |
| 5712 | fi | |
| 5713 | ||
| 5714 | index = index - 1; | |
| 5715 | od | |
| 5716 | return null; | |
| 5717 | si | |
| 5718 | ||
| 5719 | // Number of `is_try` entries currently on the stack. Cached | |
| 5720 | // at val-block entry; a return inside the body whose count | |
| 5721 | // exceeds the cached value opened a try INSIDE the block, | |
| 5722 | // so `br end_label` would cross the try's boundary. | |
| 5723 | open_try_count: int is | |
| 5724 | let count mut = 0; | |
| 5725 | for l in loops do | |
| 5726 | if l.is_try then | |
| 5727 | count = count + 1; | |
| 5728 | fi | |
| 5729 | od | |
| 5730 | return count; | |
| 5731 | si | |
| 5732 | ||
| 5733 | get_current_loop() -> LOOP_LABELS? is | |
| 5734 | let index: int mut = loops.count - 1; | |
| 5735 | ||
| 5736 | while index >= 0 do | |
| 5737 | let result = loops[index]; | |
| 5738 | ||
| 5739 | if result.is_loop then | |
| 5740 | return result; | |
| 5741 | fi | |
| 5742 | ||
| 5743 | index = index - 1; | |
| 5744 | od | |
| 5745 | return null; | |
| 5746 | si | |
| 5747 | ||
| 5748 | init() is | |
| 5749 | loops = Collections.LIST[LOOP_LABELS](); | |
| 5750 | si | |
| 5751 | ||
| 5752 | next_name(name: string) is | |
| 5753 | _next_name = name; | |
| 5754 | si | |
| 5755 | ||
| 5756 | find(name: string) -> LOOP_LABELS? is | |
| 5757 | let index: int mut = loops.count - 1; | |
| 5758 | ||
| 5759 | while index >= 0 do | |
| 5760 | let result = loops[index]; | |
| 5761 | ||
| 5762 | if result.matches(name) then | |
| 5763 | return result; | |
| 5764 | fi | |
| 5765 | ||
| 5766 | index = index - 1; | |
| 5767 | od | |
| 5768 | return null; | |
| 5769 | si | |
| 5770 | ||
| 5771 | enter_try(return_needed: TEMP, return_value: TEMP?) -> LOOP_LABELS is | |
| 5772 | let result = LOOP_LABELS(return_needed, return_value); | |
| 5773 | ||
| 5774 | loops.add(result); | |
| 5775 | ||
| 5776 | return result; | |
| 5777 | si | |
| 5778 | ||
| 5779 | enter_loop() -> LOOP_LABELS is | |
| 5780 | let result = LOOP_LABELS(_next_name); | |
| 5781 | ||
| 5782 | _next_name = null; | |
| 5783 | ||
| 5784 | loops.add(result); | |
| 5785 | ||
| 5786 | return result; | |
| 5787 | si | |
| 5788 | ||
| 5789 | leave_loop() is | |
| 5790 | loops.remove_at(loops.count - 1); | |
| 5791 | si | |
| 5792 | si | |
| 5793 | si |